Skip to content
Docs

Command palette

Jump to a page or view, or find a todo by name.

Changelog

Payments

Stripe and Polar feed one server-verified entitlement model.

Markdown

Enable providers deliberately

All providers start off. Set BILLING_PROVIDERS to the required comma-separated names: stripe, polar. Each listed provider must have its complete valid configuration. BILLING_ENVIRONMENT distinguishes SANDBOX from PRODUCTION; Stripe key mode must agree.

Stripe and Polar sell on the web. If both web providers are enabled, BILLING_CHECKOUT_PROVIDER chooses new checkouts; existing customers retain their provider portal. Visibility uses billing.web.

Entitlements and webhooks

Paid Access belongs to the User, not an Organization. The server's entitlement read model projects enabled-provider rows in the current environment. Leftover store rows (for example provider = 'revenuecat') stay in the table and are ignored. Stripe and Polar apply signed, deduplicated events in transactions.

ProviderWebhook pathServer secrets
Stripe/api/billing/stripeSTRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_IDS
Polar/api/billing/polarPOLAR_ACCESS_TOKEN, POLAR_WEBHOOK_SECRET, POLAR_PRODUCT_IDS

Raw webhook verification precedes payload parsing. A replay is a no-op, and a failed write rolls back its dedupe record so the provider can retry.

Extend and test

Add plans to the leaf billing-plans.ts, map provider product IDs in env, and add name/description translations. Checkout return URLs come from the server, never a client-supplied redirect. Test purchases in sandbox and verify billing.status, cancellation and webhook replay before enabling production visibility.

Stripe and Polar currently rely on webhooks plus the grace window; no scheduled pull reconciles their subscriptions. billing.sync re-reads that projection. See ADR-0004.

Key files

Edit on GitHubDocumentation is in English.