All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 19s
Nouveau dossier `09-Billing/` avec :
- folder.bru (overview : plans + flows upgrade/cancel/reactivate)
- 01 Get subscription : state du plan, caps, grace period, cancel flag
- 02 Start checkout : crée une Checkout Session Stripe (Pro/Business
× monthly/yearly)
- 03 Open portal : Customer Portal pour gérer CB/annulation
- 04 Reactivate : annule l'annulation programmée (sans paiement
immédiat) — gère le conflit Stripe
cancel_at vs cancel_at_period_end
Aussi documenté les endpoints in-app check-in qui manquaient dans Bruno :
- 03 In-app pending : liste des factures awaiting_user_confirmation
- 04 In-app respond paid : équivalent du lien email "C'est payé"
- 05 In-app respond pending : équivalent "Toujours en attente"
README mis à jour avec le parcours étendu (signup → … → billing).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
67 lines
2.7 KiB
Plaintext
67 lines
2.7 KiB
Plaintext
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"`
|
|
}
|