- 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>
60 lines
2.2 KiB
TypeScript
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 }
|
|
}
|