Stripe Payout 显示 paid 后变 failed:银行未到账与失败码排查
Stripe 后台或 API 一度显示 payout 为 paid,商户却没收到银行款项;几天后状态又变成 failed,或者系统只处理了 payout.paid 就把结算标记为不可逆完成。Payout 的 paid 表示款项预期可在目标账户使用,但银行仍可能随后退回。Stripe 官方文档明确说明,部分失败 payout 会先显示 paid,再在之后变为 failed,因此必须持续接收 webhook,并以对象最新状态、失败码和余额冲正完成闭环。
本文目录(22 节)
先确认 Payout 对象与模式
记录 po_... ID、账户或 Connected Account、livemode、金额、币种、创建时间、arrival_date、destination、method、automatic 和 type。不要只凭 Dashboard 截图或内部提现单号排查,也不要把 PaymentIntent、Charge、Transfer 与 Payout 混为同一对象。
确认使用正确平台/Connected Account 上下文和 live/test 模式。平台查询自己账户的 payout,无法代替查询某个 Connected Account 的 payout;测试 payout 不会实际进入银行。
理解状态流转
Stripe Payout 对象状态包括 pending、in_transit、paid、canceled 和 failed。pending 表示尚待提交银行,in_transit 表示已经在银行网络处理中;成功后为 paid,失败或取消则进入相应终态。
官方 API Reference 提醒,有些 payout 失败时最初可能显示 paid,然后在最多若干工作日内变为 failed。内部财务状态机因此不能假定 paid 永不变化;至少要允许 PAID_EXPECTED → FAILED_RETURNED 的迟到纠正。
arrival_date 不是到账保证
arrival_date 是预期到达日期,并考虑周末或银行假日,但不是银行回单。不同地区、标准或即时 payout、收款行处理和合规检查都会影响实际可用时间。
在 arrival_date 当天没看到款项时,先核对时区、银行入账描述和币种账户,再查询 Payout 最新状态与 trace ID。不要立即创建第二笔 payout,否则第一笔迟到时可能造成重复出款。
读取 failure_code 与 failure_message
当状态为 failed 时,读取 failure_code 和 failure_message。Stripe 列出的原因包括账户关闭、冻结、银行账户受限、银行所有权变化、无法处理、拒绝、账户持有人姓名/地址/税号错误、账户不存在、币种无效等。
以 failure_code 做机器分类,以 message 供内部诊断。不要把银行原始信息完整展示给终端客户,也不要通过字符串包含关系驱动资金操作。未知代码进入人工审核,不能默认自动重试。
failure_balance_transaction 解释余额退回
Payout 创建时会影响 Stripe Balance;失败或取消后,failure_ 指向将失败款项退回余额的 Balance Transaction。内部账务应同时记录原 balance_ 和失败冲正,而不是仅把原提现记录改成 failed。
用这两个交易建立不可变会计链:出款、银行退回、再次可用的余额。若金额尚未回到 available,检查 Balance Transaction 的可用时间与币种,不要在内部系统手工伪造余额。
payout.paid 不是最后一个可能事件
Stripe 事件目录说明,payout.paid 表示预计已可在目标账户使用;若之后失败,还会发送 payout.failed。因此 webhook 消费者必须接受同一 Payout 的后续状态变化和乱序交付。
每个事件按 event.id 幂等处理,并以 data.object.id 关联 Payout。收到事件后可以 retrieve 最新对象作为权威快照,比较 status 和事件创建时间;不能因为本地已是 PAID 就丢弃所有后续事件。
Webhook 丢失与补偿查询
网络超时、签名失败、端点停机或重试耗尽可能让本地漏掉 payout.failed。除 webhook 外,定期对仍在 pending/in_transit 和近期 paid 的 Payout 做状态对账,直到超过合理的银行退回观察窗口。
补偿任务只查询并更新状态,不重复创建 Payout。保存上次同步时间、Stripe 请求 ID 和对象版本快照,发现 paid→failed 时触发账务冲正与运营通知。
trace_id 怎样用于银行追踪
Payout 对象可包含 trace_id,这是受益银行生成的跟踪标识,银行可能称其为 reference number。trace_id.status 可为 pending、supported 或 unsupported;在 payout 到达终态后,部分情况下仍可能暂时 pending。
若 supported 且有 value,可让收款人带该编号联系银行定位。若 unsupported,不能编造替代编号;提供 Payout ID、金额、币种、预计日期和银行账户尾号给 Stripe 支持或财务人员。
failed 会影响外部账户
Stripe Connect 官方文档指出,Connected Account 的 payout 失败会禁用相关 external account,并触发 account.;平台更新外部账户前,不能继续向该账户 payout。
因此自动重试前必须检查目标账户是否仍可用。直接对同一失效 destination 重建 payout 会继续失败。先让账户所有者在受信任流程中更新或重新确认银行信息,再等待平台能力恢复。
不要在日志中暴露银行信息
排障日志保存 Payout ID、Connected Account ID、failure_code、状态和 destination ID 即可。银行账户完整号码、持有人税号和身份资料不应进入普通日志、工单或聊天。
用户界面可显示银行名称和尾号,并提供安全更新入口。客服不能通过邮件要求客户发送完整账户或验证码。
常见 failure_code 的处理边界
account_closed、no_account、invalid_ 等通常要求更换或修正账户;account_frozen、bank_ 需要持有人联系银行;debit_ 表示银行账户未允许所需借记能力;declined 应先联系银行再重试。
could_ 可能需要银行或 Stripe进一步判断。所有分类都应以当前 API Reference 与具体账户支持建议为准,不要根据代码名字自动修改银行资料或无限重试。
paid 但银行仍未显示
先 retrieve 最新 Payout,确认仍为 paid;检查 arrival_date、trace ID、destination 尾号和币种。让收款人查询正确账户、入账描述和银行处理日期。部分银行将款项归并显示,名称可能不是店铺名。
若超过合理时间仍未到账且没有 failed 事件,携 trace ID 联系银行;没有可用 trace ID 或银行无法定位时,再向 Stripe 支持提供 Payout ID。不要把 Stripe Balance 中已扣除直接当作收款行确认。
canceled 与 failed 的区别
cancelled 表示 Payout 被取消,failed 表示出款尝试不能完成并由银行或处理链路返回失败。两者都可能关联冲正 Balance Transaction,但运营原因和后续动作不同。
只有在 Payout 仍处于 API 允许取消的阶段时才能取消。已经进入银行网络后,不能靠本地状态修改撤回;要以 Stripe 对象返回结果为准。
自动与手动 Payout
automatic 表示由自动出款计划创建,false 表示手动请求。自动 Payout 失败后,Stripe 可能在修复银行账户后于下一计划周期重试;手动系统则要避免自己的重试任务与平台计划同时创建款项。
记录 payout schedule 和最近一次创建来源。恢复后先检查余额和现有 pending/in_transit Payout,再决定是否需要新建。
Standard 与 Instant Payout
method 可为 standard 或 instant,Instant Payout 仅在支持的银行卡、银行账户和地区可用。某个 destination 能收标准出款,不一定有即时资格。
如果 failure_code 指向 instant 不支持,不要把它当银行账户完全失效。可以在产品允许且资格明确时改用标准方式,但要重新展示预计时间和费用,不得静默改变用户选择。
Connect 平台的账户隔离
平台处理 Connected Account Payout 时,所有 API查询、Webhook 路由、幂等键和内部账本必须带账户维度。两个账户可能有同金额、同时间的 Payout,不能只用金额或银行尾号匹配。
Webhook endpoint 接收平台和连接账户事件时,记录事件的 account 上下文。权限不足、错误账户 header 或混用平台密钥,会造成“对象不存在”或对账到错误主体。
测试失败路径
Stripe 官方 Payout 文档提供测试银行账户和借记卡,可在测试 API key 下模拟成功、no_account、account_closed、insufficient_
测试环境不处理真实银行款项,但可验证状态机、Webhook 幂等、冲正、外部账户更新和客服通知。不要把测试号码用于 live Connected Account,也不要以测试时延推断真实银行结算时延。
推荐排查顺序
- 用正确账户和 live/test 模式 retrieve Payout。
- 核对 status、arrival_date、destination、method 和 automatic。
- 若 failed,读取 failure_code/message 与 failure_
balance_ transaction。 - 查
payout.paid、payout.failed、payout.updated的事件链。 - 检查 webhook 幂等、乱序和补偿对账是否生效。
- 查看 trace_id,必要时让银行定位。
- 检查 external account 是否被禁用及资料是否需更新。
- 修复后确认余额、现有 Payout,再决定是否重试。
修复后的账务验收
同一 Payout 的所有状态变更必须落在一条不可变时间线上;paid 后 failed 应生成冲正而非覆盖历史。内部可提现余额要与 Stripe Balance 和 Balance Transactions 对账,不能因 webhook 重放重复入账。
测试至少覆盖:pending→in_transit→paid、paid→failed、直接 failed、canceled、重复/乱序 webhook、外部账户禁用、资料更新和下一周期恢复。所有资金写入都需幂等。
常见问题
Stripe 显示 paid 就能给客户确认到账吗?
可以说明预计已到账,但仍应保留后续失败处理。部分 Payout 可能先为 paid,银行退回后再变 failed。
银行未到账时应该立刻再发一笔吗?
不应该。先查询最新状态和 trace ID,确认原款项不会迟到或退回,再决定后续动作,否则可能重复出款。
payout.failed 后钱去哪了?
通常通过 failure_ 冲回 Stripe Balance。应核对该交易及余额可用时间,而不是只看 Payout 状态。
为什么同一银行账户不能继续重试?
在 Connect 场景,失败可能禁用相关 external account。平台需更新或重新确认账户后才能继续 Payout。
trace_id 一定立即可用吗?
不一定。其 status 可能暂为 pending,也可能 unsupported。只有 supported 且 value 存在时才能提供给银行追踪。
总结
Stripe Payout 的 paid 是重要状态,但不是永远不可逆的银行确认。以最新 Payout 对象、failure_code、冲正 Balance Transaction、Webhook 事件和 trace ID 建立结算状态机,允许 paid 后 failed 的迟到变化;修复目标账户后再幂等重试,才能避免重复出款和账务失真。