跳转到内容

Webhook 与会员状态:支付成功后发生什么

收款商业化 · 经验分享 · 2-3 小时

不靠“支付成功”页面开通权限,而是验证平台通知、去重、不依赖到达顺序,把订单、订阅和权益分开管理。

已核验 2026-10-24 · 下次复核 2026-11-23

2026-10-24 重新阅读 Stripe webhook 与订阅 webhook 官方文档:原始请求体验签、尽快返回 2xx、重复投递与不保证顺序、正式环境最长约 3 天重试,以及 invoice.paid 后确认订阅为 active 再延长权限;未执行真实订单,也不代替其他支付平台规范。

查看本次核验来源
Webhook 与会员状态:支付成功后发生什么 配图
学习结果
写出一个可靠的支付通知处理流程,列出必须处理的事件和订阅状态对应的权限规则,并完成重复、乱序和退款三种情况的测试。
时长
2-3 小时
难度
经验分享
核验
已核验 2026-10-24 · 下次复核 2026-11-23

正文

用户付完钱,浏览器跳回你的“支付成功”页面,于是开通会员。这个做法看起来没问题,但并不可靠:用户可能在跳转前关掉了浏览器;有些支付方式要过一段时间才确认到账;“成功页”的网址还可能被人直接访问。

可靠的做法是:以支付平台发给你服务器的通知为准。这种通知叫 webhook:交易发生变化时,平台主动向你的服务器发送一条消息。浏览器的跳转只用来给用户展示结果,开通权限由服务器根据可信的通知来决定。

这篇以 Stripe 为例讲清楚怎么处理这些通知。其他平台的事件名和签名方式不同,但要解决的问题是一样的。

一条可靠的处理流程

从收到通知到开通权益

  1. 验证签名:确认这条通知真的来自支付平台。
  2. 记录事件:把事件 ID 写进数据库,用来去重。
  3. 尽快返回成功:告诉平台“收到了”,不要在这一步做耗时的事。
  4. 异步处理:把事件放进队列,慢慢处理。
  5. 读取最新状态:向平台查询这笔订单或订阅当前的真实状态。
  6. 更新订单和权益:根据最新状态更新你自己的记录。

下面一步一步说明为什么要这样做。

验证签名:用原始请求体

没有验证的话,任何人都可以伪造一条“支付成功”的通知,骗你的系统开通权限。Stripe 会在每条通知的请求头里带上签名,你用平台给你的签名密钥来验证。

最容易踩的坑是:验证签名必须使用原始的请求体。很多框架会自动把请求体解析成 JSON,再验证就会失败。Stripe 的官方库默认允许签名时间和当前时间相差 5 分钟,用来防止旧通知被重放。测试环境和正式环境的密钥不同,要分别配置。验证失败的通知直接拒绝,日志里不要打印签名密钥和完整的客户信息。

去重:同一个事件可能来好几次

平台可能会把同一条事件发送不止一次。如果每收到一次就开通一次会员、发一封邮件,用户就会收到重复的邮件,你的数据也会乱。

做法是:把处理过的事件 ID 存下来,并设为唯一;收到已经处理过的事件 ID,就直接跳过。Stripe 的文档还提到,有时同一个对象的变化会生成两个不同的事件,这时可以用“对象 ID 加事件类型”来判断是否重复。

更新订单、更新权益、标记事件已处理,这几步最好放在同一个数据库事务里完成。否则可能出现这种情况:事件被标记为已处理,但更新失败了,之后平台重试时又被跳过,用户付了钱却一直没开通。

不要依赖事件到达的顺序

Stripe 明确说明,不保证按事件发生的顺序投递。比如创建一个订阅,可能依次产生订阅创建、账单创建、账单支付成功等多个事件,但到达你服务器的顺序可能是乱的。官方还提醒,不要用事件里的创建时间来判断先后顺序。

