Billing
The template includes a Stripe integration for subscription billing. It is disabled by default — enable it in saas.config.ts and add your Stripe environment variables to activate.
Enabling Billing
In saas.config.ts, set billing.enabled to true and add your plans:
billing: {
enabled: true,
plans: [
{ id: "pro", name: "Pro", priceId: "price_xxx", enabled: true },
{ id: "team", name: "Team", priceId: "price_yyy", enabled: true },
],
},
Each plan’s priceId must match a recurring price ID from your Stripe dashboard.
Checkout Flow
Call createCheckoutSession(planId) from a server action or API route to redirect users to Stripe-hosted checkout:
import { createCheckoutSession } from "@/lib/billing/actions";
const { url, error } = await createCheckoutSession("pro");
if (url) redirect(url);
On success, Stripe redirects to /settings/billing?success=true. On cancellation, to /settings/billing?canceled=true.
Customer Portal
Let existing subscribers manage their subscription (cancel, change plan, update payment method) via the Stripe Customer Portal:
import { createPortalSession } from "@/lib/billing/actions";
const { url } = await createPortalSession(stripeCustomerId);
if (url) redirect(url);
Webhook Handler
Stripe webhook events are handled at POST /api/billing/webhook. The handler in lib/billing/webhooks.ts verifies the signature and processes these events:
| Event | Default behavior |
|---|
checkout.session.completed | Log + TODO: upsert subscription record |
customer.subscription.updated | Log + TODO: update subscription status |
customer.subscription.deleted | Log + TODO: mark subscription canceled |
invoice.payment_failed | Log + TODO: notify user |
Extend handleWebhookEvent() in lib/billing/webhooks.ts to write subscription state to your database.
Always verify the webhook signature using constructWebhookEvent(). Never trust unverified webhook payloads.
Registering the Webhook in Stripe
In the Stripe dashboard, add a webhook endpoint pointing to https://your-domain.com/api/billing/webhook and subscribe to at minimum:
checkout.session.completed
customer.subscription.updated
customer.subscription.deleted
invoice.payment_failed
Copy the Signing Secret into STRIPE_WEBHOOK_SECRET.
Environment Variables
| Variable | Required | Description |
|---|
STRIPE_SECRET_KEY | Yes | Stripe secret key (sk_live_... or sk_test_...) |
STRIPE_WEBHOOK_SECRET | Yes | Webhook endpoint signing secret (whsec_...) |
NEXT_PUBLIC_APP_URL | Yes | Base URL for Stripe redirect URLs |