交易挽回
交易挽回用于处理买家支付未完成、支付失败但仍可能重新支付、网络异常导致结果暂不明确等场景。商户可以在订单未成功前,引导买家继续支付或重新发起支付请求,以提升支付成功率。
本文关注“如何安全地重新推进支付”。各支付状态的含义、失败终态/失败非终态差异,以及订单幂等规则,请参考交易状态与幂等;如何通过通知和查询确认最终结果,请参考如何获取订单状态。
适用场景
以下场景通常可以考虑交易挽回:
| 场景 | 是否建议挽回 | 处理建议 |
|---|---|---|
| 买家支付过程中主动放弃、关闭页面或未完成钱包/银行页面操作 | 是 | 保持订单为待支付或支付中,在支付有效期内引导买家继续支付。 |
支付返回 FAILED,且业务采用失败非终态模式 | 是 | 可引导买家更换支付方式或重新发起支付请求。 |
支付返回 INIT 或 PROCESSING | 谨慎 | 不应立即创建新支付,应先等待异步通知或调用交易查询确认结果。 |
支付已返回 SUCCESS 或 AUTH_SUCCESS | 否 | 不再重新支付;AUTH_SUCCESS 场景应按业务需要发起 Capture 或 Void。 |
支付已返回 CANCEL 或 CLOSED | 通常否 | 该笔支付不可继续推进;如买家仍需购买,应重新创建订单或新支付请求。 |
失败终态模式下返回 FAILED | 否 | 该笔交易不可继续推进为成功;如需继续购买,应重新创建支付请求或订单。 |
注意
在订单最终状态不明确时,不要并发创建多笔支付请求。并发支付可能导致重复扣款,后续需要人工核对并发起退款。
重试前确认
发起交易挽回前,商户服务端建议先完成以下检查:
- 查询商户订单当前状态,确认订单尚未支付成功、未发货、未履约。
- 通过支付通知或交易查询确认 PingPong 侧交易状态。
- 检查原支付会话或原支付请求是否仍在有效期内。
- 确认本次重试是否会产生新的支付请求,并做好
merchantTransactionId、requestId、transactionId的关联记录。 - 在商户系统内对同一订单加锁或做并发控制,避免多个终端、多个浏览器窗口同时发起支付。
Hosted 模式挽回
Hosted 模式包括跳转收银台、内嵌 SDK 等由 PingPong 收银台承接买家支付体验的接入方式。商户服务端创建支付会话后,前端通常使用 paymentUrl 或 SDK 渲染能力让买家继续完成支付。
| 买家状态 | 商户处理建议 |
|---|---|
| 买家未打开收银台 | 可继续使用当前有效的 paymentUrl 或支付会话引导买家支付。 |
| 买家打开收银台但未完成支付 | 在支付会话仍有效时,可引导买家回到原收银台继续支付或重新选择支付方式。 |
| 收银台返回失败但订单未进入终态 | 先查询交易状态,再决定是否继续引导买家支付。 |
| 订单已关闭或支付会话过期 | 不再复用原支付会话;如买家仍需购买,应重新创建订单或新支付会话。 |
Hosted 模式下,前端页面展示的支付失败、取消或关闭不应直接作为订单最终状态。商户服务端仍需以异步通知或交易查询返回的 status 为准。
Non-hosted 模式挽回
Non-hosted 模式下,商户服务端直接调用下单并支付创建支付请求,并根据同步响应中的 status 或 action 推进后续流程。
requestId 是 Non-hosted 模式下控制支付请求幂等的重要字段:
| 目标 | requestId 使用建议 | 说明 |
|---|---|---|
| 重试同一次支付请求 | 复用原 requestId | 相同 merchantTransactionId + requestId 会按幂等规则返回同一支付请求结果,适合网络超时、响应未收到等场景。 |
| 让买家重新发起一笔新的支付请求 | 使用新的 requestId | 适合买家更换支付方式、原请求失败且允许重新支付的场景。 |
| 避免同一订单产生多笔支付请求 | 固定复用同一 requestId | 商户不希望同一订单有多次支付尝试时,可始终复用同一 requestId。 |
| 需要多次支付尝试并统计成功率 | 每次尝试使用新的 requestId | 商户侧需保存每次尝试记录,并将其关联到同一个 merchantTransactionId。 |
重复支付风险
如果同一笔商户订单在未支付成功前,使用不同 requestId 并发发起多笔支付请求,或者在 Hosted 模式下同时打开多个可支付入口,可能出现重复支付。商户应通过订单锁、前端按钮防重复点击、服务端状态校验等方式限制并发。
状态处理建议
交易挽回过程中,商户可按以下方式处理订单状态:
| PingPong 状态 | 是否继续挽回 | 商户订单处理 |
|---|---|---|
INIT | 是 | 保持待支付,继续等待买家支付或稍后查询。 |
PROCESSING | 暂不新建支付 | 保持支付中,等待异步通知或定时查询,不建议立即创建新支付请求。 |
FAILED | 取决于幂等模式 | 失败非终态可引导继续支付;失败终态应结束该笔请求并重新创建支付请求或订单。 |
SUCCESS | 否 | 标记支付成功,并按业务规则履约;重复通知或重复查询需幂等忽略。 |
AUTH_SUCCESS | 否 | 标记预授权成功,后续发起 Capture 或 Void,不应重新支付。 |
CANCEL | 否 | 标记取消或失败,不继续履约;如需继续购买,重新创建订单或支付请求。 |
CLOSED | 否 | 标记订单关闭,原支付入口不可继续使用。 |
推荐实现
建议商户在服务端为每笔订单维护支付尝试记录,至少保存以下信息:
| 字段 | 说明 |
|---|---|
merchantTransactionId | 商户订单号,用于关联同一业务订单。 |
requestId | 本次支付请求号,Non-hosted 模式下用于请求级幂等。 |
transactionId | PingPong 返回的交易号,用于交易查询、对账和售后操作。 |
status | 当前支付请求状态。 |
paymentMethod | 买家本次选择的支付方式。 |
createdTime / updatedTime | 支付尝试创建和最后更新时间。 |
实现时建议:
- 商户订单进入支付流程前,先判断是否已有
SUCCESS或AUTH_SUCCESS结果。 - 创建支付请求前,对商户订单加锁,避免并发创建多个支付请求。
- 同一
requestId的重试应视为同一次支付请求,不重复创建支付尝试记录。 - 新
requestId应记录为新的支付尝试,并与原商户订单关联。 - 异步通知和查询结果都应幂等更新,不能用较早的中间态覆盖已落库的终态。
重新创建订单号
如果原订单已经关闭、失败终态结束,或不在可重试窗口内,建议创建新的商户订单或在原订单号后追加后缀发起新交易。例如:
PMT-202607230001-R1
PMT-202607230001-R2这样既能避免与原交易的幂等规则冲突,也便于在对账、成功率分析和客服排查时将多次支付尝试关联回同一笔业务订单。
排查清单
如果交易挽回后仍无法支付成功,可按以下方向排查:
| 问题 | 排查方向 |
|---|---|
| 买家重复扣款 | 检查是否并发创建多个支付请求、是否使用了不同 requestId、是否在终态前重复开放支付入口。 |
| 买家重新支付后仍失败 | 检查支付方式能力、风控审核结果、支付方式页面返回原因,以及是否属于失败终态。 |
| 前端显示失败但后台成功 | 以前端结果页为辅助信息,最终以异步通知或交易查询的 status 为准。 |
长时间 PROCESSING | 继续等待异步通知,并按业务策略定时调用交易查询;不要立即创建新支付。 |
| 订单号无法再次下单 | 检查 merchantTransactionId 是否已进入终态或仍受幂等规则约束,必要时使用新的订单号或后缀。 |
