商店 › Next.js + Stripe Starter

Next.js + Stripe Starter

一个能直接跑的收款链路:Checkout、webhook 签名校验、事件幂等、订单状态机、邮件交付。 17 个文件,没有抽象层,改起来不用先读懂一个框架。

$49 立即购买 买断自用 · 含 1.x 版本更新
7 天内不满意可全额退款

点击购买即表示你已阅读并同意 《购买条款》。 条款只适用于本商店的数字商品。

这不是教程

教程告诉你怎么写;这个是把已经写对的部分给你,你只需要改商品和交付。 代码在 Next.js 15.5 + Node 22 上验证过构建和启动,不是「理论上应该能跑」。

它替你解决的四个坑

坑 1:webhook 用 req.json() 拿 body

Stripe 的签名是对原始字节算的。一旦你重新序列化(哪怕只是键序变了), constructEvent 一定抛 No signatures found matching the expected signature。 正确写法是 await req.text()。

坑 2:不处理事件重投

Stripe 的投递是「至少一次」语义。接口超过 20 秒未响应、返回非 2xx、或网络抖动都会重投同一事件, 最长重投 3 天。不去重就会重复发货、重复发邮件。必须按 event.id 去重。

坑 3:并发投递导致重复交付

只做事件去重不够。两个不同的 event(如 checkout.session.completed 和 async_payment_succeeded)可能指向同一个 session。 交付前必须基于订单状态做原子占位:只有 paid 能转 delivered。

坑 4:处理失败却返回 200

抛了错但返回 200,Stripe 认为投递成功,不会重投——订单永久卡在「已付款未发货」。 正确做法是返回 500 让 Stripe 重投,同时把订单标记为 failed 供人工补。

目录结构

stripe-starter/
├── app/
│   ├── page.tsx                 商品页
│   ├── buy-button.tsx           购买按钮(客户端组件)
│   ├── success/page.tsx         付款成功后轮询交付状态
│   ├── success/success-content.tsx
│   ├── cancel/page.tsx          取消页
│   └── api/
│       ├── checkout/route.ts    创建 Checkout Session
│       └── webhook/route.ts     接收并处理 Stripe 事件
├── lib/
│   ├── stripe.ts                Stripe 客户端 + 商品配置
│   ├── orders.ts                订单存储 + 幂等 + 状态机
│   └── email.ts                 交付邮件
├── .env.example
└── package.json

改成你自己的商品

只改环境变量,不用碰代码:

PRODUCT_NAME=我的模板
PRODUCT_PRICE_CENTS=4900     # 注意是「分」,4900 = $49.00
PRODUCT_CURRENCY=usd

上生产前必须做的 5 件事

  1. 换掉 JSON 文件存储(为了零依赖用了 JSON + 串行队列,多实例会丢数据,换 SQLite 或 Postgres,三个函数接口不变)
  2. 配生产 webhook 端点,勾选三个事件
  3. 删掉 webhook 里给本地排查用的 GET 处理器
  4. 给 /api/checkout 加 rate limit
  5. 确认 NEXT_PUBLIC_SITE_URL 是生产域名

已知限制

常见问题

需要什么环境?
Node 18 以上(验证用的是 Node 22),Next.js 15.5。更老的版本可能要对齐依赖。
本地怎么测 webhook?
用 Stripe CLI:stripe listen --forward-to localhost:3000/api/webhook,把打印的 whsec_... 填进 env 后重启。
可以用在客户项目里吗?
可以。买断自用授权包括在客户交付项目中使用。不可转售或公开发布源码。

完整条款见 购买条款。