Aller au contenu principal

Blueprint — Synchroniser le catalogue Stripe

Lecture : 15min

Synchroniser le catalogue avec l'IA

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

blueprints/stripe/02-synchroniser-catalogue-stripe.md

Voici son contenu à copier-coller intégralement :

Voir le contenu complet du blueprint
# Blueprint — Synchroniser le catalogue Stripe

## Mission

Analyse l’architecture Stripe déjà mise en place dans le projet, puis ajoute une commande permettant de synchroniser les produits et les tarifs configurés dans Stripe avec le catalogue local de l’application.

Le socle Stripe, les variables d’environnement, les migrations et les entités `billing_plans` et `billing_prices` sont supposés exister grâce au blueprint précédent.

Ne recrée pas ces éléments.

L’objectif est de pouvoir exécuter une commande afin de :

1. récupérer les produits actifs depuis Stripe ;
2. récupérer leurs tarifs ;
3. créer ou mettre à jour les plans et tarifs correspondants dans la base locale ;
4. désactiver localement les ressources qui ne sont plus actives dans Stripe ;
5. produire un compte rendu détaillé de la synchronisation.

## Principes d’architecture

Respecte impérativement les principes suivants :

- Stripe est la source de vérité pour les produits, les montants, les devises, les périodicités et l’état actif des tarifs ;
- l’application est la source de vérité pour les fonctionnalités et les droits associés à chaque plan ;
- la synchronisation ne doit jamais supprimer automatiquement un plan ou un tarif local ;
- un tarif Stripe inactif doit être désactivé localement, mais conservé pour l’historique ;
- la commande doit être idempotente ;
- plusieurs exécutions successives ne doivent créer aucun doublon ;
- les données de test et de production doivent rester strictement séparées ;
- la synchronisation ne doit jamais modifier le catalogue distant Stripe ;
- la commande doit fonctionner uniquement dans le sens Stripe vers l’application.

## Prérequis

Avant toute modification, vérifie que le projet dispose déjà :

- du SDK Stripe officiel ;
- d’un client Stripe centralisé ;
- de la variable `STRIPE_SECRET_KEY` ;
- d’une table ou entité `billing_plans` ;
- d’une table ou entité `billing_prices` ;
- des migrations correspondantes ;
- d’un champ permettant de distinguer les environnements Stripe ;
- d’un système de commandes ou de tâches exécutables.

Si un prérequis manque, arrête l’implémentation et indique précisément ce qui doit être ajouté avant d’exécuter ce blueprint.

Ne recrée pas silencieusement les éléments manquants.

## Étape 1 — Analyser le modèle existant

Avant de coder :

1. localise les entités ou modèles `billing_plans` et `billing_prices` ;
2. identifie leurs repositories ou services d’accès aux données ;
3. vérifie les contraintes d’unicité existantes ;
4. identifie le mécanisme de transactions de base de données ;
5. identifie le système de commandes du framework ;
6. vérifie si une synchronisation Stripe existe déjà ;
7. vérifie si des identifiants `prod_...` ou `price_...` sont déjà présents en base ;
8. vérifie si des codes métier sont déjà utilisés pour les plans et les tarifs.

Réutilise les conventions et services existants.

Ne crée pas une seconde logique de catalogue concurrente.

## Étape 2 — Créer la commande de synchronisation

Ajoute une commande adaptée aux conventions du projet.

Nom conceptuel recommandé :

stripe:catalog:sync

Dans un projet utilisant un `Makefile`, ajoute également une cible pratique :

stripe-sync-catalog:
<commande du framework permettant d’exécuter stripe:catalog:sync>

Adapte la commande réelle au framework détecté.

Exemples conceptuels :

php bin/console stripe:catalog:sync
php artisan stripe:catalog:sync
npm run stripe:catalog:sync
python manage.py stripe_catalog_sync

Ne force pas une syntaxe liée à un framework différent de celui du projet.

## Étape 3 — Ajouter les options de commande

La commande doit proposer au minimum les options suivantes ou leurs équivalents :

--dry-run
--product=<prod_...>
--force
--no-interaction

### `--dry-run`

Cette option doit :

- interroger Stripe ;
- calculer les créations et mises à jour nécessaires ;
- afficher les changements prévus ;
- ne modifier aucune donnée locale ;
- ne modifier aucune donnée distante.

### `--product`

Cette option permet de limiter la synchronisation à un produit Stripe précis.

Exemple :

stripe:catalog:sync --product=prod_xxxxxxxxx

### `--force`

Cette option permet de confirmer explicitement les opérations sensibles prévues par le projet.

Elle ne doit jamais autoriser la suppression automatique des historiques.

### `--no-interaction`

Cette option permet l’utilisation de la commande dans une chaîne d’intégration ou un déploiement automatisé.

## Étape 4 — Récupérer les produits Stripe

