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.
452 lines
19 KiB
Markdown
452 lines
19 KiB
Markdown
# 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).
|