- No saved addresses found. Add one to continue checkout.
+
+
+
+
- ) : (
-
- {addresses.map((address) => (
-
+ >
);
}
diff --git a/frontend/web/src/app/(user)/order-success/OrderSuccessClient.tsx b/frontend/web/src/app/(user)/order-success/OrderSuccessClient.tsx
new file mode 100644
index 0000000..8a7714a
--- /dev/null
+++ b/frontend/web/src/app/(user)/order-success/OrderSuccessClient.tsx
@@ -0,0 +1,149 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+import { useRouter, useSearchParams } from 'next/navigation';
+import { toast } from 'sonner';
+import OrderSuccessOverlay from '@/src/components/OrderSuccessOverlay';
+import { checkoutService } from '@/src/services/checkout.service';
+
+export default function OrderSuccessClient() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const sessionId = searchParams.get('session_id');
+
+ const [status, setStatus] = useState<'loading' | 'success' | 'failed'>(
+ 'loading',
+ );
+ const [errorMessage, setErrorMessage] = useState
(null);
+ const [orderId, setOrderId] = useState(null);
+
+ useEffect(() => {
+ let cancelled = false;
+
+ const getErrorMessage = (error: unknown) => {
+ if (
+ error &&
+ typeof error === 'object' &&
+ 'response' in error &&
+ error.response &&
+ typeof error.response === 'object' &&
+ 'data' in error.response &&
+ error.response.data &&
+ typeof error.response.data === 'object' &&
+ 'message' in error.response.data &&
+ typeof error.response.data.message === 'string'
+ ) {
+ return error.response.data.message;
+ }
+
+ return 'Unable to verify Stripe payment.';
+ };
+
+ const verify = async () => {
+ if (!sessionId) {
+ setStatus('failed');
+ setErrorMessage('Missing Stripe session id.');
+ return;
+ }
+
+ for (let attempt = 0; attempt < 8; attempt += 1) {
+ try {
+ const result = await checkoutService.verifyStripeSession(sessionId);
+
+ if (cancelled) return;
+
+ if (result.status === 'SUCCESS') {
+ setOrderId(result.orderId ?? null);
+ setStatus('success');
+ return;
+ }
+
+ if (result.status === 'FAILED') {
+ setStatus('failed');
+ setErrorMessage('Stripe payment was not completed.');
+ return;
+ }
+ } catch (error) {
+ console.error('Failed to verify Stripe session', error);
+
+ if (cancelled) return;
+
+ if (attempt === 7) {
+ setStatus('failed');
+ setErrorMessage(getErrorMessage(error));
+ return;
+ }
+ }
+
+ await new Promise((resolve) => setTimeout(resolve, 1500));
+ }
+
+ if (!cancelled) {
+ setStatus('failed');
+ setErrorMessage('Stripe payment is still processing. Please refresh.');
+ }
+ };
+
+ verify();
+
+ return () => {
+ cancelled = true;
+ };
+ }, [sessionId]);
+
+ const handleOverlayComplete = () => {
+ const target = orderId
+ ? `/orders?orderId=${encodeURIComponent(orderId)}`
+ : '/orders';
+ router.replace(target);
+ };
+
+ if (status === 'success') {
+ return ;
+ }
+
+ if (status === 'failed') {
+ return (
+
+
+
+ Payment not completed
+
+
+ {errorMessage ?? 'Stripe did not confirm this payment.'}
+
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
Verifying payment...
+
+ Confirming your Stripe checkout session.
+
+
+
+ );
+}
diff --git a/frontend/web/src/app/(user)/order-success/page.tsx b/frontend/web/src/app/(user)/order-success/page.tsx
new file mode 100644
index 0000000..2ec937a
--- /dev/null
+++ b/frontend/web/src/app/(user)/order-success/page.tsx
@@ -0,0 +1,12 @@
+import { Suspense } from 'react';
+import OrderSuccessClient from './OrderSuccessClient';
+
+export const dynamic = 'force-dynamic';
+
+export default function OrderSuccessPage() {
+ return (
+ Verifying payment... }>
+