Billing Overview
Leave Localhost includes a complete billing system that supports three payment providers out of the box: Polar, Stripe, and Lemon Squeezy. Billing is optional — the app works without any provider configured.
Leave Localhost includes a complete billing system that supports three payment providers out of the box: Polar, Stripe, and Lemon Squeezy. Billing is optional — the app works without any provider configured.
Architecture
┌─────────────────────────────────────────────────┐
│ Frontend (apps/app) │
│ Checkout → Plan selector → Customer portal │
└──────────────┬──────────────────────────────────┘
│ Convex actions (createCheckout, createCustomerPortal)
▼
┌─────────────────────────────────────────────────┐
│ Billing Layer (packages/backend/convex) │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ Plan Catalog │ │ Adapter │ │ Grants │ │
│ │ (catalog.ts) │ │ (adapter.ts) │ │ (grants.ts│ │
│ └─────────────┘ └──────────────┘ └───────────┘ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Provider Mapping (env vars → plan keys) │ │
│ └─────────────────────────────────────────────┘ │
└──────────────┬──────────────────────────────────┘
│ Webhooks (http.ts routes)
▼
┌─────────────────────────────────────────────────┐
│ Payment Provider (Polar / Stripe / LemonSqueezy)│
└─────────────────────────────────────────────────┘Key Concepts
Plan Catalog
The plan catalog at billing/catalog.ts defines every available plan:
Free, Pro Monthly, Pro Yearly, and Pro Lifetime. Each plan
maps to a set of capabilities.
Provider Adapter
The BillingProviderAdapter interface abstracts checkout, customer portal,
and cancellation behind three methods. Each provider implements this interface
in its own subdirectory.
Grants
When a webhook event arrives, it is converted into billing grants — rows
in the billing_grants table that grant specific capabilities to an
organization. The rest of the app checks grants, never provider-specific
subscription state.
Provider Selection
Billing is opt-in and ships disabled. Every provider integration is compiled into the deployment, so activating one is a single runtime choice:
- Set
BILLING_PROVIDERtopolar,stripe, orlemon_squeezy. - Fill that provider's variables and register its webhook URL (see the per-provider guides).
Leaving BILLING_PROVIDER blank keeps billing in "unconfigured" mode: users see
the Free plan, and checkout/portal/cancellation fail closed. BILLING_PROVIDER
is the sole selector — switching providers is just changing the env var and
redeploying; no source is mutated and no install/remove step exists.
Default Plans
| Plan Key | Display Name | Interval | Capabilities |
|---|---|---|---|
free | Free | — | (none) |
pro_monthly | Pro | Month | feature.pro, workspace.members.invite, workspace.members.limit.10, billing.portal |
pro_yearly | Pro | Year | Same as Pro Monthly |
pro_lifetime | Pro Lifetime | One-time | Same as Pro Monthly |
Billing Flow
- User clicks "Upgrade" in the app.
- Frontend calls
createCheckoutaction with aplanKey. - The billing adapter creates a checkout session with the provider and returns a redirect URL.
- User completes payment on the provider's hosted checkout page.
- Provider sends a webhook event to
/webhooks/<provider>. - The webhook handler creates/updates grants in the
billing_grantstable. - Capabilities are now active — the app reacts in real time.
Files
| File | Purpose |
|---|---|
billing.ts | Public queries and actions (listPlans, createCheckout, etc.) |
billing/catalog.ts | Plan definitions and capability mappings |
billing/config.ts | Provider selection from BILLING_PROVIDER |
billing/integration.ts | BillingProviderIntegration contract (adapter + webhook routes) |
billing/providers.ts | Static registry of all provider integrations |
billing/adapter.ts | Provider adapter interface; resolves the active provider's adapter (fails closed) |
billing/grants.ts | Grant sync, creation, revocation |
billing/providerMapping.ts | Maps provider product/price IDs to plan keys |
billing/subscriptions.ts | Subscription state selection logic |
billing/validators.ts | Billing-specific Convex validators |
billing/cleanup.ts | Organization deletion billing cleanup |
billing/stripe/ | Stripe adapter, webhook handler, queries, mutations |
billing/polar/ | Polar adapter, webhook handler, queries, mutations |
billing/lemonSqueezy/ | Lemon Squeezy adapter, webhook handler, queries, mutations |
Next Reads
- Billing Catalog — customizing plans.
- Free, Pro, and Lifetime Plans — the default plan structure.
- Entitlements and Grants — how grants work.
- Webhooks — webhook event handling.
- Polar, Stripe, Lemon Squeezy — provider-specific setup.
- Removing Billing — removing billing entirely.