2026-07-18FlareStarter Teamengineeringstripecloudflare
在 Cloudflare Workers 上实现幂等的 Stripe Webhook
重试、重放、乱序到达对 Stripe webhook 来说不是边缘情况,而是常态。这篇讲 FlareStarter Pro 让每条资金流都重放安全的模式。
Stripe 的投递语义是至少一次。超时重试、Dashboard 手动重发、事件乱序到达都是正常运行的一部分——所以一个只在理想路径上正确的 handler,迟早会在生产环境重复发放 credits 或重复激活订阅。
FlareStarter Pro 用一条规则对付它:webhook 能触发的每一次变更都带幂等 ref。
流水账模式
Credits 只经 append-only 流水移动,每笔入账的 ref 都从引发它的事件推导:
// 充值:用 checkout session id——同一 session 重发即空操作
await grantCredits(db, { orgId, amount, reason: 'topup', ref: `topup:${session.id}` })
// 订阅周期赠送:用 invoice id
await grantCredits(db, { orgId, amount, reason: 'grant', ref: `orgsub:${invoice.id}` })
grantCredits 依赖 ref 上的唯一索引插入流水行、吞掉冲突。重放是免费的——不需要分布式锁,也不需要一张要定期清理的「事件是否处理过」表。
扣减是条件更新,不是读改写
另一半是永远不要读出余额再写回去。扣减是一条 UPDATE ... WHERE balance >= amount 条件更新;影响零行即失败。两个并发请求不可能超扣,因为仲裁者是数据库而不是应用代码。
分支顺序是契约的一部分
一个端点接收所有事件,所以带 metadata 标记的流(org 订阅、credits 充值)必须在通用 billing 分支之前被拦截——上游 billing 会把每个 customer.subscription.* 事件当作个人套餐 upsert。分支顺序写进了仓库文档并由测试保障,因为这正是重构最容易悄悄破坏的那类不变量。
以上全部在 FlareStarter Pro 中接好并带测试——80+ 重放测试事件全部通过。