feat(billing): Customer Portal + page Paramètres + Standard→Premium via portal
- 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>
This commit is contained in:
parent
bda7feb196
commit
de16deede3
8 changed files with 453 additions and 17 deletions
55
src/features/billing/hooks/useCustomerPortal.ts
Normal file
55
src/features/billing/hooks/useCustomerPortal.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* 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,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue