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

22 KiB

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 DashboardProduits :

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 DashboardDéveloppeursClé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 DashboardProduits → 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 DashboardDéveloppeursWebhooks :

  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èteSTRIPE_WEBHOOK_SECRET (commence par whsec_...)

2.5 Configurer le Customer Portal

Dans Stripe DashboardParamètresCustomer 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 :

# 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 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

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

# 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

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

SELECT tier, status, "currentPeriodStart", "currentPeriodEnd"
FROM "Subscription"
WHERE "userId" = 'VOTRE_USER_ID';

7.5 Vérifier via l'API

curl http://localhost:3000/api/billing/status -H "Cookie: next-auth.session-token=VOTRE_TOKEN"

Devrait retourner :

{
  "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 ProduitsBons de réductionCodes 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_...).