Stripe wiring
The three plans
Free — 3 members, no team features
Team — $29/mo, 10 members, 90d audit retention
Business — $99/mo, unlimited members, forever audit retentionPlans are test-mode only in the demo. No real charges, ever.
Environment
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRICE_TEAM=price_...
STRIPE_PRICE_BUSINESS=price_...Without these, the billing UI runs in "not configured" mode — plans are visible, Checkout/Portal buttons return an error.
The webhook (load-bearing)
supabase/functions/stripe-webhook verifies the signature with STRIPE_WEBHOOK_SECRET, then runs insert-first idempotency:
insert into processed_stripe_events (event_id, event_type)
values ($1, $2)
on conflict (event_id) do nothing
returning event_idIf RETURNING is empty, the function returns 200 immediately, before any side effect. This is the right pattern under Stripe's at-least-once delivery.
Check-then-insert is racy and is the textbook bug: two concurrent webhook deliveries both see "not processed," both run the side effect, then one INSERT fails on the unique key — but the side effect already ran twice. The pgtap concurrency test in tests/10_concurrency.sql asserts the correct shape.
State transitions
checkout.session.completed— upserts subscription row with metadata-resolved workspace_idcustomer.subscription.updated— UPDATE in placecustomer.subscription.deleted— UPDATE status = canceledinvoice.payment_failed— audit/telemetry only; relies on Stripe's subsequent updated event for statecustomer.subscription.trial_will_end— no state change; trial banner reads fromtrial_ends_atlive
Retention
processed_stripe_eventsretains 90 days. Stripe's documented redelivery window is ~3 days; 90 is generous. The pg_cron job in 0110_pg_cron_retention.sql handles the daily sweep.
Hard-delete cascade
When a workspace is hard-deleted (24h after soft-delete), thecancel-stripe-subscription Edge Function cancels the Stripe sub with prorate=false. Failure blocks the hard-delete — orphan Stripe customers continuing to charge is the failure mode this guards against.