Files
Momento/docs/stripe-billing-guide.md
Antigravity 96e7902f01
Some checks failed
CI / Lint, Unit Tests & Build (push) Failing after 1m22s
CI / Deploy production (on server) (push) Has been skipped
feat: publication IA (magazine/brief/essay) + fixes critique
Publication IA:
- 4 templates (magazine, brief, essay, simple) avec CSS riche
- Rewrite IA (article/exercises/tutorial/reference/mixed)
- Modération avec timeout 12s + fallback safe
- Quotas publish_enhance par tier (basic=2, pro=15, business=100)
- Détection contenu stale (hash)
- Migration DB publishedContent/publishedTemplate/publishedSourceHash

Fixes:
- cheerio v1.2: Element -> AnyNode (domhandler), decodeEntities cast
- _isShared ajouté au type Note (champ virtuel serveur)
- callout colors PDF export: extraction fonction pure testable
- admin/published: guard note.userId null
- Cmd+S fonctionne en mode dialog (pas seulement fullPage)

i18n:
- 23 clés publish* traduites dans les 15 locales
- Extension Web Clipper: 13 locales mise à jour

Tests:
- callout-colors.test.ts (6 tests)
- note-visible-in-view.test.ts (5 tests)
- entitlements.test.ts + byok-entitlements.test.ts: mock usageLog + unstubAllEnvs
- 199/199 tests passent

Tracker: user-stories.md sync avec sprint-status.yaml
2026-06-28 07:32:57 +00:00

493 lines
22 KiB
Markdown

# Memento — Guide Stripe : Configuration, Architecture et Utilisation
## Vue d'ensemble
Memento 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 : Memento Pro
| Champ | Valeur |
|-------|--------|
| Nom | Memento 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 : Memento Business
| Champ | Valeur |
|-------|--------|
| Nom | Memento 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 Memento | 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).
---
## 11. Bons de réduction & Codes de promotion (Coupons & Promo Codes)
Memento intègre le support natif et sécurisé de Stripe pour les codes promotionnels lors du paiement en Embedded Checkout via l'attribut `allow_promotion_codes: true` dans `/api/billing/create-checkout/route.ts`.
### 11.1 Concepts Clés : Bon de réduction (Coupon) vs Code de promotion (Promo Code)
Dans Stripe, la gestion des remises se fait en deux niveaux :
1. **Le Bon de réduction (Coupon)** : C'est la règle financière sous-jacente (ex. `-20 % sur l'abonnement pendant 6 mois` ou `-10 € à vie`). Un coupon n'est pas vu par le client final sous cette forme.
2. **Le Code de promotion (Promo Code)** : C'est la chaîne de caractères réelle saisie par le client (ex. `WELCOME20`, `LAUNCH50`). Un code promo est obligatoirement rattaché à un Coupon. Vous pouvez créer plusieurs codes promos pour un seul et même coupon (ex. `INFLUENCEUR1` et `INFLUENCEUR2` qui appliquent tous les deux la même réduction de -10%).
### 11.2 Comment créer un Code Promo sur Stripe (Dashboard)
1. Connectez-vous à votre **Stripe Dashboard** (en mode test ou live selon votre cible).
2. Rendez-vous dans **Produits** (Products) → **Bons de réduction** (Coupons) → bouton **+ Nouveau**.
3. Remplissez les informations du Coupon :
- **Nom** : Le nom interne pour vous y retrouver (ex: `Lancement 50%`).
- **Type de réduction** : Pourcentage (ex: `50%`) ou Montant fixe (ex: `10 €`).
- **Durée** :
- *Une seule fois* (appliqué uniquement sur la première facture).
- *Plusieurs mois* (Spécifier le nombre de mois, ex: 3 mois).
- *Pour toujours* (appliqué à vie sur toutes les factures de l'abonnement).
4. Sous le bloc **Codes de promotion**, cochez **"Créer un code de promotion orienté client"** :
- **Code** : Saisissez le code en majuscules (ex: `LAUNCH50`).
- **Limites d'utilisation** (optionnel) :
- Nombre d'utilisations max (ex: limité aux 100 premiers utilisateurs).
- Date limite de validité (ex: valable uniquement jusqu'au 31 décembre).
- Limiter aux clients n'ayant jamais payé.
- Restriction à des plans spécifiques (ex: restreindre ce code promo uniquement au produit `Memento Pro`).
5. Cliquez sur **Créer le bon de réduction**.
### 11.3 Comment désactiver ou supprimer un Code Promo
1. Allez dans **Produits****Bons de réduction****Codes de promotion**.
2. Cliquez sur les `...` à côté du code promo concerné.
3. Sélectionnez **Désactiver** (Deactivate) : le code ne sera plus utilisable par aucun client, mais les clients en ayant déjà bénéficié conserveront leur réduction active selon la durée définie (ex. s'ils ont déjà eu les 3 mois, Stripe continue de l'appliquer jusqu'à la fin de la période).
4. Si vous souhaitez supprimer définitivement un coupon global et annuler la réduction pour tout le monde (y compris les abonnés actuels), allez dans **Bons de Réduction**, cliquez sur le coupon puis sur **Supprimer**.
### 11.4 Mode Test vs Production
* **En local / Test** : Créez vos codes de promotion dans Stripe en ayant activé le commutateur **Mode Test** (Test Mode). Les codes créés ici ne fonctionneront qu'avec vos clés d'API de test (`sk_test_...`).
* **En production / Live** : Désactivez le Mode Test sur Stripe pour passer en mode réel, et créez les coupons dans la section Live. Ils ne fonctionneront qu'avec vos clés d'API réelles (`sk_live_...`).