Skip to content

Commit 1042ac7

Browse files
committed
fix: order not created in db issue
1 parent 6a2a5c6 commit 1042ac7

7 files changed

Lines changed: 140 additions & 39 deletions

File tree

apps/order-service/src/controllers/order.controller.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,20 @@ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
1212

1313
// Shared order processing logic - Hardened for Production
1414
const processOrderCreation = async (userId: string, sessionId: string, paymentMethod: string) => {
15+
const createdKey = `order-created:${sessionId}`;
16+
// Atomically set processed flag / acquire lock to prevent duplicate creation
17+
const lockAcquired = await redis.set(createdKey, "true", "EX", 86400, "NX");
18+
if (!lockAcquired) {
19+
console.log(`[Order] Order for session ${sessionId} is already processed or being processed.`);
20+
return;
21+
}
22+
1523
const sessionKey = `payment-session:${sessionId}`;
1624
const sessionData = await redis.get(sessionKey);
1725

1826
if (!sessionData) {
27+
// Release lock so it can be retried later
28+
await redis.del(createdKey);
1929
throw new Error(`Critical: Payment session ${sessionId} not found or expired.`);
2030
}
2131

@@ -290,7 +300,33 @@ export const verifyPaymentSession = async (req: any, res: Response, next: NextFu
290300
const sessionId = req.query.sessionId as string;
291301
const sessionData = await redis.get(`payment-session:${sessionId}`);
292302
if (!sessionData) return res.status(404).json({ message: "Session expired" });
293-
return res.status(200).json({ success: true, session: JSON.parse(sessionData) });
303+
304+
const session = JSON.parse(sessionData);
305+
306+
// Self-heal: Check if order has already been created for this session
307+
const createdKey = `order-created:${sessionId}`;
308+
const isCreated = await redis.get(createdKey);
309+
310+
if (!isCreated) {
311+
console.log(`[Order] Order not marked as created for session ${sessionId}. Checking Stripe...`);
312+
try {
313+
// Search for Stripe PaymentIntent matching this sessionId
314+
const searchResults = await stripe.paymentIntents.search({
315+
query: `metadata['sessionId']:'${sessionId}'`,
316+
});
317+
318+
const paymentIntent = searchResults.data[0];
319+
if (paymentIntent && paymentIntent.status === "succeeded") {
320+
console.log(`[Order] Found succeeded Stripe PaymentIntent ${paymentIntent.id} for session ${sessionId}. Self-healing order creation...`);
321+
const userId = paymentIntent.metadata.userId || session.userId;
322+
await processOrderCreation(userId, sessionId, "Stripe");
323+
}
324+
} catch (stripeError) {
325+
console.error("[Order] Stripe session self-healing check failed:", stripeError);
326+
}
327+
}
328+
329+
return res.status(200).json({ success: true, session });
294330
} catch (error) {
295331
return next(error)
296332
}

apps/seller-ui/src/app/(routes)/dashboard/settings/page.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,16 @@ const SettingsPage = () => {
255255
<button
256256
onClick={connectStripe}
257257
disabled={isConnectingStripe}
258-
className="bg-blue-600 hover:bg-blue-500 text-white font-black uppercase tracking-[0.2em] text-[10px] px-12 py-5 rounded-2xl transition-all shadow-xl shadow-blue-600/20 disabled:opacity-50"
258+
className="bg-blue-600 hover:bg-blue-500 text-white font-black uppercase tracking-[0.2em] text-[10px] px-12 py-5 rounded-2xl transition-all shadow-xl shadow-blue-600/20 disabled:opacity-50 flex items-center justify-center gap-2 mx-auto"
259259
>
260-
{isConnectingStripe ? "Connecting..." : "Connect Stripe Account"}
260+
{isConnectingStripe ? (
261+
<>
262+
<Loader2 className="animate-spin w-4 h-4" />
263+
Connecting...
264+
</>
265+
) : (
266+
"Connect Stripe Account"
267+
)}
261268
</button>
262269
</div>
263270
)}

