Store › Next.js + Stripe Starter

Next.js + Stripe Starter

A working payment path: Checkout, webhook signature verification, event idempotency, order state machine, email delivery. 17 files, no abstraction layer — you can read all of it before lunch.

$49 Buy now One-time licence · includes 1.x updates
Full refund within 7 days

By purchasing you agree to the Terms of Sale. These terms cover this store's digital goods only.

This isn't a tutorial

A tutorial tells you how to write it. This gives you the part that's already correct, so you only change the product and the delivery. Build and startup verified on Next.js 15.5 + Node 22 — not "should work".

The four traps it solves

Trap 1: reading the body with req.json()

Stripe signs the raw bytes. Re-serialize them — even just key ordering — and constructEvent throws No signatures found matching the expected signature. The fix is await req.text().

Trap 2: ignoring event retries

Stripe delivery is at-least-once. A response slower than 20 seconds, any non-2xx, or a network blip triggers redelivery — for up to 3 days. Without deduplication you ship the same order twice and send the same email twice.

Trap 3: concurrent deliveries double-shipping

Event dedup alone isn't enough. Different events (checkout.session.completed and async_payment_succeeded) can point at the same session. Delivery needs an atomic claim on order state: only paid can become delivered.

Trap 4: returning 200 when the handler failed

Return 200 after an exception and Stripe considers it delivered — no retry, order stuck at "paid but never delivered" forever. Return 500 so it retries, and mark the order failed so a human can pick it up.

Structure

stripe-starter/
├── app/
│   ├── page.tsx                 product page
│   ├── buy-button.tsx           client component
│   ├── success/page.tsx         polls delivery status after redirect
│   ├── cancel/page.tsx
│   └── api/
│       ├── checkout/route.ts    creates the Checkout Session
│       └── webhook/route.ts     verifies and processes events
├── lib/
│   ├── stripe.ts                client + product config
│   ├── orders.ts                storage, idempotency, state machine
│   └── email.ts                 delivery email
├── .env.example
└── package.json

Making it your product

PRODUCT_NAME=My Template
PRODUCT_PRICE_CENTS=4900     # cents, so 4900 = $49.00
PRODUCT_CURRENCY=usd

5 things before production

  1. Replace the JSON store (zero-dependency choice; breaks under multiple instances — swap for SQLite or Postgres, the three function signatures stay)
  2. Add the production webhook endpoint with three events
  3. Delete the debug GET handler in the webhook route
  4. Rate-limit /api/checkout
  5. Point NEXT_PUBLIC_SITE_URL at your domain

Known limitations

FAQ

What do I need?
Node 18+ (verified on 22), Next.js 15.5. Older versions may need dependency alignment.
How do I test webhooks locally?
stripe listen --forward-to localhost:3000/api/webhook, paste the whsec_... into env, restart.
Can I use it in client projects?
Yes. The licence covers use in work you deliver to clients. Resale and publishing the source are not permitted.

Full terms: Terms of Sale.