首页 / 内容指南 / 当前文章

Stripe Checkout Session expired:恢复链接、弃购召回与重复订单排查

发布于 2026-08-26 · deliwaimao.cn 编辑部

用户打开 Stripe Checkout 后看到“Session expired”,运营常会立即重新生成支付链接。但如果系统没有区分旧 Session、恢复 Session 和新订单,就可能出现库存重复占用、优惠重复使用、客户付了两次,或旧购物车价格覆盖当前价格。

本文目录(17 节)

先确认 Session 的真实状态

不要只根据前端文案或页面 URL 判断。服务端使用自己的 Stripe 密钥检索 Checkout Session,核对 statuspayment_statusexpires_atlivemode、客户、金额、币种与内部订单引用。

status=expired 表示该 Session 已不能继续完成 Checkout。若 Session 仍为 open,则应继续排查链接环境、账户、代理缓存或前端错误。若已经 complete,不要再生成替代链接,应进入支付和履约核对。

expires_at 的有效范围

Stripe 创建 Checkout Session 的 API 允许设置 expires_at,其时间位于创建后 30 分钟至 24 小时之间,默认是创建后 24 小时。应用不能把 Checkout Session 当永久购物车使用。

保存 Stripe 返回的真实 expires_at,并在自己页面上显示合理的有效期。不要只保存本地预计时间,因为时区转换、队列延迟或重试可能造成偏差。所有比较应使用明确的 Unix 时间或 UTC。

手动 expire 只适用于 open Session

业务取消订单、库存超时或报价失效时,可以调用 expire 接口使仍为 open 的 Session 过期。官方接口说明,过期后客户不能完成该 Session;若它已经过期或不在可过期状态,请求会返回错误。

因此取消流程必须先读权威状态,并将“本地取消”和“Stripe 已过期”作为可重复执行的状态转换。不要在 Webhook 和后台任务中无限重试一个已经 complete 的 Session,也不要因为 expire 返回冲突就擅自撤销已经成功的支付。

创建时启用 after_expiration.recovery

若业务需要召回弃购,在创建 Checkout Session 时配置 after_expiration.recovery.enabled=true。Stripe 会在该 Session 过期后生成 recovery URL,并把它附到过期 Session 对象的恢复信息中。

恢复链接不是把原 Session 重新打开,而是帮助客户创建并进入一个可继续付款的 Checkout 流程。应用要保存旧 Session ID 与恢复后 Session 的关系,不能把两者视为同一 Stripe 对象。

recovery URL 什么时候可用

只有 Session 实际过期且预先启用了恢复功能后,才应期待 recovery URL。创建成功后立刻读取为空,不一定表示配置失败。处理 checkout.session.expired 事件时,应使用经过签名验证的事件,并按需要从 Stripe API 重新检索对象。

不要自行拼接 cs_... 生成所谓恢复地址,也不要把 Dashboard 或测试环境 URL 发给生产客户。记录 livemode 和 Stripe account,确保链接、密钥与 Webhook 属于相同环境。

使用 checkout.session.expired 做弃购信号

Stripe 的弃购恢复指南建议监听 checkout.session.expired。收到后先验证签名,再以 Session ID 和事件 ID 做幂等去重,确认该内部订单仍允许召回,最后才进入邮件或消息队列。

Webhook 只表示 Stripe Session 的状态事件,不代表用户同意营销。发送召回信息仍要符合用户授权、退订和地区法规。日志中不要保存完整支付页面 URL、客户敏感信息或签名密钥。

防止召回邮件重复发送

Stripe Webhook 会重试,运营也可能手动重发事件,因此同一个 expired 事件可能被处理多次。数据库应对 event_id 建唯一约束,并为“订单 + 原 Session”的召回任务设置唯一键。

先提交幂等状态,再异步发送邮件。若邮件服务超时,使用同一个任务 ID 重试,不要创建第二条召回任务。记录发送状态、模板版本和时间,但避免把 recovery URL 长期暴露在普通日志中。

旧订单与恢复 Session 的映射

创建 Session 时可用 client_reference_id 对应内部购物车或订单,也可通过受控 metadata 保存非敏感标识。恢复后必须确认 recovered_from 或业务映射指向正确旧 Session,并继续关联同一购物意图。

内部订单不应只用 last_checkout_session_id 覆盖。至少保存原 Session、当前 Session、恢复链、每个状态与 PaymentIntent。这样并发点击两封旧邮件时,系统才能选定哪个付款有效并阻止重复履约。

