Stripe Billing
Setup and manage Stripe billing in the template
This guide explains how to set up and manage Stripe billing for the application.
Prerequisites
- Stripe account (create one at stripe.com)
- Stripe API keys configured in
.env
Environment Variables
Add these to your .env file:
STRIPE_SECRET_KEY=sk_test_... # From Stripe Dashboard > Developers > API keys
STRIPE_WEBHOOK_SECRET=whsec_... # From webhook setup (see below)Initial Setup
1. Create Stripe Products
Run the fixtures script to create Free and Pro plans in Stripe:
bun run stripe:fixturesThis creates:
- Free Plan: $0/month, 3 members limit
- Pro Plan: $29/month or $290/year, unlimited members
2. Sync Products to Database
- Start your app:
bun run dev - Navigate to
/admin/configure-billing - Click "Sync from Stripe" to import products
Local Development
Webhook Testing
For local testing, use Stripe CLI to forward webhooks:
# Install Stripe CLI (if not already)
brew install stripe/stripe-cli/stripe
# Login to Stripe
stripe login
# Forward webhooks to local server (all events)
bun run stripe:listen
# Equivalent direct command:
# stripe listen --forward-to localhost:3000/api/auth/stripe/webhookStripe Dashboard Webhook Events
When configuring the endpoint in Stripe Dashboard, select these events:
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deleted
These are the subscription lifecycle events handled by @better-auth/stripe in this project.
Copy the webhook signing secret (whsec_...) and add it to your .env:
STRIPE_WEBHOOK_SECRET=whsec_...Testing Subscriptions
- Use Stripe's test card numbers:
- Success:
4242 4242 4242 4242 - Decline:
4000 0000 0000 0002
- Success:
- Test subscription flows at
/dashboard/billing(organization) or/settings/billing(user)
Architecture
Config Toggle
Billing is toggleable via src/config/app.ts:
Billing: {
ORGANIZATION_BILLING: true, // Enable billing for organizations
USER_BILLING: false, // Enable billing for individual users
get ENABLED() {
return this.ORGANIZATION_BILLING || this.USER_BILLING;
},
},When disabled:
- Navigation items are hidden
- Pages return
notFound() - RPC mutation endpoints throw
FORBIDDEN - Dashboard billing card is conditionally rendered
Plan Limits
Plans are stored in stripe_products table with limits in JSONB:
type PlanLimits = {
members: number; // -1 for unlimited
};Subscription Scopes
The system supports two subscription scopes:
- User subscriptions: Personal plans at
/settings/billing(controlled byConfig.Billing.USER_BILLING) - Organization subscriptions: Team plans at
/dashboard/billing(controlled byConfig.Billing.ORGANIZATION_BILLING)
Both use referenceId to associate subscriptions with either a user ID or organization ID.
Members Limit Enforcement
The members limit tracks how many members an organization (or user scope) can have. Usage checking is done via the checkUsageLimitsByReferenceId function.
Product Data Source
/admin/configure-billing reads products from the local stripe_products table (cache), not directly from Stripe APIs on each page load.
To refresh prices/products after changing them in Stripe Dashboard:
- Open
/admin/configure-billing - Click "Sync from Stripe"
Note: current webhook handling is subscription-focused and does not auto-sync Stripe product/price catalog changes.
Admin Management
Access /admin/configure-billing to:
- View all Stripe products
- Sync products from Stripe
- Monitor plan configuration
Customization
Adding New Plan Limits
- Update
PlanLimitstype insrc/features/subscriptions/schema.ts - Update
stripe_products.limitsJSONB structure - Add enforcement logic where needed
- Update Stripe product metadata
Changing Prices
- Update prices in Stripe Dashboard
- Sync products at
/admin/configure-billing - New prices apply to new subscriptions only
Troubleshooting
Products Not Showing
- Verify
STRIPE_SECRET_KEYis set correctly - Check that products exist in Stripe Dashboard
- Run sync from
/admin/configure-billing
Webhooks Not Working
- Verify
STRIPE_WEBHOOK_SECRETmatches CLI output - Ensure Stripe CLI is running and forwarding
- Check server logs for webhook errors
Limit Not Enforcing
- Verify subscription exists for the reference
- Check
stripe_products.limitshas correct values - Ensure sync was run after product changes
- Verify
Config.Billing.ENABLEDistrue