meta { name: Billing seq: 10 } docs { ## Billing — Stripe Checkout, Customer Portal, Subscriptions Endpoints qui pilotent la souscription d'une org : passage Free → Pro/Business via Stripe Checkout, gestion CB & annulation via Customer Portal, et webhook Stripe pour tenir l'org synchro avec l'état Stripe. ## Plans | Plan | Mensuel | Annuel | Limite factures actives | V2 | |---|---|---|---|---| | Free | 0 € | — | 5 (post-grace 3 mois) | — | | Pro | 19 € | 190 € | illimité | — | | Business | 49 € | 490 € | illimité | 5 sièges, reply-from-user | ## Flow d'upgrade 1. SPA → **02 Start checkout** (auth) avec `{plan, cycle}` → reçoit l'URL Stripe 2. SPA redirect vers l'URL → user paye sur Stripe Checkout (UI hostée) 3. Stripe redirect vers `?checkout=success` côté SPA 4. **En parallèle** : Stripe envoie `checkout.session.completed` au webhook → `applySubscriptionToOrg` set `org.plan='pro'`, `subscription_status='active'` 5. SPA repoll **01 Get subscription** → affiche le nouveau plan ## Flow d'annulation 1. SPA → **03 Open portal** (auth) → reçoit l'URL portal 2. SPA redirect → user click "Cancel plan" + confirme 3. Stripe pose `cancel_at` (timestamp = period_end) ou `cancel_at_period_end=true` 4. Stripe envoie `customer.subscription.updated` au webhook → `org.cancel_at_period_end = true` 5. UI affiche bandeau "ANNULÉ — accès jusqu'au DD/MM" + bouton "Réactiver" 6. À `current_period_end`, Stripe envoie `customer.subscription.deleted` → `org.plan = 'free'` ## Flow de réactivation Si l'user clique "Réactiver" avant la fin de période : 1. SPA → **04 Reactivate** (auth) 2. Backend retrieve la sub Stripe pour savoir si `cancel_at` ou `cancel_at_period_end` est posé (ils sont mutuellement exclusifs côté Stripe API), puis clear le bon 3. Pas de paiement immédiat — la sub continue son cycle normal ## Webhook idempotence Stripe peut re-livrer un event plusieurs fois. Notre handler est read-then-write (pas d'assumption "1-shot"). Les 4 events pris en charge : - checkout.session.completed - customer.subscription.created/updated → applySubscriptionToOrg - customer.subscription.deleted → bascule en Free - invoice.payment_failed → status past_due ## Pour tester en local 1. `node ace stripe:setup` une fois pour créer Products + Prices test mode 2. `stripe listen --forward-to localhost:3333/api/v1/billing/webhook` → copie le `whsec_...` dans `.env` (STRIPE_WEBHOOK_SECRET) 3. Lance Bruno **02 Start checkout** → ouvre l'URL → CB test 4242 4242 4242 4242 4. Reviens sur **01 Get subscription** → tu devrais voir `plan: "pro"` }