Aller au contenu principal

Blueprint — Initialiser la base Stripe

Lecture : 15min

Étape 1 — Initialiser les bases avec l'IA

Crée un fichier d'implémentation technique :

blueprints/stripe/01-initialiser-base-stripe.md

Voici son contenu à copier-coller intégralement :

Voir le contenu complet du blueprint
# Blueprint — Initialiser la base Stripe

## Mission

Analyse l'architecture et les conventions du projet, puis initialise le socle technique nécessaire à l'intégration de Stripe.

L'implémentation doit respecter les technologies, les conventions de nommage, l'organisation des dossiers, le système de migrations, le mécanisme de configuration et les outils de test déjà présents dans le projet.

Ne remplace pas l'architecture existante par une architecture spécifique à un autre framework.

## Principes d'architecture

Respecte impérativement les principes suivants :

- l'application est la source de vérité pour les utilisateurs, les plans, les droits fonctionnels, les périodes d'essai internes et les règles métier ;
- Stripe est la source de vérité pour les paiements, les abonnements externes, les factures et les remboursements ;
- les droits d'accès ne doivent jamais être déterminés directement par un appel en temps réel à Stripe ;
- les droits effectifs doivent être conservés dans une entité locale d'entitlement ;
- les événements Stripe doivent être traités de manière idempotente ;
- les environnements Stripe de test et de production doivent rester strictement séparés ;
- aucune clé secrète Stripe ne doit être exposée au frontend, à une application mobile ou dans le dépôt Git.

## Étape 1 — Analyser le projet

Avant toute modification :

1. identifie le langage, le framework et l'ORM utilisés ;
2. localise les entités ou modèles représentant les utilisateurs et les organisations ;
3. détermine quelle entité doit porter la facturation ;
4. identifie le système de migrations ;
5. identifie la gestion actuelle des variables d'environnement ;
6. identifie le système de tests ;
7. vérifie si une file de messages ou un système de tâches asynchrones existe déjà ;
8. vérifie si une intégration Stripe est déjà partiellement présente.

Réutilise les conventions existantes.

Ne crée pas une seconde implémentation lorsqu'une brique équivalente existe déjà.

## Étape 2 — Installer le SDK Stripe

Ajoute le SDK Stripe officiel correspondant au langage du projet.

Utilise une version stable compatible avec les dépendances existantes.

Ne charge jamais le SDK directement depuis le frontend pour effectuer des opérations nécessitant la clé secrète.

Centralise l'accès au SDK dans un composant dédié, par exemple :

StripeClient
StripeGateway
StripeService
BillingProvider

Le nom exact doit respecter les conventions du projet.

## Étape 3 — Configurer les variables d'environnement

Ajoute les variables suivantes aux fichiers d'exemple de configuration :

STRIPE_SECRET_KEY=
STRIPE_PUBLISHABLE_KEY=
STRIPE_WEBHOOK_SECRET=

Contraintes :

- ne renseigne aucune véritable clé dans le dépôt ;
- ajoute uniquement des valeurs vides ou factices dans les fichiers d'exemple ;
- documente la différence entre les clés de test et de production ;
- vérifie que les fichiers contenant de vraies clés sont ignorés par Git ;
- expose uniquement `STRIPE_PUBLISHABLE_KEY` au frontend lorsque cela est nécessaire ;
- conserve `STRIPE_SECRET_KEY` et `STRIPE_WEBHOOK_SECRET` exclusivement côté serveur.

Ajoute une validation au démarrage de l'application afin de détecter une configuration Stripe absente ou incohérente.

## Étape 4 — Créer le modèle de données

Adapte les noms des tables et des colonnes aux conventions du projet.

Utilise des identifiants locaux pour les relations internes. Les identifiants Stripe doivent être stockés comme références externes.

### Table ou entité `stripe_customers`

Cette entité associe l'entité facturée dans l'application à un client Stripe.

Prévoir au minimum :

