Billing
LaunchKit Pro ships with a billing provider switch: MOCK (default), Stripe, or Lemon Squeezy. Mock billing records subscriptions and payments in PostgreSQL without external API calls. Real providers create secure checkout sessions and sync status through webhooks.
See Product Editions for Lite / Pro / Enterprise.
Plan catalog (admin)
Admin → Plans (/admin/plans) — full CRUD on billing plans stored in PostgreSQL:
- Create / edit plans (name, code, price, edition, features, badges)
- Archive / restore / safe delete (blocked when subscriptions exist)
- Optional Stripe Price ID and Lemon Variant ID per plan — used only when
BILLING_PROVIDERisSTRIPEorLEMON_SQUEEZY - Default seed is create-only — admin edits are not overwritten on restart
User billing page reads published plans from the database (not hardcoded JSON).
Mock checkout sends planCode / planId from the selected DB plan.
See Billing Settings for provider status UI.
Provider switch
BILLING_PROVIDER | Behavior |
|---|---|
MOCK | Default. Instant mock checkout; no payment keys required |
STRIPE | Stripe Checkout Session + Stripe webhooks |
LEMON_SQUEEZY | Lemon Squeezy checkout API + Lemon webhooks |
curl -s http://localhost:4000/billing/provider-status | jqExample response:
{
"provider": "MOCK",
"configured": true,
"mode": "mock",
"message": "Mock billing is active. No real payment will be charged."
}When BILLING_PROVIDER=STRIPE or LEMON_SQUEEZY but required keys are missing, configured is false and checkout returns a friendly configuration error. The app does not fail startup when inactive provider keys are empty.
No payment secrets in frontend or docs
Stripe secret keys, Lemon Squeezy API keys, and webhook signing secrets belong in backend .env only. Never commit them or expose them in API responses or NEXT_PUBLIC_* variables.
Environment variables
See Environment Variables for the full list. Minimum billing block:
BILLING_PROVIDER=MOCK
BILLING_SUCCESS_URL=http://localhost:3000/dashboard/billing/success
BILLING_CANCEL_URL=http://localhost:3000/dashboard/billing/cancel
BILLING_SUPPORT_EMAIL=support@launchkitlabs.comStripe (when BILLING_PROVIDER=STRIPE):
STRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRETSTRIPE_PRICE_LITE,STRIPE_PRICE_PRO,STRIPE_PRICE_ENTERPRISE- Optional:
STRIPE_CUSTOMER_PORTAL_ENABLED=true
Lemon Squeezy (when BILLING_PROVIDER=LEMON_SQUEEZY):
LEMON_SQUEEZY_API_KEYLEMON_SQUEEZY_STORE_IDLEMON_SQUEEZY_WEBHOOK_SECRETLEMON_SQUEEZY_VARIANT_LITE,LEMON_SQUEEZY_VARIANT_PRO,LEMON_SQUEEZY_VARIANT_ENTERPRISE
Provider keys are buyer-owned. Map price/variant IDs to your Stripe or Lemon Squeezy products.
API routes
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /billing/provider-status | Public | Active provider and configuration state |
POST | /billing/checkout | JWT | Create checkout (mock, Stripe, or Lemon) |
GET | /billing/current | JWT | Current plan / edition for workspace |
GET | /billing/history | JWT | Payment history with receipt links when available |
POST | /billing/cancel | JWT | Cancel subscription (mock local; real providers foundation) |
GET | /billing/plans | Public | Published plan catalog (sorted by sort order) |
POST | /billing/webhooks/stripe | Signature | Stripe webhook ingestion |
POST | /billing/webhooks/lemon-squeezy | Signature | Lemon Squeezy webhook ingestion |
POST | /billing/webhooks/mock | Dev | Mock webhook ingestion |
Admin plan catalog (admin role required):
| Method | Path | Description |
|---|---|---|
GET | /admin/billing/plans | All plans including drafts and archived |
GET | /admin/billing/plans/:planId | Single plan |
POST | /admin/billing/plans | Create plan (unique slug, non-negative price) |
PATCH | /admin/billing/plans/:planId | Update plan |
POST | /admin/billing/plans/:planId/archive | Archive (hidden from user billing) |
POST | /admin/billing/plans/:planId/restore | Restore archived plan |
DELETE | /admin/billing/plans/:planId | Delete — only when the plan has no subscriptions; otherwise archive |
GET | /admin/billing/settings | Secret-safe provider settings status |
PATCH | /admin/billing/settings | Read-only in env-backed deployments (friendly message) |
Legacy workspace routes remain for compatibility:
GET /workspaces/:workspaceId/billingPOST /workspaces/:workspaceId/billing/mock-checkoutPOST /workspaces/:workspaceId/billing/cancelGET /workspaces/:workspaceId/billing/payments
Plan catalog
Plans live in the database (Plan model) and are managed from Admin Panel → Plans:
- Name, slug, description, edition (Lite / Pro / Enterprise / Custom)
- Price (cents), currency, billing interval (one-time / monthly / yearly)
- Status: Draft (admin-only), Published (visible on user billing), Archived (hidden, kept for history)
- Badge text, popular/default flags, sort order, features list, limits
- Optional provider mapping IDs (
stripePriceId,lemonVariantId) stored for future use — no credentials
Default Lite / Pro / Enterprise plans are seeded once on first boot. Seeding is create-only: admin edits to price, features, or status are never overwritten on restart. Plans with subscriptions cannot be deleted — archive them instead.
Checkout
Checkout is driven by the DB plan slug (preferred) or the legacy edition key:
curl -s -X POST http://localhost:4000/billing/checkout \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{"planCode":"pro","workspaceId":"<workspaceId>"}' | jq- MOCK — completes locally and updates current edition immediately
- STRIPE / Lemon Squeezy — returns
checkoutUrl; redirect the user; status syncs after webhook confirmation - Demo user — can use mock checkout when provider is MOCK; blocked from real provider checkout with a friendly message
Current plan and history
curl -s "http://localhost:4000/billing/current?workspaceId=<workspaceId>" \
-H "Authorization: Bearer <accessToken>" | jq
curl -s "http://localhost:4000/billing/history?workspaceId=<workspaceId>&page=1&limit=20" \
-H "Authorization: Bearer <accessToken>" | jqreceiptUrl / invoiceUrl appear only when the provider supplies them.
Webhooks
Real billing requires publicly reachable HTTPS webhook endpoints:
https://api.yourdomain.com/billing/webhooks/stripehttps://api.yourdomain.com/billing/webhooks/lemon-squeezy
Configure signing secrets in your provider dashboard and backend .env. Webhooks verify raw request bodies, update subscriptions/payments idempotently, and sync workspace edition from checkout metadata.
See Payment Webhooks for event types and local testing notes.
Frontend
Dashboard billing: /dashboard/billing
- Provider status card (Mock / Stripe / Lemon Squeezy, configured vs missing config)
- Current plan card — static premium dark design (navy/black gradient); does not use theme accent color so text stays readable in all themes
- Available Plans — published plans loaded live from
GET /billing/plans; admin edits to price/name/features show after refresh - Billing history with View receipt links when URLs exist
- Friendly status messages for mock mode, missing config, demo restrictions, and redirect checkout
Admin panel:
- Admin → Plans — full plan CRUD with status badges, provider mapping indicators, archive/restore/delete confirmations
- Admin → Billing Settings — secret-safe provider status (configured booleans, checkout URLs, webhook status). Credentials are env-backed and read-only; secrets are never returned by the API
- Read-only demo admins can view plans but cannot create/edit/archive/delete
No billing secrets in frontend env vars.
Demo behavior
| Account | MOCK provider | STRIPE / Lemon provider |
|---|---|---|
| Demo user | Can mock checkout | Blocked from real checkout; UI stays visible |
| Test user | Mock checkout OK | Provider-controlled checkout |