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