Stripe billing
Module id: stripe_billing · Kind: commerce · Configured by: a real STRIPE_SECRET_KEY and STRIPE_PRICE_PRO (not the placeholders) · Healthy when: STRIPE_WEBHOOK_SECRET is set as well
Stripe billing turns an installation into a paid service: subscription checkout for the Pro / Team / Business tiers, one-time top-ups that extend a customer's monthly AI budget, a customer billing portal, and webhooks that upgrade and downgrade accounts automatically. It is off on every fresh install — and off has a precise meaning.
The step-by-step guide lives in the main repository: docs/BILLING_SELFHOST.md. The commercial picture is on Hosting Platform. This page is the module view.
What it adds
- Checkout (
POST /api/v1/subscription/checkout) for the paid tiers, priced by your Stripe prices. - Top-ups (
/topup) — sell extra budget mid-month. - Billing portal (
/portal), cancel (/cancel) and sync (/sync) for customers to manage their subscription. - The Stripe webhook (
POST /api/v1/stripe/webhook) that applies subscription events to accounts. - The switch that makes tiers, quotas, cost budgets and upgrade prompts real — see below.
Without it — open-source mode
While Stripe is not configured the install runs in open-source mode: rate limits are unlimited for every user, storage is effectively unbounded, the cost-budget gate is never enforced, premium-only settings are open to every level, and every subscription route and upgrade prompt is hidden. A NEW user has the same feature set as an admin. This is deliberate and is what makes a self-hosted install trustworthy — you do not have to fight quotas you never set up.
When the module is absent and its gate is on, the five checkout/top-up/portal/cancel/sync routes answer 404 feature_not_configured. GET /api/v1/subscription/plans, /status and /budget are never gated (they also serve mobile in-app purchases), and the webhook keeps its own status codes for Stripe.
How to enable
Requirements: a public HTTPS domain (Stripe must reach the webhook, and Checkout redirects back to FRONTEND_URL) and a Stripe account in a supported country.
1. Products and prices in Stripe
Create one recurring product per tier you want to sell and copy each price id (price_…). Tiers you leave unset simply cannot be bought.
2. Credentials in the deployment
deploy/.env (or your platform's environment):
STRIPE_SECRET_KEY=sk_live_…
STRIPE_PUBLISHABLE_KEY=pk_live_…
STRIPE_WEBHOOK_SECRET=whsec_… # from step 3
STRIPE_PRICE_PRO=price_…
STRIPE_PRICE_TEAM=price_…
STRIPE_PRICE_BUSINESS=price_…
COST_BUDGET_GATE_ENABLED=true # enforce the per-tier monthly AI budgets
The placeholders from .env.example (sk_test_your_key_here, price_pro_monthly, …) count as not configured.
3. Register the webhook
Stripe Dashboard → Developers → Webhooks → endpoint https://<your-domain>/api/v1/stripe/webhook, subscribed to:
checkout.session.completed
customer.subscription.created
customer.subscription.updated
customer.subscription.deleted
customer.subscription.paused
customer.subscription.resumed
invoice.payment_succeeded
invoice.payment_failed
Copy the signing secret into STRIPE_WEBHOOK_SECRET. The endpoint is public and authenticated by that signature; a wrong secret means every event is rejected and paid subscriptions are never activated.
4. Restart and set your prices
Restart backend + worker. Then set displayed prices, monthly budgets and quotas per tier under Admin → Subscriptions, and your brand under Admin → Branding before the first payment.
Check it works
- Operate → Feature Status → Stripe billing:
- Available / Stripe billing configured — all good.
- Needs setup / Stripe configured but webhook secret missing — checkout would work, activation would not. Add
STRIPE_WEBHOOK_SECRET. - Not installed / Missing: STRIPE_SECRET_KEY, … or Stripe keys are placeholders — the module is off.
GET /api/v1/config/runtimereportsbilling.enabled: true;GET /api/v1/subscription/plansreportsstripeConfigured: true.- Send a test event from the Stripe dashboard and watch the backend log accept it before going live.
Configuration reference
| Variable | Default | Meaning |
|---|---|---|
STRIPE_SECRET_KEY |
placeholder | Secret API key. Real value required for configured. |
STRIPE_WEBHOOK_SECRET |
placeholder | Webhook signing secret. Required for healthy. |
STRIPE_PRICE_PRO |
placeholder | Price id of the Pro tier. Real value required for configured. |
STRIPE_PRICE_TEAM, STRIPE_PRICE_BUSINESS |
placeholder | Price ids of the other tiers; optional. |
STRIPE_PAYMENT_METHODS |
card,link,sepa_debit,klarna |
Payment method types offered at Checkout. Apple Pay / Google Pay come with card. |
STRIPE_AUTOMATIC_TAX |
false |
Let Stripe Tax compute VAT/GST and reverse-charge at Checkout (needs Stripe Tax activated and tax codes on products). |
Also read by the billing code but not module-owned: STRIPE_PUBLISHABLE_KEY, FRONTEND_URL, COST_BUDGET_GATE_ENABLED, the RATELIMITS_* config groups.
Troubleshooting
- Customer paid, account still on the free tier. The webhook did not arrive or was rejected: check the endpoint URL, the subscribed events, and that
STRIPE_WEBHOOK_SECRETmatches the endpoint's signing secret (each endpoint has its own). - Checkout redirects to the wrong host.
FRONTEND_URLmust be the public URL users see. - Amounts differ between the plans page and Stripe. The plans table drives what is displayed; Stripe's price drives what is charged. Keep them in sync under Admin → Subscriptions.
- Turning it off again. Remove the
STRIPE_*values (or set them back to placeholders) and restart. Existing subscriptions live on in Stripe — cancel or refund them there.
Related
- Hosting Platform — tiers, budgets, markup, selling AI capacity
- Mobile in-app purchases module — the store-side counterpart
- Feature modules — states, gates, the CLI