Utilise le client Stripe existant pour récupérer tous les produits actifs pertinents.

La commande doit gérer la pagination de l’API Stripe.

Ne suppose jamais que tous les produits sont présents dans la première page de résultats.

Pour chaque produit, récupérer au minimum :

- l’identifiant `prod_...` ;
- le nom ;
- la description ;
- l’état actif ;
- les métadonnées ;
- le comportement fiscal ou la catégorie fiscale lorsque ces informations sont disponibles et utiles ;
- la date de création Stripe ;
- la date de mise à jour disponible ou une information équivalente.

Par défaut, synchronise uniquement les produits actifs.

Prévois toutefois la détection des produits devenus inactifs afin de mettre à jour leur état local.

## Étape 5 — Déterminer le code métier du plan

Chaque produit Stripe doit être associé à un code de plan local stable.

Utilise en priorité une métadonnée Stripe explicite :

internal_plan_code

Exemple :

internal_plan_code=premium

Si cette métadonnée est absente, applique la stratégie suivante :

1. rechercher un plan local déjà associé au `stripe_product_id` ;
2. si une association existe, conserver son code actuel ;
3. sinon, générer un code provisoire à partir du nom du produit ;
4. normaliser le code généré ;
5. signaler clairement dans le compte rendu que ce code a été déduit automatiquement.

Exemple :

Premium Plus

peut devenir :

premium_plus

Le code généré doit :

- être en minuscules ;
- être stable ;
- ne contenir que des caractères autorisés par le projet ;
- ne pas contenir le montant ;
- ne pas contenir la devise ;
- être unique.

En cas de collision, ne crée pas un code arbitraire sans le signaler.

Utilise une stratégie déterministe et affiche un avertissement.

## Étape 6 — Créer ou mettre à jour les plans locaux

Pour chaque produit Stripe :

1. rechercher un plan local à partir de `stripe_product_id` lorsqu’il est disponible ;
2. sinon rechercher le plan par son code métier ;
3. créer le plan s’il n’existe pas ;
4. mettre à jour uniquement les données issues de Stripe ;
5. conserver les données purement métier de l’application.

La synchronisation peut mettre à jour :

- le nom commercial ;
- l’état actif ;
- l’identifiant `stripe_product_id` si le modèle le permet ;
- la description commerciale lorsque celle-ci est stockée localement.

La synchronisation ne doit pas écraser automatiquement :

- les fonctionnalités du plan ;
- les permissions ;
- les règles d’éligibilité ;
- l’ordre d’affichage ;
- les règles d’essai ;
- les règles métier propres à l’application.

Si `billing_plans` ne contient pas actuellement `stripe_product_id`, utilise la relation déjà prévue via `billing_prices`.

Ne modifie le schéma que si cela est strictement nécessaire et justifié.

## Étape 7 — Récupérer les tarifs Stripe

Pour chaque produit Stripe synchronisé, récupérer ses tarifs.

La commande doit gérer la pagination.

Récupérer au minimum :

- l’identifiant `price_...` ;
- l’identifiant du produit Stripe ;
- l’état actif ;
- la devise ;
- le montant unitaire ;
- le type ponctuel ou récurrent ;
- la période de facturation ;
- le nombre d’intervalles ;
- le comportement fiscal ;
- les métadonnées ;
- la date de création Stripe.

Prendre en charge au minimum :

- les paiements ponctuels ;
- les abonnements mensuels ;
- les abonnements annuels.

Si Stripe retourne un modèle tarifaire non pris en charge, par exemple :

- facturation à l’usage ;
- tarification par paliers ;
- tarif avec montant transformé ;
- tarif personnalisé ;
- plusieurs devises complexes ;

ne crée pas une représentation locale incorrecte.

Marque le tarif comme non pris en charge dans le compte rendu et poursuis la synchronisation des autres ressources.

## Étape 8 — Déterminer le code métier du tarif

Utilise en priorité une métadonnée Stripe :

internal_price_code

Exemple :

internal_price_code=premium_monthly

Si cette métadonnée n’existe pas :

1. rechercher un tarif local existant par `stripe_price_id` ;
2. conserver son code s’il existe ;
3. sinon générer un code stable à partir du plan et de la périodicité.

Exemples :

premium_monthly
premium_yearly
premium_one_time

Lorsqu’un produit contient plusieurs tarifs avec la même périodicité, utilise une information supplémentaire stable provenant des métadonnées ou de l’identifiant Stripe.

Ne base pas le code uniquement sur le montant.

Un changement de prix ne doit pas provoquer une réutilisation ambiguë du même tarif local.

## Étape 9 — Créer ou mettre à jour les tarifs locaux

Pour chaque tarif Stripe pris en charge :