所以不能简单地“以最后收到的事件为准”。否则一条迟到的“支付成功”通知,可能把已经退款的订单又改回已支付。更稳妥的做法是:收到事件后,向平台查询这个订单或订阅当前的状态,以平台返回的最新事实为准。

尽快返回,失败会重试

收到通知后,要在做任何复杂操作之前尽快返回成功(2xx 状态码)。如果你在返回之前先去更新会计系统、发邮件,很容易超时,平台就会认为投递失败。

投递失败后,Stripe 在正式环境中会用逐渐拉长的间隔重试,最长持续约 3 天;测试环境只在几小时内重试 3 次。所以短暂的故障通常不会丢通知,但你要在后台能看到哪些事件一直没有处理成功。

订单、订阅和权益,分开管理

订单、订阅和权益各管各的

这是最容易混在一起的地方。三种记录各有自己的生命周期:

  • 订单:一次性购买的记录,状态是已支付或已退款。
  • 订阅:周期性付费的关系,有试用、正常、逾期、取消等状态。
  • 权益:用户现在能用什么、能用到什么时候。

Stripe 推荐的做法是:在你的系统里给每个用户保存一个权益到期时间,用户登录时检查这个时间是否还没到。订阅续费成功时,收到 invoice.paid 事件后,先查询订阅,确认它的状态是 active,再延长到期时间。

订阅状态和权限的对应关系,可以参考下表:

订阅状态 含义 你应该做什么
trialing 试用期 可以开通
active 状态正常 开通,按周期延长到期时间
incomplete 首次付款未完成 暂不开通,提示用户完成支付
past_due 续费扣款失败或未扣款 通知用户更新支付方式
canceled 已取消 收回权限
unpaid 多次重试后仍未付款(取决于你的账单设置) 收回权限

除了订阅事件,还要处理退款(charge.refunded)和争议(charge.dispute.created)。这些规则也要和你的服务条款一致:用户取消自动续费,通常可以用到当期结束;部分退款和争议,不一定等于立刻封禁账号。

Stripe 订阅 webhook 文档

图:Stripe“将 webhooks 用于订阅”官方文档页面截图,截取于 2026-10-24,内容以官方当前页面为准。

对账和人工补单

再可靠的流程也会有漏网之鱼。每天对比一次支付平台的交易记录和你的订单记录,发现不一致的地方就处理。

用户说“付了钱没开通”时,先在平台后台查到这笔交易,确认已支付,再手工补开权益,并记录操作人、时间和依据。不要因为某个事件处理失败,就给所有相关用户无条件开通。

学习步骤

  1. 在测试环境配置 webhook 地址,用平台的命令行工具把事件转发到本地。
  2. 实现签名验证,确认使用的是原始请求体。
  3. 建一张事件表,事件 ID 设为唯一,实现去重。
  4. 收到事件后先返回成功,把处理逻辑放到异步任务里。
  5. 处理时查询订单或订阅的最新状态,再更新订单和权益到期时间。
  6. 测试重复投递、乱序到达和退款三种情况,并写一份对账和人工补单流程。

场景拆解

一个“付了钱没开通”的工单

小周的工时工具上线订阅后,收到一位用户的邮件:昨天付了年费,到现在还是免费版。

  1. 查平台:在 Stripe 后台找到这笔交易,状态是已支付。
  2. 查事件:在 webhook 的投递记录里,看到 invoice.paid 事件投递了 4 次,都返回了 500 错误。
  3. 查日志:服务器日志显示签名验证失败。原因是前一天升级框架后,请求体在验证前被自动解析成了 JSON。
  4. 修复:改为读取原始请求体再验证。修复后,平台的下一次重试成功,系统查询到订阅为 active,自动为这位用户延长了权益。
  5. 补救:检查同一时间段内还有没有其他失败的事件,确认都已自动重试成功;给用户回邮件说明原因,并赠送一个月。

这次没有丢单,靠的是平台约 3 天的重试,以及他能看到事件投递记录。之后他加了一条告警:webhook 连续失败超过 3 次就通知他。

