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 StatusStripe 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/runtime reports billing.enabled: true; GET /api/v1/subscription/plans reports stripeConfigured: 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_SECRET matches the endpoint's signing secret (each endpoint has its own).
  • Checkout redirects to the wrong host. FRONTEND_URL must 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