# Backend — Patterns : Stripe
> Extrait de la base de connaissance Lead_tech. Voir `knowledge/backend/patterns/README.md` pour l'index complet.
---
## Pattern : Provider-Strategy pour intégrations tierces — périmètre complet
- Objectif : isoler intégralement la logique propre à un prestataire (Stripe, Brevo, Firebase…) derrière une interface stable, pour éviter la contamination du domaine par le SDK tiers.
- Contexte : backend NestJS/TypeScript avec 1+ prestataires externes (paiement, email, storage…).
- Quand l'utiliser : dès qu'un service applicatif dépend d'un SDK tiers (et plus encore s'il y a des webhooks).
- Quand l'éviter : intégration ponctuelle non critique sans effet de bord (rare) — sinon on perd vite le contrôle.
- Avantage :
- Testabilité : mock du provider, pas du SDK
- Remplacement du prestataire sans refactor "en cascade"
- Responsabilités claires : provider = "parle Stripe", service = "parle domaine"
- Limites / vigilance :
- L'interface doit exposer des **types normalisés** (pas de types Stripe)
- Le provider gère aussi les webhooks : validation signature, parsing event, mapping
- Validé le : 09-03-2026
- Contexte technique : NestJS v10+ / intégration Stripe (webhooks) — pattern généralisable
### Implémentation (exemple minimal)
```typescript
// billing-provider.interface.ts (pas d'import Stripe)
export type BillingPlan = 'MONTHLY' | 'ANNUAL';
export type BillingWebhookResult = {
userId: string;
externalId: string;
plan: BillingPlan;
status: 'ACTIVE' | 'INACTIVE' | 'CANCELLED';
currentPeriodEnd: Date | null;
};
export interface BillingProvider {
createCheckoutSession(userId: string, plan: BillingPlan): Promise<{ checkoutUrl: string }>;
cancelSubscription(externalId: string): Promise;
handleWebhook(rawBody: Buffer, signature: string): Promise;
}
// billing.service.ts (domaine uniquement)
async handleWebhook(rawBody: Buffer, signature: string): Promise {
const result = await this.billingProvider.handleWebhook(rawBody, signature);
if (!result) return;
await this.prisma.subscription.upsert({ /* données normalisées */ });
}
```
---
## Pattern : Stripe — metadata sur `subscription_data`, pas sur la Session
- Objectif : garantir que `userId` (ou tout identifiant métier) soit accessible dans les events `customer.subscription.*`, pas seulement dans `checkout.session.completed`.
- Contexte : intégration Stripe Checkout avec webhooks abonnement.
- Quand l'utiliser : systématiquement dès qu'on crée une Checkout Session liée à une Subscription.
- Risque si ignoré : `metadata.userId` absent des events `customer.subscription.updated/deleted` → silent failure en prod.
- Validé le : 09-03-2026
- Contexte technique : Stripe API v17+ / NestJS
### Implémentation
```typescript
stripe.checkout.sessions.create({
metadata: { userId }, // pour checkout.session.completed
subscription_data: { metadata: { userId } }, // pour customer.subscription.*
});
```
---
## Pattern : Webhooks entrants — parsing unique (single `constructWebhookEvent`)
- Objectif : appeler `constructWebhookEvent` une seule fois par requête, puis router vers des extracteurs purs.
- Contexte : endpoint webhook recevant des events de plusieurs types (subscription, pack, facture…).
- Quand l'utiliser : dès qu'on a 2+ handlers webhook sur le même endpoint.
- Risque si ignoré : double vérification de signature + états partiels possibles (sub OK / pack KO).
- Validé le : 09-03-2026
- Contexte technique : Stripe / NestJS
### Implémentation
```typescript
// 1. Parser unique — 1 seul constructWebhookEvent(rawBody, sig) → event opaque
// 2. Extracteurs purs, sans effet de bord :
handleSubscriptionWebhookEvent(event): WebhookResult | null
handlePackWebhookEvent(event): PackWebhookResult | null
// 3. Orchestrateur unique appelle les extracteurs, persiste les résultats
```
---
## Pattern : restauration d'achats Stripe en 3 étapes
- Objectif : reconstruire un état local cohérent à partir de Stripe sans dépendre d'une hypothèse fragile.
- Contexte : flux de restore purchases mobile/web avec état local potentiellement désynchronisé.
- Quand l'utiliser : dès qu'un utilisateur peut restaurer des achats depuis un nouveau device ou après désynchronisation.
- Quand l'éviter : si l'état Stripe n'est pas la source de vérité.
- Avantage :
- rend la réconciliation explicite
- supporte retries et restaurations tardives
- Limites / vigilance :
- la pagination Stripe et l'idempotence d'écriture restent obligatoires
- Validé le : 10-03-2026
- Contexte technique : Stripe API / backend Node/NestJS
### Implémentation (exemple minimal)
```txt
1. Résolution du customer Stripe (ID persisté en DB, fallback robuste si absent)
2. Reconstruction de l'état Stripe utile au domaine
3. Réconciliation et écritures locales idempotentes
```
### Checklist
- `stripeCustomerId` persistant côté app
- Réconciliation explicite documentée
- Upsert ou écriture idempotente
---
## Pattern : Sémantique explicite `Trial` vs `Paid` dans Subscription
- Objectif : aligner le modèle métier, les guards et les jeux de tests sur une définition unique de l'abonnement payant actif.
- Contexte : modèle `Subscription` où `trialEndsAt` matérialise un essai.
- Quand l'utiliser : dès qu'un même enregistrement supporte trial et abonnement payant.
- Quand l'éviter : si trial et abonnement payant sont modélisés par des entités distinctes.
- Avantage :
- évite les incohérences silencieuses dans les guards
- rend les fixtures et mocks e2e cohérents avec la règle métier
- Limites / vigilance :
- toute logique `isActive` doit préciser si elle signifie "trial ou paid" ou "paid only"
- Validé le : 10-03-2026
- Contexte technique : Backend agnostique / modèle d'abonnement
### Implémentation (exemple minimal)
```txt
- Un abonnement payant actif n'est pas seulement status = ACTIVE
- Il doit aussi avoir trialEndsAt = null
- Les fixtures et mocks e2e d'un abonnement payant fixent toujours trialEndsAt: null
```
### Checklist
- Règle métier explicitée
- Guards alignés sur la sémantique choisie
- Fixtures et seeds cohérents