Stripe
Stripe & billing
Org-level subscriptions use Stripe (test keys by default) with server code in packages/server/src/billing/ and UI on the product app (/settings/billing, pricing/checkout flows).
Configuration
Client singleton and guards: packages/server/src/billing/stripe-config.ts (getStripe(), getStripeWebhookSecret()).
Database
- Table
subscriptions(one per organization):stripe_customer_id,stripe_subscription_id,stripe_price_id, status, billing period,cancel_at_period_end. - Checkout and customers attach
organizationIdin metadata so webhooks and reconciliation can resolve the workspace.
Server functions (high level)
getPricingCatalog— Plans fromPRICING_CATALOGin shared config, filtered to env-configured price ids.getOrgBillingSummary— Org subscription snapshot for settings UI (membership check).ensureStripeCustomerForOrg— Creates or recovers Stripe customer; clears stale IDs if the customer does not exist for the current API key.createStripeCheckoutSession/ Customer Portal — Restricted to owner/admin; price ids must match configured catalog env vars.reconcileOrgSubscriptionFromStripe— Pulls latest subscription from Stripe (retries with delay) and upserts DB — useful when webhooks are not wired locally.
Implementation: packages/server/src/billing/billing-actions.ts, subscription-sync.ts.
Webhook endpoint
- Route:
apps/web/src/routes/api/stripe/webhook.tsx→packages/server/src/billing/stripe-webhook.ts. - Verifies signature with
STRIPE_WEBHOOK_SECRET. - Handled events include:
checkout.session.completed— sync from session.customer.subscription.created/updated— sync by subscription id.customer.subscription.deleted— mark canceled.invoice.payment_failed— notify org billing recipients (seebilling-notification-hooks).customer.subscription.trial_will_end— trial ending notifications where implemented.
Local development
- Without
stripe listen, use “Pull from Stripe” /reconcileOrgSubscriptionFromStripeafter checkout so the DB updates. - With webhooks:
stripe listen --forward-to http://localhost:3001/api/stripe/webhook(use your app port) and setSTRIPE_WEBHOOK_SECRETto the CLI signing secret.
Billing access
- Only owner and admin org roles may manage billing (
assertBillingManagerinbilling-actions.ts).