rubis/bruno/09-Billing/folder.bru
ordinarthur 3bad1451a9
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 19s
docs(bruno): collection Billing + endpoints check-in in-app
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>
2026-05-07 17:25:55 +02:00

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"`
}