diff --git a/docs/sprint-status.yaml b/docs/sprint-status.yaml index 360e139..a9cd0c2 100644 --- a/docs/sprint-status.yaml +++ b/docs/sprint-status.yaml @@ -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 diff --git a/docs/stripe-billing-guide.md b/docs/stripe-billing-guide.md new file mode 100644 index 0000000..e6622ed --- /dev/null +++ b/docs/stripe-billing-guide.md @@ -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). diff --git a/memento-note/app/(admin)/admin/user-list.tsx b/memento-note/app/(admin)/admin/user-list.tsx index 4d31130..1dd2702 100644 --- a/memento-note/app/(admin)/admin/user-list.tsx +++ b/memento-note/app/(admin)/admin/user-list.tsx @@ -135,7 +135,7 @@ export function UserList({ initialUsers }: { initialUsers: any[] }) {
- {t('appearance.description') || "Personnalisez l'interface"} + {t('appearance.description')}
{t('appearance.accentColorDescription') || 'Définissez la couleur principale de votre espace de travail'}
+{t('appearance.accentColorDescription')}
- {t('trash.emptyDescription') || 'Deleted items will appear here. They are kept for 30 days before permanent deletion.'} + {t('trash.emptyDescription')}