Billing & Payments Guide
This guide explains the Stripe billing integration that ships with ChimerAI and how to install it via the CLI.
Installation
chimerai add billing
This command installs the following files and configures your environment:
| File | Purpose |
|---|---|
app/billing/page.tsx | Pricing page with plan overview and upgrade flow |
lib/stripe.ts | Stripe helper functions (no SDK needed) |
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 |
Dependency installed automatically:
stripe ^14.0.0
Environment variables added to .env:
STRIPE_SECRET_KEY=sk_test_ # From https://dashboard.stripe.com/apikeys
STRIPE_WEBHOOK_SECRET=whsec_ # From Stripe Dashboard -> Webhooks
lib/stripe.ts
The Stripe library uses the raw Stripe REST API via fetch - no Stripe SDK needed at runtime. It exports the following functions:
getOrCreateStripeCustomer(userId, email)
Looks up an existing Stripe Customer ID on the user record. If none exists, creates a new Stripe customer and returns the ID.
const customerId = await getOrCreateStripeCustomer(session.user.id, session.user.email);
createCheckoutSession(customerId, priceId, successUrl, cancelUrl)
Creates a Stripe Checkout Session in subscription mode. Returns { id, url }. Redirect the user to url to complete payment.
const { url } = await createCheckoutSession(
customerId,
'price_xxx',
`${origin}/billing?success=1`,
`${origin}/billing?canceled=1`
);
redirect(url);
createPortalSession(customerId, returnUrl)
Creates a Stripe Customer Portal session where the user can manage or cancel their subscription. Returns { url }.
verifyStripeSignature(payload, signature)
Verifies a Stripe webhook signature using the Web Crypto API (no external dependency). Checks the HMAC-SHA256 signature and a 5-minute timestamp tolerance.
const valid = await verifyStripeSignature(rawBody, request.headers.get('stripe-signature'));
if (!valid) return new Response('Invalid signature', { status: 400 });
API Routes
POST /api/billing/checkout
Creates a Stripe Checkout Session and returns the redirect URL.
Request body:
{ "priceId": "price_xxx" }
Response:
{ "url": "https://checkout.stripe.com/..." }
POST /api/billing/portal
Creates a Stripe Customer Portal session.
Response:
{ "url": "https://billing.stripe.com/..." }
GET /api/billing/subscription
Returns the current user's subscription status and credit balance.
Response:
{
"plan": "Pro",
"status": "active",
"currentPeriodEnd": "2026-05-29T00:00:00.000Z",
"stripeSubscriptionId": "sub_xxx",
"credits": {
"current": 4800,
"limit": 5000,
"lifetimeUsed": 12400
}
}
POST /api/webhooks/stripe
Handles incoming Stripe events. Must be registered in the Stripe Dashboard as a webhook endpoint.
Handled 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 |
Billing Page
The page at /billing shows pricing plans and the current subscription status. It:
- Loads the current plan from
/api/billing/subscription - Redirects to Stripe Checkout when the user clicks an upgrade button
- Shows a "Manage subscription" button that redirects to the Stripe Customer Portal
- Handles
?success=1and?canceled=1query params after checkout
Setup Steps
- Create products and prices in the Stripe Dashboard
- Copy the price IDs (e.g.
price_xxx) into the billing page plan configuration - Register your webhook endpoint in Stripe Dashboard -> Webhooks:
- URL:
https://yourdomain.com/api/webhooks/stripe - Events:
checkout.session.completed,customer.subscription.updated,customer.subscription.deleted,invoice.paid,invoice.payment_failed
- URL:
- Copy the signing secret (
whsec_...) intoSTRIPE_WEBHOOK_SECRET - For local development, use the Stripe CLI:
stripe listen --forward-to localhost:3000/api/webhooks/stripe
Notes
- The Stripe library uses
fetchdirectly against the Stripe REST API. There is nostripenpm package import in the generated code, which reduces bundle size. - The webhook handler requires the raw request body for signature verification. Next.js provides this by default in Route Handlers.
- Credit balances are only meaningful if your Prisma schema includes a
CreditBalancemodel. The subscription route handles missing models gracefully and returns default values. - Test with Stripe's test mode keys (
sk_test_,pk_test_) before switching to live keys.