docs: add comprehensive Stripe billing guide
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 4s
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:
@@ -51,7 +51,7 @@ development_status:
|
||||
3-6-stripe-subscription-tiers: review
|
||||
epic-3-retrospective: optional
|
||||
epic-4: in-progress
|
||||
4-1-gdpr-cookie-consent: ready-for-dev
|
||||
4-1-gdpr-cookie-consent: in-progress
|
||||
4-2-gdpr-right-to-be-forgotten: backlog
|
||||
4-3-data-portability: backlog
|
||||
4-4-explicit-ai-consent: backlog
|
||||
|
||||
451
docs/stripe-billing-guide.md
Normal file
451
docs/stripe-billing-guide.md
Normal 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).
|
||||
Reference in New Issue
Block a user