Skip to main content

Feature deep-dive · SaaSForge Core

Stripe SaaS boilerplate for Next.js (subscriptions + credits)

Stripe SaaS boilerplate for Next.js (subscriptions + credits)

Stripe is the kind of integration that looks like a two-hour job in a tutorial and a two-week job in production. Webhook reliability, subscription state machines, customer portal sync, refund handling, and the difference between a `payment_succeeded` and a `subscription.updated` event are all real concerns. A Stripe boilerplate that handles them up front saves the sprint that would otherwise be spent learning Stripe the hard way.

SaaSForge Core, subscriptions for B2B SaaS

SaaSForge Core ships Stripe subscriptions with three tiers (Starter / Pro / Enterprise), monthly and yearly cycles, Stripe Checkout for new purchases, and Stripe Customer Portal for self-serve plan changes, cancellations, and payment method updates. Subscription state is mirrored into the workspace model so the app reads from its own database, not from Stripe on every request.

The webhook handler covers the lifecycle events that actually matter: `checkout.session.completed`, `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted`, and `invoice.payment_failed`. Each branch is documented so you know what to extend versus what to leave alone.

SaaSForge AI, credits layered on subscriptions

SaaSForge AI uses the same subscription substrate and adds a credit economy on top. Each plan has a monthly credit allotment; the subscription's renewal webhook resets the balance; product actions (chat turns, embeddings, image generations) debit credits in proportion to their real token cost. The credit balance lives in your Postgres, not in Stripe.

The two billing models compose: a customer subscribes to a plan (Stripe handles the money), and the credit ledger meters their usage against the plan's allotment (your database handles the operational view). Upgrading to a larger plan tops up credits immediately; cancelling keeps credits until period end.

Idempotency, the part that bites in production

Stripe retries webhooks on failure. The same `invoice.payment_succeeded` event can hit your endpoint two or three times in a few minutes. A naive handler that 'just adds the credits' will silently double-credit the customer. SaaSForge AI and Core both track event IDs in a `stripe_events` table and reject duplicates before any state mutation.

Idempotency keys are also used inside the application: each credit debit carries an `idempotency_key` (e.g., `chat:<turn_id>`), so a client retry on a flaky network does not double-debit a single chat turn. The pattern is uniform across the codebase, which is the only way to keep it honest.

Idempotent webhook handler skeleton
export async function POST(req: Request) {
  const event = await verifyStripeSignature(req);
  const seen = await db.stripeEvents.findUnique({
    where: { id: event.id },
  });
  if (seen) return Response.json({ ok: true });

  await db.$transaction(async (tx) => {
    await tx.stripeEvents.create({ data: { id: event.id, type: event.type } });
    await handleEvent(tx, event);
  });
  return Response.json({ ok: true });
}

Customer portal vs custom billing UI

The Stripe Customer Portal is the boring right answer for plan changes, payment methods, invoices, and cancellation. Both templates wire it up: a single endpoint generates a portal session and redirects the user. You skip building the billing UI Stripe already maintains.

Custom billing UI is reserved for things the portal cannot show: credit balance and consumption history on AI, workspace-scoped billing on Core. The split keeps your custom code small while the portal handles the high-stakes parts (payment method storage, invoice access, dispute prevention).

Frequently asked

Which template do I need for Stripe Checkout?
SaaSForge Core if you are building a B2B SaaS with tiered subscriptions. SaaSForge AI if your product is usage-based (chat turns, document processing). SaaSForge Starter does not ship Stripe, it is a content/landing template, but you can add Checkout on top using either Core's or AI's webhook pattern as a reference.
Does it support yearly billing and prorations?
Yes. Core's three tiers each have monthly and yearly prices, and Stripe handles the proration math for mid-cycle upgrades and downgrades automatically through the Customer Portal. The webhook handler picks up `customer.subscription.updated` and re-syncs the workspace's plan state.
What about EU VAT and international tax?
Both templates lean on Stripe Tax for VAT, GST, and sales tax handling. You enable it in the Stripe Dashboard and the Checkout session passes through the tax-collected amount; the template does not maintain its own tax rules. For Merchant-of-Record handling specifically, BoilerlyKit itself sells via Polar, that is a different decision for your own product.
Can I do one-time purchases instead of subscriptions?
Yes. The same webhook substrate handles `checkout.session.completed` for one-time payments, you just do not subscribe to the recurring lifecycle events. SaaSForge AI's credit-top-up pattern is essentially this: a one-time Checkout that adds credits via the same webhook handler.
What happens when a payment fails?
The `invoice.payment_failed` webhook downgrades the workspace's plan state and (for AI) freezes the credit balance. Stripe's smart retries kick in for the actual recovery; the application surfaces a banner asking the user to update their payment method via the Customer Portal. No custom dunning UI to maintain.
Ships in SaaSForge Core

See SaaSForge Core. Skip the deliberation.

Full source code. Lifetime updates. Polar Merchant-of-Record checkout. Private GitHub repo on purchase.