apps/seller-ui/src/app/(routes)/signup/page.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Link from "next/link";
33
import { useRouter } from "next/navigation";
44
import React, { useRef, useState } from "react";
55
import { useForm } from "react-hook-form";
6-
import { Eye, EyeOff } from "lucide-react";
6+
import { Eye, EyeOff, Loader2 } from "lucide-react";
77
import { useMutation } from "@tanstack/react-query";
88
import axios, { AxiosError } from "axios";
99
import countries from "@/utils/countries";
@@ -27,6 +27,7 @@ const Signup = () => {
2727
const [otp, setOtp] = useState(["", "", "", ""]);
2828
const [sellerData, setSellerData] = useState<FormData | null>(null);
2929
const [sellerId, setSellerId] = useState("");
30+
const [isConnectingStripe, setIsConnectingStripe] = useState(false);
3031
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
3132

3233
const {
@@ -118,6 +119,7 @@ const Signup = () => {
118119

119120
const connectStripe = async () => {
120121
try {
122+
setIsConnectingStripe(true);
121123
console.log(sellerId, "SellerId");
122124
if (!sellerId) {
123125
toast.error("Seller ID is missing. Please restart signup.");
@@ -139,6 +141,8 @@ const Signup = () => {
139141
error.message ||
140142
"Failed to connect to Stripe"
141143
);
144+
} finally {
145+
setIsConnectingStripe(false);
142146
}
143147
};
144148

@@ -360,9 +364,19 @@ const Signup = () => {
360364
<br />
361365
<button
362366
onClick={connectStripe}
363-
className="w-full m-auto flex items-center justify-center gap-3 text-lg bg-[#334155] text-white py-2 rounded-lg"
367+
disabled={isConnectingStripe}
368+
className="w-full m-auto flex items-center justify-center gap-3 text-lg bg-[#334155] text-white py-2 rounded-lg disabled:opacity-50"
364369
>
365-
Connect <StripeLogo />
370+
{isConnectingStripe ? (
371+
<>
372+
<Loader2 className="animate-spin w-5 h-5" />
373+
Connecting...
374+
</>
375+
) : (
376+
<>
377+
Connect <StripeLogo />
378+
</>
379+
)}
366380
</button>
367381
</div>
368382
)}

apps/seller-ui/src/app/shared/components/sidebar/sidebar.item.tsx

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,34 @@ interface Props {
55
icon: React.ReactNode;
66
title: string;
77
isActive?: boolean;
8-
href: string;
8+
href?: string;
9+
onClick?: () => void;
910
}
1011

11-
const SidebarItem = ({ icon, title, isActive, href }: Props) => {
12-
return (
13-
<Link href={href} className="my-2 block">
14-
<div
15-
className={`flex gap-2 w-full min-h-12 h-full items-center px-[13px] rounded-lg cursor-pointer transition hover:bg-[#2b2f31]
16-
${
17-
isActive &&
18-
"scale-[.98] bg-[#0f3158] fill-blue-200 hover:!bg-[#0f3158d6]"
19-
} `}
20-
>
21-
{icon}
22-
<h5 className="text-slate-200 text-lg">{title}</h5>
23-
</div>
24-
</Link>
12+
const SidebarItem = ({ icon, title, isActive, href, onClick }: Props) => {
13+
const content = (
14+
<div
15+
onClick={onClick}
16+
className={`flex gap-2 w-full min-h-12 h-full items-center px-[13px] rounded-lg cursor-pointer transition hover:bg-[#2b2f31]
17+
${
18+
isActive &&
19+
"scale-[.98] bg-[#0f3158] fill-blue-200 hover:!bg-[#0f3158d6]"
20+
} `}
21+
>
22+
{icon}
23+
<h5 className="text-slate-200 text-lg">{title}</h5>
24+
</div>
2525
);
26+
27+
if (href) {
28+
return (
29+
<Link href={href} className="my-2 block">
30+
{content}
31+
</Link>
32+
);
33+
}
34+
35+
return <div className="my-2 block">{content}</div>;
2636
};
2737

2838
export default SidebarItem;

apps/seller-ui/src/app/shared/components/sidebar/sidebar.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
"use client";
22

33
import useSidebar from "@/hooks/useSidebar";
4-
import { usePathname } from "next/navigation";
4+
import { usePathname, useRouter } from "next/navigation";
55
import React, { useEffect } from "react";
66
import Box from "../box";
77
import { Sidebar } from "./sidebar.styles";
88
import Link from "next/link";
99
import useSeller from "@/hooks/useSeller";
1010
import Logo from "@/assets/svgs/logo";
1111
import SidebarItem from "./sidebar.item";
12+
import axiosInstance from "@/utils/axiosInstance";
13+
import toast from "react-hot-toast";
1214
import {
1315
BellPlus,
1416
BellRing,
@@ -28,12 +30,24 @@ import SidebarMenu from "./sidebar.menu";
2830
const SideBarWrapper = () => {
2931
const { activeSidebar, setActiveSidebar } = useSidebar();
3032
const pathName = usePathname();
33+
const router = useRouter();
3134
const { seller } = useSeller();
3235

3336
useEffect(() => {
3437
setActiveSidebar(pathName);
3538
}, [pathName, setActiveSidebar]);
3639

40+
const handleLogout = async () => {
41+
try {
42+
await axiosInstance.get("/api/logout");
43+
toast.success("Logged out successfully");
44+
router.push("/login");
45+
} catch (error: any) {
46+
console.error("Logout failed:", error);
47+
toast.error("Logout failed. Please try again.");
48+
}
49+
};
50+
3751
const getIconColor = (path: string) => {
3852
return activeSidebar === path ? "#0085ff" : "#969696";
3953
};
@@ -196,8 +210,7 @@ const SideBarWrapper = () => {
196210
icon={
197211
<LogOut size={20} color={getIconColor("/dashboard/logout")} />
198212
}
199-
isActive={activeSidebar === "/dashboard/logout"}
200-
href="/dashboard/logout"
213+
onClick={handleLogout}
201214
/>
202215
</SidebarMenu>
203216
</div>

docker-compose.production.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ services:
5959
command: |
6060
bash -c '
6161
echo "Creating Kafka Topics...."
62-
kafka-topics --create --topic user-events --bootstrap-server kafka:29092 --replication-factor 1 --partitions 1
62+
kafka-topics --create --topic users-events --bootstrap-server kafka:29092 --replication-factor 1 --partitions 1
6363
kafka-topics --create --topic logs --bootstrap-server kafka:29092 --replication-factor 1 --partitions 1
64-
kafka-topics --create --topic chat.new_message --bootstrap-server kafka:29092 --replication-factor 1 --partitions 1
64+
kafka-topics --create --topic chat.new.message --bootstrap-server kafka:29092 --replication-factor 1 --partitions 1
6565
echo "Kafka Topics Created Successfully...."
6666
'
6767
restart: no

packages/utils/kafka/index.ts

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,35 @@
1-
import {Kafka} from "kafkajs";
2-
3-
export const kafka = new Kafka({
4-
clientId: "kafka-service",
5-
brokers: [process.env.KAFKA_BROKER!],
6-
ssl: {
7-
rejectUnauthorized: false,
8-
},
9-
sasl: {
10-
mechanism: "plain",
11-
username: process.env.KAFKA_API_KEY!,
12-
password: process.env.KAFKA_API_SECRET!
1+
import { Kafka, KafkaConfig } from "kafkajs";
2+
3+
const getEnv = (key: string): string | undefined => {
4+
const value = process.env[key];
5+
if (value !== undefined) return value.trim();
6+
7+
for (const envKey of Object.keys(process.env)) {
8+
if (envKey.trim() === key) {
9+
return process.env[envKey]?.trim();
1310
}
14-
})
11+
}
12+
return undefined;
13+
};
14+
15+
const broker = getEnv("KAFKA_BROKER") || getEnv("KAFKA_BROKERS") || "localhost:9092";
16+
const apiKey = getEnv("KAFKA_API_KEY");
17+
const apiSecret = getEnv("KAFKA_API_SECRET");
18+
19+
const kafkaConfig: KafkaConfig = {
20+
clientId: "kafka-service",
21+
brokers: [broker],
22+
};
23+
24+
if (apiKey && apiSecret) {
25+
kafkaConfig.ssl = {
26+
rejectUnauthorized: false,
27+
};
28+
kafkaConfig.sasl = {
29+
mechanism: "plain",
30+
username: apiKey,
31+
password: apiSecret,
32+
};
33+
}
34+
35+
export const kafka = new Kafka(kafkaConfig);

0 commit comments

Comments
 (0)