1. rechercher l’entrée locale par `stripe_price_id` ;
2. sinon rechercher une correspondance par code et environnement ;
3. créer l’entrée si elle n’existe pas ;
4. mettre à jour les champs synchronisés depuis Stripe ;
5. associer le tarif au plan correspondant.

Synchroniser au minimum :

- `stripe_product_id` ;
- `stripe_price_id` ;
- `currency` ;
- `amount` ;
- `billing_type` ;
- `billing_interval` ;
- `interval_count` ;
- `tax_behavior` ;
- `is_active` ;
- `livemode`.

Le montant doit être conservé dans la plus petite unité monétaire.

Exemples :

9,90 € = 990
99,00 € = 9900

Ne convertis pas arbitrairement les devises.

## Étape 10 — Gérer les tarifs désactivés

Après la récupération des tarifs actifs et inactifs nécessaires à la comparaison :

1. identifier les tarifs locaux appartenant à l’environnement courant ;
2. identifier ceux qui ne sont plus actifs dans Stripe ;
3. positionner leur champ local `is_active` à `false` ;
4. conserver toutes les relations et données historiques ;
5. ne supprimer aucun tarif local.

Un tarif inactif ne doit plus pouvoir être utilisé pour une nouvelle souscription.

Il doit toutefois rester disponible pour :

- les abonnements existants ;
- l’historique des factures ;
- les contrôles de cohérence ;
- les audits.

## Étape 11 — Gérer les produits désactivés

Lorsqu’un produit Stripe est désactivé :

- désactive localement le plan correspondant si cette donnée est synchronisée ;
- désactive ses tarifs pour les nouvelles souscriptions ;
- ne supprime pas le plan ;
- ne retire pas automatiquement les entitlements existants ;
- ne résilie pas les abonnements existants.

La désactivation commerciale d’un produit ne doit pas provoquer une suppression de droits ou une résiliation financière automatique.

## Étape 12 — Garantir l’idempotence

La commande doit pouvoir être exécutée plusieurs fois sans créer :

- plusieurs plans pour un même produit Stripe ;
- plusieurs tarifs pour un même `price_...` ;
- plusieurs associations entre un plan et un tarif ;
- plusieurs versions locales identiques.

Utilise :

- les contraintes d’unicité existantes ;
- des opérations de type `upsert` lorsque cela est adapté ;
- des transactions de base de données ;
- une recherche prioritaire par identifiant Stripe ;
- une recherche secondaire par code métier et environnement.

## Étape 13 — Utiliser une transaction locale

Les écritures locales doivent être réalisées dans une transaction de base de données lorsque le projet le permet.

En cas d’erreur bloquante :

- annule les écritures de la synchronisation concernée ;
- affiche la ressource ayant provoqué l’erreur ;
- retourne un code de sortie non nul.

Les erreurs sur un tarif non pris en charge peuvent être signalées sans annuler toute la synchronisation, à condition qu’aucune donnée incohérente ne soit enregistrée.

## Étape 14 — Produire un compte rendu

À la fin de la commande, afficher un résumé lisible.

Exemple conceptuel :

Synchronisation du catalogue Stripe terminée

Produits Stripe analysés : 3
Plans créés : 1
Plans mis à jour : 2
Plans désactivés : 0

Tarifs Stripe analysés : 7
Tarifs créés : 2
Tarifs mis à jour : 4
Tarifs désactivés : 1
Tarifs ignorés : 0
Erreurs : 0

Afficher également :

- l’environnement Stripe utilisé ;
- les identifiants des ressources en erreur ;
- les codes générés automatiquement ;
- les tarifs non pris en charge ;
- les incohérences détectées ;
- la durée totale de la synchronisation.

Ne jamais afficher :

- la clé secrète Stripe ;
- le secret du webhook ;
- des données sensibles inutiles.

## Étape 15 — Ajouter une cible Makefile

Si le projet possède déjà un `Makefile`, ajoute les cibles suivantes ou leurs équivalents :

stripe-sync-catalog:
<commande du framework> stripe:catalog:sync

stripe-sync-catalog-dry-run:
<commande du framework> stripe:catalog:sync --dry-run

Si le projet ne possède pas de `Makefile`, n’en crée un que si cela reste cohérent avec les outils du projet.

Dans le cas contraire, documente simplement la commande native.

Le nom de la cible peut être adapté aux conventions existantes.

## Étape 16 — Prévoir l’automatisation

La commande doit pouvoir être exécutée :

- manuellement lors de l’installation ;
- après une modification du catalogue Stripe ;
- pendant un déploiement ;
- dans une tâche planifiée ;
- dans une chaîne d’intégration continue.

Ne programme pas automatiquement une exécution récurrente sans besoin explicite.

Documente cependant les différentes possibilités.

La synchronisation complète du catalogue ne doit pas être déclenchée à chaque requête utilisateur.

## Étape 17 — Ajouter les tests

Ajoute au minimum les tests suivants :