价格、库存与优惠必须重新确认

恢复链接可能对应客户早先看到的购物车条件。业务在发送前应判断商品是否仍可售、报价是否仍有效、币种和税务配置是否变化。不要在邮件中承诺已经失效的价格或库存。

after_expiration.recovery.allow_promotion_codes 控制恢复流程是否允许促销码,但它不替代内部优惠资格校验。一次性优惠、限购与库存占用应以服务端当前规则为准,并在最终履约前再次核对。

不要把 success_url 当付款凭证

用户可能完成付款后关闭页面,也可能伪造访问 success URL。Stripe 官方履约文档明确要求使用 Webhook 确保每笔付款都能履约,重定向只用于让客户尽快看到结果。

履约函数应按 Checkout Session ID 多次、并发调用仍安全,检索 Session 并检查 payment_status。对于延迟支付方式,checkout.session.completed 时资金可能尚未最终成功,还应处理异步支付成功或失败事件。

避免原订单和恢复订单双重履约

极端情况下,状态同步延迟、并发创建或多条恢复路径可能让同一内部订单关联多个支付对象。履约事务应以内部订单为锁,写入 Stripe Session、PaymentIntent 与履约结果的唯一关系。

若订单已经履约,再收到另一 Session 的成功事件,不能简单返回成功并忽略资金。应进入异常支付队列,核对是否重复扣款并按业务规则退款。自动退款也要有幂等键和审计记录,不能在 Webhook 内盲目执行。

Webhook 处理要快速返回

Stripe 文档说明,生产 Webhook 投递失败时会自动重试;超时、3xx、4xx 和 5xx 都会造成失败记录。端点应验证签名、持久化事件并快速返回 2xx,把邮件、库存和订单逻辑放到可靠队列。

不要返回 302 到登录页,也不要让防火墙把 Stripe POST 变成 HTML 挑战。通过 Workbench 的 Event deliveries 核对 Delivered、Pending、Failed、HTTP 状态和下一次重试时间。

推荐排查顺序

  1. 从服务端检索报错的 Checkout Session,确认环境、状态与 expires_at
  2. 核对创建请求是否启用 after_expiration.recovery
  3. 检查 checkout.session.expired 是否已到达正确 Webhook endpoint。
  4. 验证签名、事件去重和恢复邮件任务的唯一约束。
  5. 检查 recovery URL 是否来自 Stripe 对象而非自行拼接。
  6. 核对恢复 Session 与原 Session、内部订单的映射。
  7. 重新验证库存、报价、税务和优惠资格。
  8. 用付款 Webhook 而非 success URL 驱动幂等履约。
  9. 模拟重复事件、并发点击和延迟支付,确认不会重复发货。
  10. 在测试环境验证过期、恢复、付款和异常退款完整链路。

常见错误

不要延长或伪造已经过期的 Session URL;不要在每次页面刷新时创建新 Session;不要把 recovery URL 明文写入长期日志;不要仅凭 checkout.session.expired 就认定客户永远不会付款;也不要把 Webhook 的至少一次投递误当成只会调用一次。

常见问题

Checkout Session 默认多久过期?

创建 API 当前说明默认在创建后 24 小时过期;自定义 expires_at 必须在创建后 30 分钟至 24 小时范围内。应以当前 Stripe API 文档和返回对象为准。

已过期的 Session 能重新打开吗?

不能继续使用原 Session 完成付款。若创建时启用了恢复功能,可使用 Stripe 在过期后提供的 recovery URL 进入恢复流程。

为什么 after_expiration 已启用但立即看不到 recovery URL?

恢复 URL 在 Session 过期后生成。先确认状态确实是 expired,再从经过验证的事件或 API 检索结果读取。

收到 expired 事件就可以释放库存吗?

这取决于内部订单和库存策略。释放动作必须幂等,并考虑是否还有其他有效 Session 或支付已完成,不能仅凭单一事件无条件修改。

客户回到 success_url 是否表示已经付款?

不表示。应由服务端检索 Session,并通过支付相关 Webhook 和 payment_status 驱动履约;success URL 只用于用户体验。

总结

Stripe Checkout 过期恢复的关键是把旧 Session、recovery URL、新 Session 和内部订单建成可审计的状态链。创建时配置恢复、用 expired Webhook 触发合规召回、按当前库存和价格复核,并让付款履约保持幂等,才能既挽回弃购又避免重复订单与重复扣款。

来源资料