| Champ | Rôle |
|---|---|
| `id` | Identifiant local |
| `owner_type` | Type d'entité facturée si le projet utilise une relation polymorphe |
| `owner_id` | Identifiant local de l'utilisateur, de l'organisation ou du compte facturé |
| `stripe_customer_id` | Identifiant Stripe commençant par `cus_` |
| `livemode` | Indique si la ressource appartient à Stripe en production |
| `created_at` | Date de création |
| `updated_at` | Date de mise à jour |

Si le projet facture toujours le même type d'entité, privilégie une relation explicite plutôt qu'une relation polymorphe.

Ajoute les contraintes suivantes :

- unicité de `stripe_customer_id` ;
- unicité de l'association entre le propriétaire local et `livemode` ;
- index sur le propriétaire local ;
- index sur `livemode`.

### Table ou entité `billing_plans`

Cette entité représente les plans métier gérés par l'application.

Prévoir au minimum :

| Champ | Rôle |
|---|---|
| `id` | Identifiant local |
| `code` | Code métier stable, par exemple `premium` |
| `name` | Nom interne ou commercial |
| `is_active` | Indique si le plan est utilisable |
| `created_at` | Date de création |
| `updated_at` | Date de mise à jour |

Le champ `code` doit être unique et stable.

Un plan métier ne doit pas être confondu avec un produit Stripe.

### Table ou entité `billing_prices`

Cette entité associe les variantes tarifaires internes aux tarifs Stripe.

Prévoir au minimum :

| Champ | Rôle |
|---|---|
| `id` | Identifiant local |
| `billing_plan_id` | Plan métier associé |
| `code` | Code métier stable, par exemple `premium_monthly` |
| `stripe_product_id` | Identifiant Stripe commençant par `prod_` |
| `stripe_price_id` | Identifiant Stripe commençant par `price_` |
| `currency` | Devise du tarif |
| `amount` | Montant local de référence dans la plus petite unité monétaire |
| `billing_type` | Paiement ponctuel ou récurrent |
| `billing_interval` | `month`, `year` ou valeur nulle pour un paiement ponctuel |
| `interval_count` | Nombre d'intervalles entre deux facturations |
| `tax_behavior` | Comportement fiscal du tarif si nécessaire |
| `is_active` | Indique si le tarif peut être proposé |
| `livemode` | Environnement Stripe |
| `created_at` | Date de création |
| `updated_at` | Date de mise à jour |

Contraintes attendues :

- unicité de `stripe_price_id` ;
- unicité de `code` et `livemode` ;
- index sur `billing_plan_id` ;
- index sur `is_active` ;
- index sur `livemode`.

Le montant local sert à l'affichage et au contrôle de cohérence.

Lors d'un paiement, le tarif Stripe `price_...` reste la référence financière transmise à Stripe.

### Table ou entité `billing_subscriptions`

Cette entité représente localement les abonnements financiers gérés par Stripe.

Prévoir au minimum :

| Champ | Rôle |
|---|---|
| `id` | Identifiant local |
| `owner_type` | Type d'entité abonnée si nécessaire |
| `owner_id` | Identifiant du propriétaire local |
| `billing_plan_id` | Plan métier associé |
| `billing_price_id` | Tarif local associé |
| `stripe_customer_id` | Référence vers le client Stripe |
| `stripe_subscription_id` | Identifiant Stripe commençant par `sub_` |
| `status` | Statut synchronisé depuis Stripe |
| `current_period_start` | Début de la période de facturation courante |
| `current_period_end` | Fin de la période de facturation courante |
| `cancel_at_period_end` | Indique si la résiliation est programmée |
| `cancel_at` | Date de résiliation programmée éventuelle |
| `canceled_at` | Date de résiliation |
| `ended_at` | Date de fin définitive |
| `livemode` | Environnement Stripe |
| `created_at` | Date de création |
| `updated_at` | Date de mise à jour |

Ajoute une contrainte d'unicité sur `stripe_subscription_id`.

Cette table représente la relation financière externe.

Elle ne doit pas être utilisée seule pour déterminer les droits fonctionnels.

### Table ou entité `user_entitlements`

Cette entité constitue la source de vérité locale pour les droits fonctionnels.

Adapte son nom si les droits sont attribués à une organisation plutôt qu'à un utilisateur.

Prévoir au minimum :

