From 2386e94c6d26e05ed92718c74cb324c44e691817 Mon Sep 17 00:00:00 2001 From: sepehr Date: Sun, 31 May 2026 23:05:01 +0200 Subject: [PATCH] fix: page /checkout/success qui auto-sync le plan apres paiement Stripe (success_url corrige) --- frontend/src/app/checkout/success/page.tsx | 90 ++++++++++++++++++++++ services/payment_service.py | 2 +- 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 frontend/src/app/checkout/success/page.tsx diff --git a/frontend/src/app/checkout/success/page.tsx b/frontend/src/app/checkout/success/page.tsx new file mode 100644 index 0000000..93568e7 --- /dev/null +++ b/frontend/src/app/checkout/success/page.tsx @@ -0,0 +1,90 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useSearchParams, useRouter } from 'next/navigation'; +import { API_BASE } from '@/lib/config'; +import { CheckCircle2, XCircle, RefreshCw } from 'lucide-react'; + +/** + * /checkout/success + * Stripe redirects here after a successful payment. + * We call /api/v1/auth/checkout/sync to update the user plan in DB, + * then redirect to /dashboard/profile?tab=subscription. + */ +export default function CheckoutSuccessPage() { + const searchParams = useSearchParams(); + const router = useRouter(); + const sessionId = searchParams.get('session_id'); + + const [status, setStatus] = useState<'syncing' | 'ok' | 'error'>('syncing'); + const [message, setMessage] = useState(''); + + useEffect(() => { + if (!sessionId) { + // No session_id — just redirect to profile + router.replace('/dashboard/profile?tab=subscription'); + return; + } + + const token = localStorage.getItem('token'); + if (!token) { + router.replace('/auth/login'); + return; + } + + // Sync the checkout session to update plan in DB + fetch(`${API_BASE}/api/v1/auth/checkout/sync?session_id=${sessionId}`, { + headers: { Authorization: `Bearer ${token}` }, + }) + .then(async (res) => { + let data: any = {}; + try { data = await res.json(); } catch { /* ignore */ } + if (res.ok) { + setStatus('ok'); + setMessage(data.data?.plan ? `Forfait ${data.data.plan} activé !` : 'Abonnement activé !'); + // Redirect after 2s + setTimeout(() => router.replace('/dashboard/profile?tab=subscription'), 2000); + } else { + setStatus('error'); + setMessage(data.message ?? data.error ?? 'Erreur de synchronisation'); + setTimeout(() => router.replace('/dashboard/profile?tab=subscription'), 3000); + } + }) + .catch(() => { + setStatus('error'); + setMessage('Erreur réseau. Votre paiement est confirmé — rechargez votre profil.'); + setTimeout(() => router.replace('/dashboard/profile?tab=subscription'), 3000); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sessionId]); + + return ( +
+
+ {status === 'syncing' && ( + <> + +

Activation en cours…

+

Nous mettons à jour votre abonnement, veuillez patienter.

+ + )} + {status === 'ok' && ( + <> + +

Paiement confirmé !

+

{message}

+

Redirection vers votre profil…

+ + )} + {status === 'error' && ( + <> + +

Paiement reçu

+

{message}

+

Redirection…

+ + )} +
+
+ ); +} diff --git a/services/payment_service.py b/services/payment_service.py index 1fa699a..20bdc2d 100644 --- a/services/payment_service.py +++ b/services/payment_service.py @@ -117,7 +117,7 @@ async def create_checkout_session( mode="subscription", payment_method_types=["card"], line_items=[line_item], - success_url=success_url or f"{os.getenv('FRONTEND_URL', 'http://localhost:3000')}/dashboard?session_id={{CHECKOUT_SESSION_ID}}", + success_url=success_url or f"{os.getenv('FRONTEND_URL', 'http://localhost:3000')}/checkout/success?session_id={{CHECKOUT_SESSION_ID}}", cancel_url=cancel_url or f"{os.getenv('FRONTEND_URL', 'http://localhost:3000')}/pricing", metadata={"user_id": user_id, "plan": plan.value}, subscription_data={