Billing & Stripe Flow¶
This page explains how Workbench AI handles subscriptions and billing using Stripe + Supabase.
- Stripe is the source of truth for billing events (subscriptions, invoices, payments).
- Supabase stores a mirrored view of subscription status in the
subscriptionsand related tables. - The frontend coordinates checkout/upgrade/portal actions via Supabase Edge Functions.
Subscription lifecycle (high level)¶
From the app’s point of view, a tenant’s subscription goes through these stages:
-
Signup + trial start
- Owner chooses a plan on
/pricing(e.g.starter,professional). /signupcreates a Supabase user + tenant/company record.- A Stripe Checkout Session is created via the
create-checkout-sessionedge function. - Stripe manages any free trial periods configured on the Price.
- Owner chooses a plan on
-
Checkout completed
- User completes checkout on Stripe.
- Stripe sends webhooks (e.g.
checkout.session.completed,customer.subscription.created). stripe-webhookedge function:- Validates the webhook using
STRIPE_WEBHOOK_SECRET. - Finds the associated
company_id(via metadata / subscription / customer lookups). - Upserts or updates a row in
subscriptionswith the new status and Stripe IDs. - Records the event in
webhook_events_processedfor idempotency.
-
Ongoing subscription changes
- Stripe events like
customer.subscription.updated,invoice.paid,customer.subscription.deletedtriggerstripe-webhook. - The webhook and sync helpers (
sync-stripe-subscriptions,sync-payment-history) keep Supabase tables in sync. - The frontend reads subscription state via
useSubscriptionand helpers likeisSubscriptionActive.
- Stripe events like
-
Upgrade/downgrade / plan changes
- User initiates a plan change from
/billingor via upgrade prompts. - Depending on UI design, either:
- A new Stripe Checkout Session is created (again using
create-checkout-session), or - The user is sent to the Stripe Billing Portal via
create-portal-session. - Stripe adjusts subscription; webhooks update Supabase.
- User initiates a plan change from
-
Cancellation / reactivation
- Cancellation may be triggered via Billing Portal or customer support.
- Stripe webhooks update
subscriptionsstatus to canceled. reactivate-subscriptionedge function can be used to restart a subscription using Stripe APIs and update Supabase.
Source of truth
Stripe is the authoritative source for billing. Supabase mirrors status for quick checks and UI gating. When in doubt, trust Stripe and let sync/webhook functions reconcile Supabase.
UI trigger locations (where billing starts)¶
At least three main entry points in the UI trigger billing flows:
-
Signup → plan selection → checkout
/pricingpage lets the user select a plan (e.g.?plan=professional)./signupusesuseSignup+useCheckoutto:- Create user + company.
- Call
create-checkout-sessionedge function to create a Stripe Checkout Session. - User is redirected to Stripe Checkout.
-
Billing settings page
/billingand/settings/billingpages expose subscription details and upgrade/downgrade options.- Buttons on these pages typically trigger:
create-portal-sessionedge function → Stripe Billing Portal.- or a new call to
create-checkout-sessionfor plan changes.
-
Locked feature upgrade prompts
- Protected routes with
featureflags (e.g. certain tools or analytics) useProtectedRoute. - When a user lacks access,
ProtectedRouterenders an "Upgrade Required" card with anUpgradePrompt. - The upgrade button eventually routes the user to
/billingor triggers a checkout/portal call.
- Protected routes with
Other parts of the app (e.g. owner dashboard) may surface links to billing, but these three are the primary flow entry points.
Edge functions involved¶
All billing-related edge functions live under supabase/functions/:
-
stripe-webhook/- Receives all Stripe webhooks.
- Validates signatures using
STRIPE_WEBHOOK_SECRET. - Ensures idempotency via
webhook_events_processed. - Updates
subscriptions, payment history, and related tables.
-
create-checkout-session/- Creates Stripe Checkout Sessions for subscription plans.
- Ensures a Stripe customer exists for the company.
- Uses price IDs from env vars (e.g.
STRIPE_STARTER_PRICE_ID,STRIPE_PROFESSIONAL_PRICE_ID). - Returns a
session.urlto the frontend.
-
create-portal-session/- Creates Stripe Billing Portal sessions for managing payment methods, invoices, and plan changes.
- Looks up
stripe_customer_idfromsubscriptions(or via Stripe if missing). - Returns a portal URL that the frontend opens.
-
sync-stripe-subscriptions/andsync-payment-history/- Periodically reconcile Supabase tables with Stripe data.
- Useful if any webhook events were missed.
-
get-stripe-subscription/,get-upcoming-invoice/,get-customer-invoices/- Read-only helpers for the frontend to display subscription and invoice information.
-
reactivate-subscription/- Handles reactivating canceled/paused subscriptions, updating both Stripe and Supabase.
API/Data details
For request/response shapes of each function, see the API/Data → Edge Functions page (once filled out).
Data model (where subscription status lives)¶
Key Supabase tables (names may vary slightly; see migrations for exact names):
-
subscriptionscompany_id– which tenant/company the subscription belongs to.stripe_subscription_id– Stripe subscription ID.stripe_customer_id– Stripe customer ID.status– Stripe-status mirror (trialing,active,past_due,canceled, etc.).- Plan and billing period fields.
-
webhook_events_processedevent_id– Stripe event ID.status,processed_at– track idempotency.
-
Payment history / invoices tables
- Tables updated by
sync-payment-historyand invoice-related webhooks.
- Tables updated by
On the frontend:
useSubscriptionhook reads subscription status (via Supabase).isSubscriptionActive(subscription)determines if the user should have access.getLockoutReason(subscription)provides a human-readable reason for redirects to/billing.
End-to-end subscription flow (diagram)¶
flowchart LR
U[User Browser] --> UI[/Billing / Signup UI/]
UI --> EF1["create-checkout-session (Edge Function)"]
EF1 --> ST[Stripe Checkout]
ST -->|User pays / completes| STSUB[Stripe Subscription]
STSUB -->|webhooks| EF2["stripe-webhook (Edge Function)"]
EF2 --> SBD[(Supabase: subscriptions + payments)]
UI -->|check status| SBD
click UI "../architecture/frontend/" "Frontend billing & signup pages"
click EF1 "../api/edge-functions/" "create-checkout-session"
click EF2 "../api/edge-functions/" "stripe-webhook"
click ST "./billing-stripe/" "Billing flow (this page)"
click SBD "../architecture/supabase/" "Supabase schema & subscriptions"
Narrative:
- User triggers billing (signup, upgrade, or manage billing).
- Frontend calls
create-checkout-session(orcreate-portal-session) to get a Stripe URL. - User completes checkout or manages subscription on Stripe.
- Stripe fires webhooks;
stripe-webhookupdatessubscriptionsand payment tables. - Frontend reads subscription status from Supabase via
useSubscriptionand gates routes withProtectedRoute.
Where to look next¶
- For auth/session details feeding into billing: Auth Flow
- For how webhooks update the database: Stripe Webhooks Flow
- For Supabase tables and functions: Supabase Architecture and the API/Data section.