| Champ | Rôle |
|---|---|
| `id` | Identifiant local |
| `owner_type` | Type de bénéficiaire si nécessaire |
| `owner_id` | Identifiant local du bénéficiaire |
| `plan` | Code du plan accordé |
| `source` | Origine du droit |
| `status` | État du droit |
| `starts_at` | Début de validité |
| `ends_at` | Fin de validité éventuelle |
| `external_subscription_id` | Identifiant de l'abonnement externe éventuel |
| `auto_renew` | Indique si la source est censée renouveler automatiquement le droit |
| `created_at` | Date de création |
| `updated_at` | Date de mise à jour |

Prévoir au minimum les sources suivantes :

internal_trial
stripe
apple
google
gift
admin
promotion

Prévoir au minimum les statuts suivants :

pending
active
grace_period
expired
revoked

Les entitlements provenant d'une source externe comme Stripe, Apple ou Google doivent être modifiés uniquement par les mécanismes de synchronisation correspondants.

Une action d'administration ne doit pas modifier directement un entitlement externe verrouillé.

Pour accorder manuellement un accès, créer un nouvel entitlement avec une source telle que `admin`, `gift` ou `promotion`.

### Table ou entité `stripe_webhook_events`

Cette entité conserve les événements Stripe reçus et leur état de traitement.

Prévoir au minimum :

| Champ | Rôle |
|---|---|
| `id` | Identifiant local |
| `stripe_event_id` | Identifiant Stripe commençant par `evt_` |
| `event_type` | Type de l'événement |
| `stripe_created_at` | Date de création de l'événement chez Stripe |
| `livemode` | Environnement Stripe |
| `payload` | Charge utile originale |
| `status` | État du traitement |
| `attempts` | Nombre de tentatives |
| `processed_at` | Date du traitement réussi |
| `last_error` | Dernière erreur rencontrée |
| `created_at` | Date de réception |
| `updated_at` | Date de mise à jour |

Prévoir les statuts suivants ou leurs équivalents :

received
processing
processed
failed
ignored

Ajoute une contrainte d'unicité sur `stripe_event_id`.

Le payload doit être stocké dans un type adapté aux données JSON lorsque la base de données le permet.

## Étape 5 — Créer les migrations

Crée les migrations correspondant au modèle de données.

Les migrations doivent :

- respecter les conventions du projet ;
- être réversibles lorsque le système de migrations le permet ;
- créer les clés étrangères utiles ;
- créer les index et contraintes d'unicité ;
- ne supprimer aucune donnée métier existante ;
- ne modifier aucune table existante sans nécessité démontrée.

N'exécute pas de suppression destructive automatique.

## Étape 6 — Créer l'endpoint webhook

Ajoute un endpoint serveur dédié :

POST /webhooks/stripe

Adapte l'URL aux conventions de routage du projet.

Cet endpoint doit être accessible par Stripe sans authentification utilisateur classique.

Il doit toutefois vérifier obligatoirement la signature Stripe.

Le traitement doit suivre cet ordre :

1. lire le corps HTTP brut ;
2. lire l'en-tête de signature Stripe ;
3. vérifier la signature avec `STRIPE_WEBHOOK_SECRET` ;
4. refuser la requête lorsque la signature est absente ou invalide ;
5. extraire l'identifiant et le type de l'événement ;
6. enregistrer l'événement dans `stripe_webhook_events` ;
7. ignorer proprement un événement déjà enregistré ;
8. déléguer son traitement à un service dédié ou à une tâche asynchrone ;
9. retourner rapidement une réponse HTTP de succès lorsque l'événement a été accepté.

La vérification de signature doit utiliser le corps brut exact de la requête.

Ne vérifie jamais la signature à partir d'un objet JSON reconstitué.

## Étape 7 — Créer l'architecture de traitement des événements

Crée une architecture extensible permettant d'associer un type d'événement Stripe à un handler dédié.

Exemple conceptuel :

StripeWebhookController


StripeWebhookReceiver


StripeEventDispatcher

├── CheckoutSessionCompletedHandler
├── SubscriptionUpdatedHandler
├── SubscriptionDeletedHandler
├── InvoicePaidHandler
└── InvoicePaymentFailedHandler

