Billing & Subscriptions
ChimerAI integrates Stripe for subscription billing with pre-built plan management, checkout, customer portal, and webhook handling — all scaffolded and ready to go.
What you get
- Subscription plans — Free, Enterprise, Enterprise Pro (fully configurable)
- Stripe Checkout — hosted checkout sessions
- Customer Portal — self-service plan management & invoice download
- Webhook handler — processes
checkout.session.completed,customer.subscription.*,invoice.* - Billing API routes —
/api/billing/checkout,/api/billing/portal,/api/billing/webhook
Quick setup
npx chimerai add billing
Scaffolds:
| File | Purpose |
|---|---|
app/billing/page.tsx | Pricing page with plan overview and upgrade flow |
lib/stripe.ts | Stripe helper functions — no Stripe SDK, uses raw fetch |
app/api/billing/checkout/route.ts | Create Stripe Checkout Session |
app/api/billing/portal/route.ts | Create Stripe Customer Portal Session |
app/api/billing/subscription/route.ts | Read current subscription and credit balance |
app/api/webhooks/stripe/route.ts | Handle incoming Stripe webhook events |
No stripe npm package needed — lib/stripe.ts calls the Stripe REST API directly via fetch.
# Only dev dependency needed:
pnpm add -D @types/stripe
Environment variables
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
# Your Stripe Price IDs
STRIPE_PRICE_ENTERPRISE=price_...
STRIPE_PRICE_ENTERPRISE_PRO=price_...
lib/stripe.ts functions
All Stripe calls go through helper functions in lib/stripe.ts — no Stripe SDK import:
// Get (or create) a Stripe Customer for a user
const customerId = await getOrCreateStripeCustomer(userId, email);
// Start a checkout session — returns { id, url }
const { url } = await createCheckoutSession(
customerId,
'price_xxx',
`${origin}/billing?success=1`,
`${origin}/billing`
);
redirect(url);
// Open the self-service billing portal
const { url } = await createPortalSession(customerId, `${origin}/billing`);
// Verify a Stripe webhook signature (Web Crypto API — no SDK)
const valid = await verifyStripeSignature(rawBody, req.headers.get('stripe-signature'));
if (!valid) return new Response('Invalid signature', { status: 400 });
Handled Stripe webhook events
| Event | Action |
|---|---|
checkout.session.completed | Activates the subscription in the DB |
customer.subscription.updated | Updates status and period end date |
customer.subscription.deleted | Sets subscription status to canceled |
invoice.paid | Tops up the user’s credit balance |
invoice.payment_failed | Logs a warning |
Creating a checkout session
// app/api/billing/checkout/route.ts
import { getOrCreateStripeCustomer, createCheckoutSession } from '@/lib/stripe';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
export async function POST(req: Request) {
const session = await getServerSession(authOptions);
if (!session) return new Response('Unauthorized', { status: 401 });
const { priceId } = await req.json();
const customerId = await getOrCreateStripeCustomer(session.user!.id, session.user!.email!);
const { url } = await createCheckoutSession(
customerId,
priceId,
`${process.env.NEXTAUTH_URL}/billing?success=1`,
`${process.env.NEXTAUTH_URL}/billing`
);
return Response.json({ url });
}
// app/api/webhooks/stripe/route.ts
import { verifyStripeSignature } from '@/lib/stripe';
import { db } from '@/lib/db';
export async function POST(req: Request) {
const rawBody = await req.text();
const valid = await verifyStripeSignature(rawBody, req.headers.get('stripe-signature'));
if (!valid) return new Response('Invalid signature', { status: 400 });
const event = JSON.parse(rawBody);
if (event.type === 'customer.subscription.updated') {
const sub = event.data.object;
await db.subscription.upsert({
where: { stripeSubscriptionId: sub.id },
update: { status: sub.status, currentPeriodEnd: new Date(sub.current_period_end * 1000) },
create: { stripeSubscriptionId: sub.id, status: sub.status, userId: sub.metadata.userId },
});
}
return new Response('ok');
}
5-step Stripe setup
- Create Products & Prices in the Stripe Dashboard, copy the
price_xxxIDs - Register your webhook endpoint in Stripe Dashboard → Developers → Webhooks:
- URL:
https://yourdomain.com/api/webhooks/stripe - Events:
checkout.session.completed,customer.subscription.*,invoice.paid,invoice.payment_failed
- URL:
- Copy the Signing secret (
whsec_...) →STRIPE_WEBHOOK_SECRET - For local dev use the Stripe CLI:
stripe listen --forward-to localhost:3000/api/webhooks/stripe - Switch from
sk_test_/pk_test_to live keys when going to production
Further reading
- Billing Guide — complete reference with all
lib/stripe.tsfunction signatures - Stripe Docs
- ChimerAI Billing Package