- useCustomerPortal hook (mutation + redirect full-page) - AccountBillingSection: badge plan + bouton Gérer mon abonnement (Standard/Premium) - ParametresPage: page conteneur /parametres avec section billing - PricingPage: Standard→Premium redirige vers Customer Portal (prorata natif Stripe) - Tests: 212 → 219 verts (+7) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
/**
|
||
* Sprint 5d — Hook Stripe Customer Portal.
|
||
*
|
||
* Wrap la mutation `createCustomerPortalSession` + redirect full-page vers
|
||
* la session Customer Portal Stripe. Utilisé par :
|
||
* - `AccountBillingSection` (page Paramètres) : bouton « Gérer mon abonnement ».
|
||
* - `PricingPage` (Standard→Premium) : redirige vers le portal qui gère
|
||
* nativement l'upgrade prorata + confirmation du montant.
|
||
*
|
||
* Erreur 400 `NO_ACTIVE_SUBSCRIPTION` propagée telle quelle (le backend
|
||
* fournit déjà un message FR exploitable directement).
|
||
*/
|
||
|
||
import { useState } from 'react'
|
||
import { useMutation } from '@tanstack/react-query'
|
||
import { createCustomerPortalSession } from '../api'
|
||
|
||
export interface UseCustomerPortalResult {
|
||
openPortal: () => void
|
||
isLoading: boolean
|
||
error: string | null
|
||
}
|
||
|
||
const FALLBACK_ERROR_MESSAGE =
|
||
'Impossible d’ouvrir l’espace abonnement. Réessayez dans quelques instants.'
|
||
|
||
export function useCustomerPortal(): UseCustomerPortalResult {
|
||
const [error, setError] = useState<string | null>(null)
|
||
|
||
const mutation = useMutation({
|
||
mutationFn: createCustomerPortalSession,
|
||
onSuccess: (data) => {
|
||
// Redirect full-page : l'utilisateur reviendra sur ${APP_URL}/dashboard
|
||
// (cf. backend `return_url`). Le query param `?upgrade=success` n'est PAS
|
||
// ajouté par le portal — pas de banner de bienvenue dans ce flow,
|
||
// seulement une éventuelle invalidation au refresh manuel.
|
||
window.location.href = data.url
|
||
},
|
||
onError: (err: unknown) => {
|
||
const message = err instanceof Error && err.message ? err.message : FALLBACK_ERROR_MESSAGE
|
||
setError(message)
|
||
},
|
||
})
|
||
|
||
function openPortal(): void {
|
||
setError(null)
|
||
mutation.mutate()
|
||
}
|
||
|
||
return {
|
||
openPortal,
|
||
isLoading: mutation.isPending,
|
||
error,
|
||
}
|
||
}
|