expria-frontend/src/features/dashboard/hooks/useUpgradeSuccessHandler.ts
Hermann_Kitio bda7feb196 feat(billing): useStripeCheckout hook + post-redirect upgrade success
- useStripeCheckout: mutation + redirect full-page, pendingPriceType exposed
- PricingPage migré vers useStripeCheckout (suppression useMutation inline)
- useUpgradeSuccessHandler: détecte ?upgrade=success, invalide plan cache, clean URL
- UpgradeSuccessBanner: callout success dans DashboardPage
- Tests: 203 → 212 verts (+9)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 05:19:18 +03:00

60 lines
2.2 KiB
TypeScript

/**
* 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 }
}