/** * Sprint 5c — Détection retour Stripe Checkout réussi. * * Lit `?upgrade=success` au mount de la page Dashboard, déclenche : * 1. invalidation du cache plan (`PLAN_QUERY_KEY`) → refetch automatique * du plan mis à jour par le webhook backend `checkout.session.completed`, * 2. affichage d'un banner de succès (consommé par DashboardPage), * 3. nettoyage du query param via `history.replaceState` (un refresh ne * doit pas re-déclencher le banner). * * Indépendant de react-router (lit `window.location.search` directement) * pour faciliter les tests sans MemoryRouter. * * Race connue (Sprint 5c) : le webhook Stripe peut arriver après le * redirect frontend (latence ~1-3 s). Si l'invalidation refetch trop tôt, * `usePlan()` retourne encore l'ancien plan. Mitigation MVP : message * neutre + refresh manuel résoud. Polling/retry à tracer en FTD si * problème observé en production. */ import { useEffect, useState } from 'react' import { useQueryClient } from '@tanstack/react-query' import { PLAN_QUERY_KEY } from '@/entities/user/query-keys' export interface UseUpgradeSuccessHandlerResult { showSuccess: boolean dismiss: () => void } const QUERY_PARAM = 'upgrade' const SUCCESS_VALUE = 'success' export function useUpgradeSuccessHandler(): UseUpgradeSuccessHandlerResult { const [showSuccess, setShowSuccess] = useState(false) const queryClient = useQueryClient() useEffect(() => { if (typeof window === 'undefined') return const params = new URLSearchParams(window.location.search) if (params.get(QUERY_PARAM) !== SUCCESS_VALUE) return setShowSuccess(true) void queryClient.invalidateQueries({ queryKey: PLAN_QUERY_KEY }) // Nettoyage URL : retire UNIQUEMENT le param `upgrade`, conserve les autres // (utm_*, etc.). `replaceState` ne déclenche pas de remount React Router. params.delete(QUERY_PARAM) const remaining = params.toString() const newSearch = remaining.length > 0 ? `?${remaining}` : '' const newUrl = window.location.pathname + newSearch + window.location.hash window.history.replaceState(null, '', newUrl) }, [queryClient]) function dismiss(): void { setShowSuccess(false) } return { showSuccess, dismiss } }