1. création d’un plan depuis un produit Stripe ;
2. mise à jour d’un plan existant ;
3. création d’un tarif depuis un Price Stripe ;
4. mise à jour d’un tarif existant ;
5. absence de doublon après deux synchronisations ;
6. association correcte entre tarif et plan ;
7. conservation des données métier du plan ;
8. synchronisation correcte du montant ;
9. synchronisation correcte de la devise ;
10. synchronisation correcte de la périodicité ;
11. prise en charge d’un paiement ponctuel ;
12. prise en charge d’un abonnement mensuel ;
13. prise en charge d’un abonnement annuel ;
14. désactivation locale d’un tarif inactif ;
15. absence de suppression physique d’un tarif ;
16. absence de résiliation d’un abonnement lors de la désactivation d’un produit ;
17. refus d’un tarif appartenant au mauvais environnement ;
18. gestion de la pagination Stripe ;
19. comportement correct du mode `--dry-run` ;
20. synchronisation limitée avec `--product` ;
21. gestion d’un modèle tarifaire non pris en charge ;
22. rollback de la transaction lors d’une erreur bloquante ;
23. génération déterministe des codes métier ;
24. conservation d’un code métier déjà existant.

Utilise des doubles de test pour l’API Stripe.

Ne crée pas de véritables produits ou tarifs Stripe dans les tests unitaires.

## Étape 18 — Ajouter la documentation

Documente :

- la commande de synchronisation ;
- la cible Makefile ;
- le mode `--dry-run` ;
- la synchronisation d’un produit précis ;
- la stratégie utilisée pour générer les codes métier ;
- les métadonnées Stripe recommandées ;
- les modèles tarifaires pris en charge ;
- les modèles tarifaires non pris en charge ;
- la gestion des produits et tarifs inactifs ;
- la séparation test/production ;
- la procédure à suivre après l’ajout d’un produit ou d’un tarif dans le Dashboard Stripe.

Recommande l’ajout des métadonnées suivantes dans Stripe :

text
Produit :
internal_plan_code=premium

Tarif :
internal_price_code=premium_monthly

## Étape 19 — Valider le catalogue synchronisé

À la fin de la synchronisation, exécute une série de contrôles de cohérence.

Vérifie notamment que :

- chaque `billing_price` est rattaché à un `billing_plan` ;
- chaque `stripe_product_id` est unique ;
- chaque `stripe_price_id` est unique ;
- aucun tarif actif n'est orphelin ;
- chaque plan possède au moins un tarif actif lorsque le produit Stripe est actif ;
- tous les tarifs d'un même produit appartiennent au même environnement (`livemode`) ;
- les montants sont stockés dans la plus petite unité monétaire ;
- aucune devise incohérente n'est détectée pour un même tarif ;
- les codes métier sont uniques ;
- aucune incohérence n'existe entre les ressources Stripe et la base locale.

En cas d'anomalie :

- afficher un rapport détaillé ;
- retourner un code d'erreur non nul si l'incohérence empêche l'utilisation correcte du catalogue ;
- ne jamais tenter de corriger silencieusement des données ambiguës.

## Résultat attendu

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

- d’une commande de synchronisation du catalogue Stripe ;
- d’une cible Makefile lorsque cela est cohérent ;
- d’une récupération paginée des produits ;
- d’une récupération paginée des tarifs ;
- d’une création ou mise à jour idempotente des plans ;
- d’une création ou mise à jour idempotente des tarifs ;
- d’une désactivation locale sans suppression ;
- d’un mode `--dry-run` ;
- d’un filtre par produit ;
- d’un compte rendu détaillé ;
- de tests automatisés ;
- d’une documentation d’utilisation.

## Contraintes finales

- Ne recrée pas les entités du blueprint précédent.
- Ne recrée pas les migrations existantes.
- Ne crée pas de session Checkout.
- Ne crée pas d’abonnement Stripe.
- Ne crée pas de client Stripe.
- Ne modifie aucun produit Stripe.
- Ne modifie aucun tarif Stripe.
- Ne supprime aucune ressource Stripe.
- Ne supprime aucune donnée locale historique.
- Ne détermine pas les droits fonctionnels depuis les produits Stripe.
- Ne déclenche pas la synchronisation à chaque requête utilisateur.
- Ne mélange jamais les ressources de test et de production.
- Ne journalise jamais une clé secrète.
- Ne considère jamais le nom d’un produit comme un identifiant métier fiable.

## 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. la commande ajoutée ;
4. les cibles Makefile ajoutées ;
5. la stratégie de correspondance retenue ;
6. les métadonnées Stripe utilisées ;
7. les ressources prises en charge ;
8. les ressources ignorées ;
9. les tests ajoutés et leur résultat ;
10. les commandes exactes à exécuter ;
11. les éventuels ajustements manuels à effectuer dans Stripe.