Subscription billing is the revenue engine behind every SaaS product, but getting it right with Next.js requires more than just dropping in a Stripe checkout. You need webhook idempotency, plan management, usage metering, and graceful upgrade/downgrade flows — all while keeping your Next.js app responsive and your billing state consistent.
This tutorial walks through building a production subscription billing system using Next.js 14 App Router, Stripe, and Prisma. We'll cover everything from initial Stripe setup to handling edge cases like failed payments and mid-cycle plan changes.
Project Setup and Dependencies
Start with a Next.js project and install the billing dependencies:
Configure your environment variables:
Database Schema for Billing
Design your Prisma schema to mirror Stripe's subscription model:
The WebhookEvent table is critical — it provides idempotency for webhook processing. Without it, retried webhooks can double-process state changes.
Stripe Client Configuration
Create a shared Stripe instance with proper configuration:
Checkout Session API Route
Build the checkout endpoint that creates Stripe sessions:
Webhook Handler with Idempotency
The webhook handler is the most critical piece. Every event must be processed exactly once:
Subscription Event Handlers
Each webhook event needs a dedicated handler with proper error handling:
Plan Change (Upgrade/Downgrade) API
Handling mid-cycle plan changes requires proration logic:
Need a second opinion on your saas engineering architecture?
I run free 30-minute strategy calls for engineering teams tackling this exact problem.
Book a Free CallSubscription Cancellation Flow
Implement a cancellation flow that retains customers until the period ends:
Pricing Page Component
Build a pricing page that handles plan selection and checkout:
Billing Dashboard
Create a dashboard where users manage their subscription:
Feature Gating Middleware
Enforce plan limits across your application:
Usage in an API route:
Usage Metering for Pay-As-You-Go
For API call metering, report usage to Stripe:
Testing the Billing Flow
Use Stripe's test cards and CLI for local development:
Write integration tests for the webhook handler:
Production Checklist
Before going live, verify these critical items:
- Webhook endpoint registered in Stripe Dashboard with all required events
- Idempotency confirmed — replay webhooks and verify no duplicate state changes
- Error monitoring set up for failed webhook processing (Sentry, Datadog)
- Stripe CLI used to test all event types locally before deploy
- Customer portal configured in Stripe for self-service payment method updates
- Dunning emails enabled in Stripe settings for failed payment retry
- Plan migration path tested — upgrade, downgrade, cancel, reactivate
- Proration verified with real test subscriptions (not just mock data)
- Database indexes on
stripe_customer_id,stripe_subscription_id,stripe_event_id - Rate limiting on checkout endpoint to prevent abuse