支付宝如何关闭未支付的预支付单并阻止用户继续支付?
结论
支付宝交易尚未支付时,不应调用退款接口,而应调用 alipay.trade.close(统一收单交易关闭接口)。
退款接口用于退回已经支付成功的资金。用户还没有付款,也就没有可退金额,此时调用退款接口可能返回 ACQ.TRADE_NOT_EXIST 等错误。系统取消订单后,应使用创建支付时传给支付宝的同一个 out_trade_no 关闭交易。交易关闭成功后,原支付链接、订单字符串或二维码通常无法继续完成支付。
为什么退款接口不适用
这里需要区分三个接口的用途:
alipay.trade.close:关闭尚未完成付款的交易。alipay.trade.refund:退回已经支付成功的交易资金。alipay.trade.query:查询交易当前状态。
生成预支付信息后,支付宝侧是否已经形成可查询的交易记录,取决于具体支付产品和交易创建时机,不能简单地认为”只有付款后才会生成支付宝订单”。
可以确定的是,未支付交易没有可退款资金,退款接口也不能用来阻止用户继续支付。
ACQ.TRADE_NOT_EXIST 还可能由以下原因引起:
- 使用的
out_trade_no不是创建支付宝交易时传入的商户订单号。 - 请求发到了不同的支付宝应用或商户账号。
- 混用了正式环境和沙箱环境。
- 交易尚未在支付宝侧成功创建。
- 交易创建与查询、关闭之间存在短暂的状态同步延迟。
正确处理步骤
1. 保存支付宝支付请求中的商户订单号
创建支付时,应保存传给支付宝的 out_trade_no。关闭交易必须使用这个值,它不一定是系统数据库中的主键。
例如:
系统订单 ID:182736
支付宝 out_trade_no:PAY_20260908_182736
关闭时应传:
{
"out_trade_no": "PAY_20260908_182736"
}
如果支付成功后已经获得支付宝交易号,也可以使用 trade_no,通常选择其中一个即可。
2. 取消本地订单后调用交易关闭接口
Java SDK 示例:
AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
request.setBizContent("""
{
"out_trade_no": "PAY_20260908_182736"
}
""");
AlipayTradeCloseResponse response = alipayClient.execute(request);
if (response.isSuccess()) {
// 支付宝交易关闭成功
// 更新本地支付记录,例如标记为 CLOSED
} else {
// 记录 code、subCode、subMsg,后续根据交易状态处理
System.out.println("code: " + response.getCode());
System.out.println("subCode: " + response.getSubCode());
System.out.println("subMsg: " + response.getSubMsg());
}
如果直接构造请求,核心业务参数如下:
{
"out_trade_no": "PAY_20260908_182736"
}
具体网关地址、签名算法和公共请求参数,应以项目正在使用的支付宝 SDK 及支付产品文档为准,不建议自行拼接签名请求。
3. 关闭失败时查询交易状态
关闭交易时,用户可能正在付款,因此不能只根据本地订单状态判断最终结果。关闭失败后,可以调用 alipay.trade.query,确认支付宝侧的实际状态。
常见状态包括:
WAIT_BUYER_PAY:等待买家付款,可以继续尝试关闭。TRADE_CLOSED:交易已经关闭。TRADE_SUCCESS:用户已付款,需要进入退款流程。TRADE_FINISHED:交易已结束,能否退款取决于具体产品规则和交易条件。
可以按以下逻辑处理:
取消本地订单
↓
调用 alipay.trade.close
↓
关闭成功 → 本地支付记录标记为 CLOSED
↓
关闭失败 → 调用 alipay.trade.query
├─ WAIT_BUYER_PAY → 重试关闭
├─ TRADE_CLOSED → 按关闭成功处理
└─ TRADE_SUCCESS → 调用 alipay.trade.refund
必须处理支付与取消并发
需要特别留意竞态问题:系统正在关闭交易时,用户可能恰好完成付款。
即使本地订单已经取消,也必须处理支付宝的异步支付通知。收到支付成功通知后,应先验证签名、商户身份、订单号和金额,再结合本地订单状态进行处理:
if (alipayNotifyVerified) {
Order order = orderService.findByOutTradeNo(outTradeNo);
if (order.isCancelled()) {
// 用户付款与订单取消发生并发
// 记录支付成功事实,并发起退款或进入人工补偿流程
refundService.refund(order);
} else {
// 正常更新为已支付
orderService.markPaid(order);
}
}
不能因为本地订单已经取消,就直接丢弃支付成功通知。否则可能出现用户已经被扣款,但系统既不履约也不退款的情况。
建议增加的保护措施
设置支付有效期
创建支付时,可以查看所用支付宝产品是否支持类似 timeout_express 的超时参数。这个参数可以缩短交易的可支付时间,但不能代替主动关闭:
{
"out_trade_no": "PAY_20260908_182736",
"total_amount": "99.00",
"subject": "订单支付",
"timeout_express": "15m"
}
参数是否可用以及具体格式,应以对应支付接口的文档为准。
做好幂等控制
订单取消、交易关闭、支付回调和退款都有可能重复执行,因此需要做好幂等控制:
- 使用
out_trade_no唯一标识一次支付交易。 - 关闭接口应支持安全重试。
- 后到的”取消”操作不能直接覆盖支付成功状态。
- 退款应使用稳定且唯一的退款请求号。
- 使用事务、条件更新或状态机控制状态变更。
记录支付宝返回信息
至少保存以下信息,以便定位问题:
app_idout_trade_notrade_no- 接口名称
codesub_codesub_msg- 当前使用的是沙箱环境还是正式环境
- 请求时间及当时的本地订单状态
总结
未支付订单应调用 alipay.trade.close,而不是 alipay.trade.refund。关闭交易时,需要传入创建支付交易所使用的 out_trade_no。如果关闭操作与用户付款同时发生,应查询支付宝侧的交易状态。交易仍未支付时继续关闭,已经支付时则进入退款流程。支付宝异步通知也必须正常处理,确保订单取消后发生的支付能够得到退款或补偿。
备注:内容仅供参考。
