⚡ You're viewing a live demo of ChimerAI. Data resets daily at midnight UTC.Get the CLI →

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:

FilePurpose
app/billing/page.tsxPricing page with plan overview and upgrade flow
lib/stripe.tsStripe helper functions — no Stripe SDK, uses raw fetch
app/api/billing/checkout/route.tsCreate Stripe Checkout Session
app/api/billing/portal/route.tsCreate Stripe Customer Portal Session
app/api/billing/subscription/route.tsRead current subscription and credit balance
app/api/webhooks/stripe/route.tsHandle incoming Stripe webhook events

No stripe npm package neededlib/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.tsno 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

EventAction
checkout.session.completedActivates the subscription in the DB
customer.subscription.updatedUpdates status and period end date
customer.subscription.deletedSets subscription status to canceled
invoice.paidTops up the user’s credit balance
invoice.payment_failedLogs 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

  1. Create Products & Prices in the Stripe Dashboard, copy the price_xxx IDs
  2. 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
  3. Copy the Signing secret (whsec_...) → STRIPE_WEBHOOK_SECRET
  4. For local dev use the Stripe CLI: stripe listen --forward-to localhost:3000/api/webhooks/stripe
  5. Switch from sk_test_ / pk_test_ to live keys when going to production

Further reading

ChimerAI Docs · Back to Demo