APP支付生成orderString后取消支付,如何关闭交易?
结论
orderString 并不表示支付宝交易已经创建成功。它只是经过签名的 APP 支付请求。在用户通过支付宝客户端提交请求之前,支付宝侧可能没有对应的交易记录,此时调用 Factory.Payment.Common().close() 就会返回“交易不存在”。
已经生成的 orderString 不能通过关闭接口直接撤销。建议按以下方式处理:
- 生成
orderString时设置较短的支付有效期。 - 用户取消支付后,先把商户订单标记为已取消。
- 继续查询支付宝交易。交易创建后,如果仍处于待支付状态,再调用关闭接口。
- 如果交易因并发操作已经支付成功,则无法关闭,需要按业务规则退款。
旧的 orderString 即使还能调起支付宝客户端,也不一定能完成付款。最终是否允许支付,由支付宝侧的交易状态和支付有效期决定。
为什么会提示“交易不存在”
Factory.Payment.App().pay() 主要负责组装参数并签名,生成供 APP 调用支付宝客户端的 orderString。这一步不一定会立即在支付宝服务端创建交易。
通常,APP 需要先把 orderString 交给支付宝客户端,再由客户端向支付宝发起请求。支付宝收到请求后,才会创建对应的交易。因此,实际流程中可能存在以下时间差:
商户服务端生成 orderString
↓
用户尚未提交,或直接取消
↓
支付宝侧没有对应交易
↓
调用 close() 返回交易不存在
关闭接口只能关闭支付宝侧已经存在的交易,不能撤销本地生成的签名字符串。
推荐处理流程
1. 生成支付请求时设置有效期
可以通过 timeout_express 限制交易的支付时间:
Map<String, String> optionalArgs = new HashMap<>();
optionalArgs.put("timeout_express", "10m");
AlipayTradeAppPayResponse response = Factory.Payment.App()
.batchOptional(optionalArgs)
.pay(subject, outTradeNo, totalAmount);
String orderString = response.body;
这里的 10m 只是示例,具体时长应根据实际业务设置。如果当前接口版本支持绝对过期时间参数,也可以使用对应参数,具体以所用 SDK 和支付宝接口文档为准。
支付有效期限制的是最终付款时间,并不保证 orderString 过期后无法调起支付宝客户端。客户端仍可能打开,但用户提交支付时,应因订单过期或交易已关闭而失败。
2. 用户取消后先关闭商户订单
用户点击取消时,应先更新商户系统中的订单状态,不能只依赖支付宝关闭接口的结果:
cancelMerchantOrder(outTradeNo);
后续处理异步通知或查询支付结果时,都必须检查商户订单状态。如果已取消的订单收到支付成功通知,不能直接发货或开通服务,而应转入退款或人工处理流程。
3. 查询交易是否已经在支付宝侧创建
用户取消后,可以查询支付宝交易:
AlipayTradeQueryResponse queryResponse =
Factory.Payment.Common().query(outTradeNo);
根据实际返回的 trade_status 分别处理:
WAIT_BUYER_PAY:交易已经创建,但尚未支付,可以调用关闭接口。TRADE_CLOSED:交易已经关闭,无需再次处理。TRADE_SUCCESS:交易已经支付成功,不能再关闭,需要判断是否退款。TRADE_FINISHED:交易已经结束,通常无法关闭。- 查询结果为“交易不存在”:支付宝侧暂时没有该交易,也就没有可关闭的对象。
4. 交易存在后执行关闭
确认交易处于待支付状态后,再调用关闭接口:
AlipayTradeCloseResponse closeResponse =
Factory.Payment.Common().close(outTradeNo);
if (!closeResponse.isSuccess()) {
// 记录 subCode、subMsg,并安排后续查询或补偿处理
}
如果第一次查询返回“交易不存在”,可以在支付有效期内安排短时间的延迟查询。例如,用户取消后每隔数秒重试一次,直到订单有效期结束。如果发现交易状态变为 WAIT_BUYER_PAY,应立即关闭。
重试需要设置明确的截止时间和最大次数,不能无限持续。
并发支付的处理
用户取消操作可能与支付宝提交支付同时发生:
用户点击取消 ─────→ 商户订单已取消
支付宝客户端提交 ─→ 支付宝交易创建或支付成功
因此,即使先查询再关闭,也无法彻底消除竞争条件。查询时交易可能仍处于待支付状态,但执行关闭时可能已经付款成功。
最终结果应以支付宝服务端查询和异步通知为准:
switch (tradeStatus) {
case "WAIT_BUYER_PAY":
closeTrade(outTradeNo);
break;
case "TRADE_SUCCESS":
refundIfMerchantOrderCancelled(outTradeNo);
break;
case "TRADE_CLOSED":
break;
default:
scheduleReconciliation(outTradeNo);
}
退款时需要使用实际支付金额、退款单号和对应的退款接口,不能仅根据客户端的“取消支付”回调直接退款。
注意事项
- 客户端取消回调不能作为最终交易状态。用户关闭收银台时,交易可能尚未创建,也可能处于待支付状态,甚至可能刚刚支付成功。
outTradeNo必须保持唯一,并用于整个支付、查询、关闭、退款和对账流程。- 不要为同一笔业务反复生成长期有效的
orderString,以免延长用户取消后的可支付时间。 - 商户订单状态更新、交易关闭和退款操作都需要保证幂等。
- 异步通知必须验签,并根据商户订单状态决定发货、退款或转入异常处理。
- 如果业务要求用户取消后立即且绝对无法支付,仅靠 APP 支付的
orderString和关闭接口无法保证。还需要结合较短的支付有效期、商户侧状态控制、延迟关单,以及支付成功后的自动退款机制。
备注:内容仅供参考。