Ne place pas toute la logique métier dans le contrôleur HTTP.

Le contrôleur doit uniquement :

- vérifier la requête ;
- enregistrer l'événement ;
- déclencher son traitement ;
- retourner la réponse HTTP.

Prépare les handlers sans nécessairement implémenter immédiatement toute la logique métier des prochains blueprints.

Les événements inconnus doivent être enregistrés puis marqués comme ignorés, sans provoquer une erreur serveur.

## Étape 8 — Garantir l'idempotence

Stripe peut envoyer plusieurs fois le même événement.

Le système doit donc garantir que le traitement répété d'un événement ne crée pas :

- plusieurs clients Stripe locaux ;
- plusieurs abonnements locaux ;
- plusieurs entitlements identiques ;
- plusieurs paiements métier ;
- plusieurs remboursements ;
- plusieurs notifications identiques.

Utilise conjointement :

- la contrainte d'unicité sur `stripe_event_id` ;
- des contraintes d'unicité métier ;
- des opérations de type `upsert` lorsque cela est adapté ;
- des transactions de base de données ;
- des clés d'idempotence pour les appels Stripe initiés par l'application.

## Étape 9 — Ne pas dépendre de l'ordre des événements

Ne suppose jamais que les webhooks seront reçus dans leur ordre de création.

Par exemple, l'application peut recevoir :

invoice.paid

avant :

customer.subscription.created

Les handlers doivent pouvoir :

- retrouver les ressources locales existantes ;
- les créer ou les mettre à jour si nécessaire ;
- récupérer l'état actuel d'une ressource depuis Stripe lorsqu'une donnée indispensable manque ;
- ignorer un événement devenu obsolète lorsque la base locale contient déjà un état plus récent.

Utilise `stripe_created_at` ou une information équivalente pour éviter qu'un ancien événement remplace un état plus récent.

## Étape 10 — Prévoir le retraitement des erreurs

Ajoute un mécanisme permettant de retrouver les événements dont le statut est `failed`.

Selon les capacités du projet, prévoir :

- une commande en ligne de commande ;
- une tâche d'administration ;
- une file de messages avec nouvelles tentatives ;
- ou un service de retraitement ciblé.

Le retraitement doit rester idempotent.

Ne supprime pas les événements en erreur.

Conserve leur historique et leur dernier message d'erreur.

## Étape 11 — Ajouter les tests

Ajoute au minimum les tests suivants :

1. chargement correct de la configuration Stripe ;
2. rejet d'un webhook sans signature ;
3. rejet d'un webhook avec une signature invalide ;
4. acceptation d'un webhook signé correctement ;
5. enregistrement du payload original ;
6. impossibilité d'enregistrer deux fois le même `evt_...` ;
7. absence de double traitement d'un événement ;
8. séparation des ressources de test et de production ;
9. création correcte des contraintes et relations du modèle ;
10. marquage en erreur lorsqu'un handler échoue ;
11. possibilité de retraiter un événement en erreur ;
12. traitement propre d'un type d'événement inconnu.

Utilise les outils de test déjà présents dans le projet.

Ne nécessite pas de véritables paiements Stripe pour les tests automatisés unitaires.

## Étape 12 — Ajouter la documentation technique

Documente dans le projet :

- les variables d'environnement nécessaires ;
- la procédure pour récupérer les clés de test ;
- la procédure pour récupérer le secret du webhook ;
- l'URL locale et publique du webhook ;
- la manière de lancer Stripe CLI si elle est utilisée ;
- la commande de migration ;
- la commande de test ;
- la méthode de retraitement des webhooks en erreur.

Ne copie aucune véritable clé dans la documentation.

## Résultat attendu

À la fin de cette mission, le projet doit disposer :

- du SDK Stripe officiel ;
- d'un client Stripe centralisé ;
- des variables d'environnement nécessaires ;
- des migrations du socle de facturation ;
- d'une association locale avec les clients Stripe ;
- d'un catalogue métier local ;
- d'une représentation locale des abonnements Stripe ;
- d'une entité locale d'entitlements ;
- d'un journal des événements Stripe ;
- d'un endpoint webhook sécurisé ;
- d'un mécanisme d'idempotence ;
- d'une architecture extensible de handlers ;
- d'un mécanisme de retraitement ;
- de tests automatisés ;
- d'une documentation d'installation.

