
How I Implemented Stripe Subscriptions and Credit Top-Ups in Next.js 16
Yes: combine Stripe Checkout modes with strict metadata, verify webhook signatures, and make fulfillment idempotent in your database. In this codebase, subscriptions unlock dashboard access, while one-time payments purchase WhatsApp credits. A service-role Supabase client performs secure, server-only billing writes and updates.

Why this pattern works
- Subscription checkout and top-up checkout run through separate modes (
subscriptionvspayment), but share tenant metadata. - Webhook fulfillment uses signature verification plus database idempotency keys.
- Middleware reads subscription state to gate protected dashboard routes.
Core checkout creation pattern
const session = await stripe.checkout.sessions.create({
customer: customerId,
mode: "subscription",
line_items: [{ price: basePriceId, quantity: 1 }],
metadata: {
salonId: salon.id,
planType,
},
success_url: `${baseUrl}/dashboard/settings/billing?success=true&session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${baseUrl}/dashboard/settings/billing?canceled=true`,
});
const session = await stripe.checkout.sessions.create({
customer: customerId,
mode: "payment",
line_items: [
{
price_data: {
currency: "aed",
product_data: { name: `${credits} WhatsApp Credits` },
unit_amount: credits * CREDIT_PRICE_FILS,
},
quantity: 1,
},
],
metadata: { salonId: profile.salon_id, type: "credits", amount: credits.toString() },
});
Webhook idempotency guard
const idempotencyKey = `Stripe session ${session.id}`;
const { data: existing } = await serviceSupabase
.from("credit_transactions")
.select("id")
.eq("salon_id", salonId)
.eq("description", idempotencyKey)
.limit(1);
if (!existing || existing.length === 0) {
// fulfill once: update credits and write transaction
}
Practical lesson
If billing affects access control, treat checkout success and webhook completion as two related but separate events. This codebase uses webhook truth for final state and a post-checkout sync helper for immediate UX continuity.
Read the full implementation context in the case study: /case-studies/cooard-salon-platform