feat(billing): TD-13 webhook idempotency + Stripe Customer Portal + doc cleanup

- Table stripe_webhook_events + helpers isEventProcessed/markEventProcessed
- POST /stripe/customer-portal (auth + stripe_customer_id check)
- ARCHITECTURE-backend.md: suppression POST /plans/upgrade (duplication doc)
- TD-13 fermé dans TECH_DEBT-backend.md
- Tests: 261 → 278 verts (+17)
This commit is contained in:
Hermann_Kitio 2026-04-26 04:15:46 +03:00
parent ec0598d122
commit 6671bac347
10 changed files with 891 additions and 324 deletions

View file

@ -97,15 +97,14 @@
### TD-13 — Webhook Stripe non idempotent
**Priorité :** 🔴 Critique
**Statut :** Ouvert — à faire avant mise en production
**Description :** Stripe peut livrer un même event webhook deux fois (retries réseau, rejeu manuel depuis le dashboard). La route `POST /stripe/webhook` traite chaque réception sans dédoublonnage. En pratique, les opérations `updateUserPlan` et `updateUserStripeInfo` sont idempotentes par nature (même résultat en cas de double appel), mais si de la logique non idempotente est ajoutée plus tard (ex: compteur, envoi d'email, crédit utilisateur), un double traitement causerait un bug.
**À faire :**
**Statut :** Résolu — Sprint 5a (2026-04-26)
**Description :** Stripe peut livrer un même event webhook deux fois (retries réseau, rejeu manuel depuis le dashboard). La route `POST /stripe/webhook` traite désormais chaque réception via une déduplication explicite : check `stripe_webhook_events(id)` avant traitement, INSERT après succès.
**Résolution Sprint 5a :**
- Créer une table `stripe_webhook_events(id TEXT PRIMARY KEY, processed_at TIMESTAMPTZ)`
- Avant traitement, vérifier si `event.id` est déjà en base → si oui, retourner 200 sans rien faire
- Après traitement, insérer l'`event.id` dans la table
**Session concernée :** Stripe (POST /stripe/webhook)
**Condition de résolution :** Avant la mise en production publique.
- Migration `supabase/migrations/007_sprint_5a_stripe_webhook_events.sql` — table `stripe_webhook_events(id TEXT PRIMARY KEY, processed_at TIMESTAMPTZ NOT NULL DEFAULT NOW())` + index sur `processed_at`.
- Helper `src/lib/stripeWebhookEvents.ts``isEventProcessed` / `markEventProcessed` (insert idempotent, conflit unique avalé silencieusement).
- `src/routes/stripe.ts` — early return `200 { received: true, replayed: true }` si l'event est déjà journalisé ; `markEventProcessed(event.id)` après traitement réussi (pas si exception, pour permettre rejeu Stripe).
- 8 tests unitaires + 2 tests d'intégration (`isEventProcessed`/`markEventProcessed` + comportement route).
---
@ -258,3 +257,4 @@ Gate de qualité actuel : npm run test.
| TD-16 | Bucket Storage abandonné | 2026-04-25 | Sprint 4b — Deepgram direct |
| TD-17 | Limite audio in-memory caduque | 2026-04-25 | Sprint 4b |
| TD-18 | RLS Storage caduque | 2026-04-25 | Sprint 4b |
| TD-13 | Webhook Stripe idempotent | 2026-04-26 | Sprint 5a |