## Contraintes finales

- Ne crée pas encore de page de paiement.
- Ne crée pas encore de session Checkout.
- Ne crée pas encore de portail client.
- Ne mets pas en place de période d'essai Stripe.
- Ne détermine jamais les droits uniquement à partir du statut d'un abonnement Stripe.
- Ne stocke aucune clé secrète dans le code.
- Ne crée aucune dépendance directe du domaine métier vers le SDK Stripe.
- Ne modifie pas les ressources de production Stripe.
- Ne crée aucune donnée distante sans demande explicite.
- N'invente pas les identifiants `prod_...`, `price_...`, `cus_...` ou `sub_...`.

## Compte rendu attendu

À la fin de l'implémentation, fournis un compte rendu contenant :

1. les fichiers créés ;
2. les fichiers modifiés ;
3. les migrations ajoutées ;
4. les variables d'environnement ajoutées ;
5. l'URL du webhook ;
6. les décisions d'architecture prises ;
7. les commandes à exécuter ;
8. les tests ajoutés et leur résultat ;
9. les éventuels points restant à configurer manuellement dans Stripe.

Étape 2 — Accéder à l'API Keys

  1. Connectez-vous à votre Dashboard Stripe
  2. Cliquez sur Développeurs (gauche, en bas)
  3. Sélectionnez Clés API

Vous accédez alors à la page montrant vos clés en mode Test ou Live.

attention

Au démarrage du projet, travaillez exclusivement en mode Test.

Les clés de test commencent par pk_test_ (publique) et sk_test_ (secrète).

Les clés de production commencent par pk_live_ et sk_live_.

Ne mélangez jamais les environnements.

Étape 3 — Configurer le webhook

Le webhook permet à Stripe de notifier automatiquement l'application lorsqu'un événement se produit (paiement, abonnement, remboursement, etc.).

Ces notifications permettent de maintenir les données de l'application synchronisées avec Stripe.

Créer une destination d'événements

Depuis le Dashboard Stripe, ouvrez :

Développeurs → Workbench → Webhooks

Image webhook stripe

Cliquez ensuite sur Ajouter une destination.

Image webhook stripe

Sélectionner les événements

Conservez les options par défaut :

  • Votre compte
  • Version de l'API proposée par Stripe

Image webhook stripe

Stripe écoutera ainsi les événements liés au compte Stripe du client.

Cliquez sur Continuer.

Choisir le type de destination

Sélectionnez :

Endpoint de webhook

Image webhook stripe

Cliquez sur Continuer.

Configurer la destination

Stripe crée automatiquement deux destinations :

  • une pour les charges utiles instantanées ;
  • une pour les charges utiles légères.

Ces deux formats sont complémentaires et permettent à Stripe d'optimiser l'envoi des événements.

Pour chacune des destinations, renseignez :

ChampValeur
NomStripe Webhook
URL d'endpointhttps://votre-domaine.com/webhooks/stripe

Image webhook stripe

La même URL peut être utilisée pour les deux destinations.

Une fois les deux formulaires complétés, cliquez sur Continuer.

Vérifier la configuration

Stripe affiche alors un récapitulatif des deux destinations.

Cliquez sur Ajouter des destinations.

Les webhooks sont maintenant enregistrés.

Récupérer le secret de signature

Ouvrez ensuite la destination créée.

Stripe affiche un Signing Secret (ou Secret de signature).

Copiez cette valeur.

Elle ressemble à :

whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Ajoutez-la dans le fichier .env :

STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
attention

Le secret de signature est confidentiel.

Il permet de vérifier que chaque requête reçue provient bien de Stripe et n'a pas été modifiée pendant son transport.

Récapitulatif — Variables d'environnement

À la fin de cette étape, le fichier .env doit contenir au minimum :

STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxx
STRIPE_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxx
info

L'environnement (test ou production) est déterminé automatiquement par les clés Stripe utilisées (sk_test_... ou sk_live_...).


</details>