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

@ -0,0 +1,30 @@
-- Sprint 5a — Idempotency des webhooks Stripe (TD-13)
--
-- Stripe peut livrer le même `event.id` plusieurs fois (retries réseau,
-- rejeu manuel depuis le dashboard). Cette table sert de journal de
-- déduplication : la route `POST /stripe/webhook` consulte la table
-- avant traitement et y insère l'event après succès.
--
-- Stratégie (cf. TD-13) :
-- 1. Avant traitement, `SELECT 1 FROM stripe_webhook_events WHERE id = $1`.
-- Présent → retour 200 immédiat sans rien faire.
-- 2. Après traitement, `INSERT ... ON CONFLICT DO NOTHING`.
--
-- Race window résiduelle (deux deliveries concurrentes passent toutes deux
-- le SELECT initial) couverte par l'idempotence native des opérations
-- métier (`updateUserPlan`, `updateUserStripeInfo`).
--
-- À exécuter manuellement : `supabase db push` (Hermann — cf. Règle F).
-- Idempotent : sûre à rejouer en dev comme en prod.
CREATE TABLE IF NOT EXISTS stripe_webhook_events (
id TEXT PRIMARY KEY,
processed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Index pour les futures purges (rétention ~90 jours envisagée).
CREATE INDEX IF NOT EXISTS stripe_webhook_events_processed_at_idx
ON stripe_webhook_events (processed_at);
COMMENT ON TABLE stripe_webhook_events IS
'Journal de déduplication des webhooks Stripe (Sprint 5a — TD-13).';