docs: add comprehensive Stripe billing guide
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 4s

Covers architecture, configuration steps, user flows, API routes,
webhooks, pricing, testing with Stripe CLI, production checklist,
and troubleshooting.
This commit is contained in:
Antigravity
2026-05-16 21:10:26 +00:00
parent aa12d2226f
commit bb75b2e763
36 changed files with 2099 additions and 735 deletions

View File

@@ -0,0 +1,451 @@
# Momento — Guide Stripe : Configuration, Architecture et Utilisation
## Vue d'ensemble
Momento utilise **Stripe** pour la gestion des abonnements payants (Pro, Business, Enterprise). Le système repose sur :
- **Stripe Embedded Checkout** (modal dans l'app, sans redirection)
- **Webhooks** pour synchroniser l'état des abonnements en temps réel
- **Customer Portal** pour que les utilisateurs gèrent leur carte/facturation
- Un **feature flag** pour activer/désactiver le billing sans déploiement
---
## 1. Architecture
```
┌─────────────────────────────────────────────────────────┐
│ Frontend (React) │
│ │
│ /settings/billing │
│ └─ billing-plans.tsx │
│ ├─ Cartes Free / Pro / Business / Enterprise │
│ ├─ Toggle mensuel/annuel │
│ ├─ Embedded Checkout (modal Stripe) │
│ └─ Lien vers Customer Portal │
│ │
│ /settings → SettingsNav → onglet "Facturation" │
│ Sidebar → UsageMeter → lien vers /settings/billing │
└───────────────────────┬─────────────────────────────────┘
POST /api/billing/create-checkout
POST /api/billing/portal
GET /api/billing/status
┌───────────────────────▼─────────────────────────────────┐
│ Backend (Next.js API) │
│ │
│ lib/stripe.ts → Client Stripe singleton │
│ lib/billing/ → Logique métier billing │
│ ├─ stripe-prices.ts → Mapping priceId ↔ tier │
│ └─ sync-subscription-from-stripe.ts │
│ → Upsert Prisma Subscription depuis webhook │
│ │
│ POST /api/billing/webhook → Reception événements │
│ Stripe (raw body + sig) │
└───────────────────────┬─────────────────────────────────┘
┌────────────▼────────────┐
│ PostgreSQL (Prisma) │
│ │
│ Subscription { │
│ userId │
│ tier (BASIC/PRO/ │
│ BUSINESS/ENTERP.) │
│ status (ACTIVE/ │
│ CANCELED/...) │
│ stripeCustomerId │
│ stripeSubscriptionId │
│ stripePriceId │
│ currentPeriodStart │
│ currentPeriodEnd │
│ cancelAtPeriodEnd │
│ } │
└────────────┬────────────┘
┌────────────▼────────────┐
│ Redis │
│ │
│ usage:{userId}: │
│ {feature}:{period} │
│ → Compteurs mensuels │
└─────────────────────────┘
```
---
## 2. Configuration pas à pas
### 2.1 Créer un compte Stripe
1. Aller sur https://dashboard.stripe.com/register
2. Créer un compte (utiliser l'email `sales@memento-note.com`)
3. Activer le **mode test** (toggle en haut à droite)
### 2.2 Créer les produits et prix
Dans le **Stripe Dashboard****Produits** :
#### Produit 1 : Momento Pro
| Champ | Valeur |
|-------|--------|
| Nom | Momento Pro |
| Description | Pour les consultants et créateurs exigeants |
Créer **2 prix** :
| Prix | Montant | Récurrent |
|------|---------|-----------|
| Pro Mensuel | 9,90 EUR | Tous les mois |
| Pro Annuel | 99 EUR | Tous les ans |
#### Produit 2 : Momento Business
| Champ | Valeur |
|-------|--------|
| Nom | Momento Business |
| Description | Pour les équipes et chefs de produit |
Créer **2 prix** :
| Prix | Montant | Récurrent |
|------|---------|-----------|
| Business Mensuel | 29,90 EUR | Tous les mois |
| Business Annuel | 299 EUR | Tous les ans |
> **Enterprise** n'a pas de prix Stripe — c'est un contact manuel (`sales@memento-note.com`).
### 2.3 Récupérer les clés et price IDs
Dans **Stripe Dashboard****Développeurs****Clés API** :
| Variable | Où la trouver |
|----------|---------------|
| `STRIPE_SECRET_KEY` | Clés API → Clé secrète (sk_test_...) |
| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | Clés API → Clé publiable (pk_test_...) |
Dans **Stripe Dashboard****Produits** → cliquer sur chaque prix :
| Variable | Source |
|----------|--------|
| `STRIPE_PRICE_PRO_MONTHLY` | Produit Pro → Prix mensuel → `price_1xxx...` |
| `STRIPE_PRICE_PRO_ANNUAL` | Produit Pro → Prix annuel → `price_1yyy...` |
| `STRIPE_PRICE_BUSINESS_MONTHLY` | Produit Business → Prix mensuel → `price_1zzz...` |
| `STRIPE_PRICE_BUSINESS_ANNUAL` | Produit Business → Prix annuel → `price_1www...` |
### 2.4 Configurer le webhook
Dans **Stripe Dashboard****Développeurs****Webhooks** :
1. Cliquer **Ajouter un endpoint**
2. URL : `https://memento-note.com/api/billing/webhook`
3. Événements à écouter :
- `checkout.session.completed`
- `customer.subscription.created`
- `customer.subscription.updated`
- `customer.subscription.deleted`
- `invoice.payment_failed`
4. Copier la **signature secrète**`STRIPE_WEBHOOK_SECRET` (commence par `whsec_...`)
### 2.5 Configurer le Customer Portal
Dans **Stripe Dashboard****Paramètres****Customer Portal** :
1. Activer le portal
2. Configurer les fonctions autorisées :
- Mettre à jour le moyen de paiement
- Voir les factures
- Annuler l'abonnement
- Changer de plan (optionnel)
3. URL de retour : `https://memento-note.com/settings/billing`
### 2.6 Remplir le fichier `.env`
Ajouter dans `memento-note/.env` :
```env
# Stripe — Server-side (NE JAMAIS exposer côté client)
STRIPE_SECRET_KEY="sk_test_VOTRE_CLE_SECRETE"
STRIPE_WEBHOOK_SECRET="whsec_VOTRE_SIGNATURE_WEBHOOK"
# Stripe — Price IDs
STRIPE_PRICE_PRO_MONTHLY="price_ID_PRO_MENSUEL"
STRIPE_PRICE_PRO_ANNUAL="price_ID_PRO_ANNUEL"
STRIPE_PRICE_BUSINESS_MONTHLY="price_ID_BUSINESS_MENSUEL"
STRIPE_PRICE_BUSINESS_ANNUAL="price_ID_BUSINESS_ANNUEL"
# Stripe — Client-side (sûr à exposer)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_VOTRE_CLE_PUBLIABLE"
# Activer l'interface de facturation
NEXT_PUBLIC_FEATURE_BILLING_ENABLED="true"
```
> **Important** : Relancer le serveur après modification du `.env`.
### 2.7 Résumé des variables
| Variable | Où | Obligatoire |
|----------|----|----|
| `STRIPE_SECRET_KEY` | `.env` (server) | Oui |
| `STRIPE_WEBHOOK_SECRET` | `.env` (server) | Oui |
| `STRIPE_PRICE_PRO_MONTHLY` | `.env` (server) | Oui |
| `STRIPE_PRICE_PRO_ANNUAL` | `.env` (server) | Oui |
| `STRIPE_PRICE_BUSINESS_MONTHLY` | `.env` (server) | Oui |
| `STRIPE_PRICE_BUSINESS_ANNUAL` | `.env` (server) | Oui |
| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | `.env` (client+server) | Oui |
| `NEXT_PUBLIC_FEATURE_BILLING_ENABLED` | `.env` (client+server) | Oui |
---
## 3. Flux utilisateur
### 3.1 Checkout (achat d'un plan)
```
Utilisateur Frontend Backend Stripe
│ │ │ │
│ Clique "Passer Pro" │ │ │
│───────────────────────────>│ │ │
│ │ POST /api/billing/ │ │
│ │ create-checkout │ │
│ │ {tier: "PRO", │ │
│ │ interval: "month"} │ │
│ │─────────────────────────>│ │
│ │ │ Crée/respire │
│ │ │ Customer + Session │
│ │ │───────────────────────>│
│ │ │ │
│ │ │ {clientSecret} │
│ │ │<───────────────────────│
│ │ │ │
│ │ Modal Embedded │ │
│ │ Checkout Stripe │ │
│ │<─────────────────────────│ │
│ │ │ │
│ Remplit carte 4242... │ │ │
│───────────────────────────>│ │ │
│ │ Paiement Stripe │ │
│ │─────────────────────────────────────────────────>│
│ │ │ │
│ │ │ Webhook: │
│ │ │ checkout.session. │
│ │ │ completed │
│ │ │<───────────────────────│
│ │ │ │
│ │ │ Upsert Subscription │
│ │ │ tier=PRO status=ACTIVE│
│ │ │ │
│ Toast "Bienvenue Pro !" │ │ │
│ UsageMeter rafraîchi │ │ │
│<───────────────────────────│ │ │
```
### 3.2 Webhook — Cycle de vie des abonnements
| Événement Stripe | Action Momento | Statut Prisma |
|------------------|---------------|---------------|
| `checkout.session.completed` | Upsert subscription avec tier/periode | `ACTIVE` |
| `customer.subscription.created` | Upsert (nouvelle souscription) | Selon Stripe |
| `customer.subscription.updated` | Upsert (changement de plan, etc.) | Selon Stripe |
| `customer.subscription.deleted` | Marquer annulé | `CANCELED` |
| `invoice.payment_failed` | Sync subscription (passage en `PAST_DUE`) | `PAST_DUE` |
### 3.3 Mapping statuts Stripe → Prisma
| Stripe | Prisma | Comportement `getEffectiveTier()` |
|--------|--------|----------------------------------|
| `active` | `ACTIVE` | Retourne le tier payé (PRO/BUSINESS) |
| `trialing` | `TRIALING` | Retourne le tier (accès complet pendant l'essai) |
| `past_due` | `PAST_DUE` | Garde le tier jusqu'à `currentPeriodEnd` |
| `canceled` / `unpaid` | `CANCELED` | Garde le tier jusqu'à `currentPeriodEnd`, puis BASIC |
| `incomplete_expired` | `INACTIVE` | Retourne BASIC |
---
## 4. Pages et composants
### 4.1 Accès utilisateur
| URL | Description |
|-----|-------------|
| `/settings/billing` | Page principale : plans, usage, facturation |
| `/settings` → onglet "Facturation" | Navigation via SettingsNav |
| UsageMeter (sidebar) → "Upgrade" | Lien direct vers `/settings/billing` |
### 4.2 Composants clés
| Fichier | Rôle |
|---------|------|
| `components/settings/billing-plans.tsx` | Cartes de plans, checkout modal, usage grid, portal |
| `components/settings/SettingsNav.tsx` | Onglet "Facturation" avec icône CreditCard |
| `components/settings/billing-history.tsx` | Lien vers le portal Stripe pour factures |
| `components/usage-meter.tsx` | Meter sidebar → lien vers billing quand quota dépassé |
### 4.3 API Routes
| Route | Méthode | Auth | Description |
|-------|---------|------|-------------|
| `/api/billing/create-checkout` | POST | Session | Crée une session checkout embedded |
| `/api/billing/portal` | POST | Session | Ouvre le portal client Stripe |
| `/api/billing/status` | GET | Session | Retourne tier, status, periodEnd |
| `/api/billing/webhook` | POST | Signature Stripe | Reçoit les événements lifecycle |
### 4.4 Librairies backend
| Fichier | Rôle |
|---------|------|
| `lib/stripe.ts` | Client Stripe singleton (`getStripe()`) |
| `lib/billing/stripe-prices.ts` | `resolvePriceId()` et `priceIdToTier()` |
| `lib/billing/sync-subscription-from-stripe.ts` | Upsert Prisma depuis webhook Stripe |
| `lib/entitlements.ts` | `getEffectiveTier()`, `TIER_LIMITS`, `getUserQuotas()` |
---
## 5. Quotas par tier
| Feature | BASIC | PRO | BUSINESS | ENTERPRISE |
|---------|-------|-----|----------|------------|
| Recherche sémantique | 30 | 100 | 1000 | ∞ |
| Tags automatiques | 20 | 200 | 1000 | ∞ |
| Titres automatiques | 10 | 200 | 1000 | ∞ |
| Reformulation | — | 50 | 500 | ∞ |
| Chat IA | — | 100 | 1000 | ∞ |
| Brainstorm (création) | 1 | 30 | 200 | ∞ |
| Brainstorm (expansion) | 10 | 100 | 500 | ∞ |
| Brainstorm (enrichissement) | 20 | 200 | 1000 | ∞ |
> Les compteurs sont stockés dans Redis avec un TTL de 90 jours. Le tier détermine les limites, pas les compteurs.
---
## 6. Tests
### Tests unitaires (22 tests, 100% passent)
| Fichier | Tests | Description |
|---------|-------|-------------|
| `tests/unit/billing-price-map.test.ts` | 10 | Mapping priceId → tier, erreurs si env manquant |
| `tests/unit/billing-sync.test.ts` | 12 | Upsert Prisma, mapping statuts Stripe→Prisma, métadonnées |
### Lancer les tests
```bash
cd memento-note
npx vitest run tests/unit/billing-price-map.test.ts tests/unit/billing-sync.test.ts
```
---
## 7. Test local avec Stripe CLI
### 7.1 Installer Stripe CLI
```bash
# macOS
brew install stripe/stripe-cli/stripe
# Linux
curl -s https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public/download.gpg | sudo tee /usr/share/keyrings/stripe.gpg
echo "deb [signed-by=/usr/share/keyrings/stripe.gpg] https://packages.stripe.dev/stripe-cli-debian-local stable main" | sudo tee -a /etc/apt/sources.list.d/stripe.list
sudo apt update && sudo apt install stripe
```
### 7.2 Forward des webhooks en local
```bash
stripe listen --forward-to localhost:3000/api/billing/webhook
```
Cette commande affiche la signature webhook (`whsec_...`) à mettre dans `STRIPE_WEBHOOK_SECRET`.
### 7.3 Tester un paiement
1. Lancer le serveur : `npm run dev`
2. Aller sur `/settings/billing`
3. Cliquer "Passer au Plan Pro"
4. Utiliser la carte test : **4242 4242 4242 4242**
5. Date d'expiration : n'importe quelle date future
6. CVC : n'importe quels 3 chiffres
7. Le webhook se déclenche → `Subscription` upserté dans la DB
### 7.4 Vérifier en base
```sql
SELECT tier, status, "currentPeriodStart", "currentPeriodEnd"
FROM "Subscription"
WHERE "userId" = 'VOTRE_USER_ID';
```
### 7.5 Vérifier via l'API
```bash
curl http://localhost:3000/api/billing/status -H "Cookie: next-auth.session-token=VOTRE_TOKEN"
```
Devrait retourner :
```json
{
"tier": "PRO",
"effectiveTier": "PRO",
"status": "ACTIVE",
"currentPeriodEnd": "2026-06-16T...",
"cancelAtPeriodEnd": false,
"hasStripeSubscription": true
}
```
---
## 8. Passage en production
### 8.1 Checklist
- [ ] Compte Stripe vérifié (KYC complété)
- [ ] Produits et prix créés en **mode live**
- [ ] Webhook configuré en mode live avec l'URL de production
- [ ] Variables `.env` mises à jour avec les clés live (`sk_live_...`, `pk_live_...`)
- [ ] `NEXT_PUBLIC_FEATURE_BILLING_ENABLED="true"`
- [ ] Customer Portal configuré pour le mode live
- [ ] Tester un paiement réel avec une vraie carte
### 8.2 Sécurité
- **Jamais** de `STRIPE_SECRET_KEY` ou `STRIPE_WEBHOOK_SECRET` côté client
- Les price IDs sont côté serveur uniquement
- Le webhook vérifie la signature Stripe-Signature sur chaque requête
- L'upsert par `stripeSubscriptionId` garantit l'idempotence
---
## 9. Dépannage
### Les cartes de plans ne s'affichent pas
→ Vérifier `NEXT_PUBLIC_FEATURE_BILLING_ENABLED="true"` dans `.env` et relancer le serveur.
### Le checkout ne s'ouvre pas
→ Vérifier `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` est défini. Regarder la console navigateur.
### Le webhook ne se déclenche pas
→ En local : utiliser `stripe listen --forward-to localhost:3000/api/billing/webhook`
→ En production : vérifier l'URL du webhook dans le Stripe Dashboard.
### L'abonnement reste BASIC après paiement
→ Vérifier les logs du webhook (`[billing/webhook]`). Le `userId` doit être dans les metadata de la session.
### `getStripe()` throw "STRIPE_SECRET_KEY is required"
→ Ajouter `STRIPE_SECRET_KEY` dans `.env`.
---
## 10. Tarification
| Tier | Mensuel | Annuel | Économie annuelle |
|------|---------|--------|-------------------|
| Gratuit | 0 € | — | — |
| Pro | 9,90 € | 99 € | ~17% |
| Business | 29,90 € | 299 € | ~17% |
| Enterprise | Sur devis | Sur devis | — |
Devise : EUR (configurable dans Stripe Dashboard pour multi-devises).