Billing (Stripe)
Stripe integration, subscriptions, and billing flows. This is the pack-billing-stripe pack.
What you need
- A Stripe account. Sign up at dashboard.stripe.com.
- Postgres and Valkey (subscription status is cached in Valkey, keyed by organization).
- Optionally Resend for receipt emails. If
RESEND_API_KEYis set, the pack sends receipts; otherwise it skips email.
Stripe credentials
From the Stripe dashboard, grab:
- Secret key (
STRIPE_SECRET_KEY) from API Keys. - Webhook signing secret (
STRIPE_WEBHOOK_SECRET) from Webhooks, or create one locally with the Stripe CLI (below). - Price IDs for the plans you sell (
STRIPE_PRICE_PRO,STRIPE_PRICE_PREMIERin the pack's env schema).
Data model
There is no Prisma "subscriptions" table in this pack. Billing state is org-scoped and stored in Valkey:
| Key pattern | Meaning |
|---|---|
stripe:org:{organizationId} | Stripe customer id for that organization |
stripe:customer:{customerId} | Cached subscription / billing status |
Checkout, portal, and webhook sync code live under apps/backend/src/features/billing/ and
apps/backend/src/libs/stripe/. Organization admins get MANAGE:BILLING through the pack's permission grants.
Checkout and portal flow
Subscribe an organization
Create checkout
The frontend calls the billing module. The backend creates a Stripe Checkout session for the org's customer (creating one if needed).
Customer pays in Stripe
Stripe hosts the checkout UI. Success returns to the pack's checkout-success route.
Webhook sync
POST /checkout/webhook verifies the signature, then syncs customer and subscription status into Valkey.
Customer portal
create-billing-portal opens Stripe's portal for plan changes and invoices against the same org customer.
Webhook endpoint
The pack exposes a Stripe webhook receiver at:
POST /checkout/webhookIn production, create a webhook endpoint in Stripe pointing at https://your-api-origin/checkout/webhook, subscribe to
the events the pack handles (checkout, subscription, customer), and copy the signing secret into
STRIPE_WEBHOOK_SECRET.
Local development with the Stripe CLI
Stripe cannot reach localhost over the internet, so use the Stripe CLI to forward events. Install it from
stripe.com/docs/stripe-cli, then:
stripe listen --forward-to localhost:3000/checkout/webhookThe CLI prints a whsec_... signing secret. Put it in STRIPE_WEBHOOK_SECRET.
Failure and retry notes
- Signature verification failures reject the webhook. Fix the secret before debugging business logic.
- Sync helpers update the Valkey cache from Stripe's customer / subscription objects. If the UI looks stale after a portal change, confirm the webhook events are arriving and the org → customer mapping exists.
- Receipt email is best-effort: without
RESEND_API_KEYthe send path returns early and checkout still succeeds.
Environment variables
| Variable | Purpose |
|---|---|
STRIPE_SECRET_KEY | Stripe API secret key |
STRIPE_WEBHOOK_SECRET | Signing secret for the webhook endpoint |
STRIPE_PRICE_PRO | Stripe Price ID for Pro |
STRIPE_PRICE_PREMIER | Stripe Price ID for Premier |
RESEND_API_KEY | Optional. Enables receipt emails |
In the admin
On top of a starter, this pack adds a revenue row to the admin app dashboard, a
billing card on the organization details page, and /admin/billing API routes. Nothing extra to configure beyond Stripe
env vars; the UI shows up once both packs are installed.
Related
- Organizations for the tenant boundary billing attaches to
- Mailing for receipt templates
- Going to production for webhook URL checklist
Every purchase and sponsorship funds my 8+ years of work on open source given freely to the community. It keeps the lights on, funds new packs, and keeps the ecosystem alive. Even a small tier means a lot. Thank you!