补充材料

思维导图、练习、清单和参考资料可按需展开。

01

思维导图和离线文件

适合读完后下载到自己的知识库里继续整理。

可靠的处理流程

  • 验证签名,用原始请求体
  • 记录事件 ID 去重
  • 尽快返回成功
  • 查询最新状态再更新

三种记录分开管

  • 订单:已支付或已退款
  • 订阅:试用、正常、逾期、取消
  • 权益:用户能用到什么时候

出错时

  • 平台会重试,但要看得到失败
  • 每天对账
  • 人工补单要有依据和记录

Webhook 与会员状态:支付成功后发生什么 思维导图

02

实践练习

根据文章内容记录自己的判断与依据。

在测试环境跑通完整流程

用 Stripe 的测试环境(或你所用平台的沙盒)实现并测试一个订阅的支付通知处理。

准备材料

  • 一个测试环境的支付平台账号。
  • 平台的命令行工具,用来转发和触发测试事件。
  • 一个能保存订单、事件和权益的数据库。

练习步骤

01 / 接收并验证通知

时间: 40 分钟

操作:

  1. 配置 webhook 地址,把测试事件转发到本地。
  2. 实现签名验证,用一条篡改过的请求确认会被拒绝。

产出: 一个能验证签名的接收端点。

检查: 使用原始请求体验证;篡改后的请求被拒绝。

02 / 去重和异步处理

时间: 40 分钟

操作:

  1. 记录事件 ID 并设为唯一,收到后先返回成功。
  2. 重复发送同一个事件,确认只处理一次。

产出: 事件表和去重逻辑。

检查: 重复事件不会导致重复开通或重复发邮件。

03 / 状态和权益

时间: 40 分钟

操作:

  1. 处理时查询订阅最新状态,按状态表更新权益到期时间。
  2. 测试退款和取消,确认权益按规则变化。

产出: 一份测试记录。

检查: 先到达的旧事件不会覆盖已经退款的状态。

完成检查

  • 权限只由服务器验证过的通知决定。
  • 重复和乱序的事件都能正确处理。
  • 有对账和人工补单的流程。
03

清单、模板和关键词

用于项目记录和自查。

必须处理的事件(以 Stripe 订阅为例)

  • checkout.session.completed:用户完成结账
  • invoice.paid:账单支付成功,确认订阅为 active 后延长权益
  • invoice.payment_failed:扣款失败,通知用户
  • customer.subscription.updated:订阅状态或套餐变化
  • customer.subscription.deleted:订阅结束,收回权限
  • charge.refunded:退款
  • charge.dispute.created:争议或拒付

事件表字段

事件 ID(唯一)、事件类型、关联的订单或订阅 ID、收到时间、处理状态(待处理、成功、失败)、失败原因、重试次数、处理完成时间。

人工补单记录

用户与订单:
平台交易号与状态(截图或链接):
问题原因:
补开的权益与到期时间:
操作人与时间:
是否已通知用户:

关键概念

  • Webhook:支付平台主动发送给服务器的事件通知
  • 签名验证:确认通知来自平台,需要原始请求体
  • 幂等:同一个事件处理多次,结果和处理一次相同
  • 乱序:事件到达顺序和发生顺序不一致
  • 权益到期时间:决定用户能否使用的依据
  • 对账:对比平台交易和本地记录

容易误判

  • 根据浏览器的“支付成功”页面开通权限
  • 框架自动解析请求体,导致签名验证失败
  • 不记录事件 ID,重复开通、重复发邮件
  • 以最后收到的事件为准,被旧事件覆盖状态
  • 在返回成功之前做耗时操作,导致超时
  • 收到 invoice.paid 不检查订阅状态就延长权益
04

工具和参考资料

本篇涉及的工具和参考链接。

可参考工具

  • Stripe CLI
  • Stripe Billing
  • Paddle Webhooks
  • Lemon Squeezy Webhooks

参考资料