Skip to content

Billing & Subscriptions

FastSvelte handles subscription billing through Stripe's Customer Portal — no custom billing UI to build. Subscription state is webhook-driven: Stripe is the source of truth, and the backend updates the database only from webhook events sent to /webhooks/stripe. Three columns hold the link: organization.stripe_customer_id, plan.stripe_product_id, and organization_plan (status, period, subscription id).

Setup

1. API key

Create a Stripe account and a sandbox for development, then copy your Secret key (Developers → API Keys; sk_test_... in sandbox) into backend/.env:

FS_STRIPE_API_KEY=sk_test_YOUR_KEY_HERE

Only the secret key is needed — the portal handles all payment UI. Never commit it.

2. Create products

In Products → Add Product, create your tiers and copy each Product ID (prod_...):

  • Free tier — a single monthly price; must be $0 (auto-provisioned on first login).
  • Paid tiersmonthly and annual prices in the same currency.

No public free tier?

You still need a $0 default product for auto-provisioning — name it "Default" and simply don't add it to the portal, so users can't select it.

The kit seeds three plans (Free, Professional, Premium). At /admin/plans, edit each and paste its Stripe Product ID. The default plan (Free) must map to your $0 product — it's validated and auto-assigned to new users.

4. Configure the Customer Portal

In Settings → Billing → Customer portal: enable "customers can switch plans" and add the products/prices you want to offer; enable updating payment methods and viewing invoices, and optionally cancellation. Save.

Local development

Forward Stripe webhooks to your backend with the Stripe CLI:

stripe login
stripe listen --forward-to localhost:8000/webhooks/stripe

Copy the printed whsec_... into backend/.env, then restart the backend (keep stripe listen running in a second terminal):

FS_STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx

Testing

Use Stripe test cards in sandbox (any future expiry, any CVC and postal code):

Card Result
4242 4242 4242 4242 Success
4000 0000 0000 0002 Declined
4000 0025 0000 3155 3D Secure

Going live

In Developers → Webhooks, add an endpoint at https://api.yourdomain.com/webhooks/stripe subscribed to these events, and copy its signing secret to FS_STRIPE_WEBHOOK_SECRET:

  • customer.subscription.created / updated / deleted
  • checkout.session.completed — fulfills AI credit-pack purchases

Then switch to Live mode: recreate products with live pricing (free tier still $0), use live sk_live_... keys, update /admin/plans with the live Product IDs, configure the portal in live mode, and test a real upgrade.

Troubleshooting

Free subscription didn't sync (dev). If you logged in before stripe listen was running, the free subscription exists in Stripe but not your database ("No active plan found"). Fix it from /billingManage Subscription → in the portal click Cancel subscription, then Don't cancel — that fires customer.subscription.updated and syncs. (Or resend the original event from Stripe → Events.)

Plans & Usage · AI Usage & Credit Billing · Deployment