支付报错ACQ.TRADE_HAS_SUCCESS,请问如何处理?
结论
ACQ.TRADE_HAS_SUCCESS 表示这笔交易已经支付成功,支付宝因此拒绝了重复支付请求。此时不要再次发起支付。应先查询支付宝端的交易状态,再把本地订单更新为已支付。
如果用户已经扣款,但业务系统仍显示未支付,常见原因是异步通知尚未到达、通知处理失败,或者本地订单状态未正确更新。
常见原因
该报错一般由以下情况引起:
- 用户已经完成支付,但页面再次提交了支付请求。
- 用户刷新页面、重复点击,或者网络重试,导致系统使用同一个
out_trade_no再次发起支付。 - 支付宝异步通知处理失败,本地订单仍处于”待支付”状态。
- 业务系统没有检查订单状态,直接对已支付订单重新调用支付接口。
- 创建新订单时,误用了已经支付成功的
out_trade_no。
out_trade_no 是商户订单号。在同一商户账号下,它应唯一对应一笔交易,不能随意复用。
处理步骤
1. 停止重复支付
收到 ACQ.TRADE_HAS_SUCCESS 后,不要更换支付方式、重复请求或自动重试,以免造成业务状态混乱。
前端可以提示用户:
该订单已经支付,请勿重复支付。正在确认订单状态。
2. 主动查询交易状态
调用 alipay.trade.query 查询实际交易结果。查询时通常传入商户订单号 out_trade_no。如果系统已经保存了支付宝交易号,也可以使用 trade_no。
需要重点检查返回结果中的 trade_status:
TRADE_SUCCESS:交易支付成功。TRADE_FINISHED:交易已完成,按支付成功处理。WAIT_BUYER_PAY:交易等待付款。TRADE_CLOSED:交易已经关闭。
如果查询结果是 TRADE_SUCCESS 或 TRADE_FINISHED,应核对商户订单号、金额和收款账号等信息,确认无误后再更新本地订单。
3. 修正本地订单状态
更新订单前,至少要校验以下信息:
out_trade_no是否与本地订单一致。- 支付金额是否与订单应付金额一致。
- 收款方身份是否属于当前商户。
- 当前订单是否已经处理过。
订单状态更新和后续业务操作必须支持幂等。即使异步通知和主动查询同时确认支付,发货、充值或开通服务等操作也只能执行一次。
4. 检查异步通知
如果支付宝端已经支付成功,但本地仍显示未支付,需要检查:
notify_url能否从公网正常访问。- 系统是否正确验签。
- 通知处理是否因超时或程序异常而失败。
- 处理成功后,是否按接口要求返回了成功响应。
- 系统是否错误地依赖同步跳转结果更新订单。
支付结果应以支付宝异步通知或服务端主动查询的结果为准,不能只依赖浏览器跳转页面。
Java 处理示例
以下示例演示收到重复支付提示后,如何通过 alipay.trade.query 查询交易状态。具体类名和调用方式应以项目使用的支付宝 Java SDK 版本为准。
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
request.setBizContent(
"{\"out_trade_no\":\"" + outTradeNo + "\"}"
);
AlipayTradeQueryResponse response = alipayClient.execute(request);
if (response.isSuccess()) {
String tradeStatus = response.getTradeStatus();
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
// 必须先校验订单号、金额、收款方等信息
verifyTrade(response);
// 使用条件更新或事务保证幂等
boolean updated = orderService.markPaidIfUnpaid(
outTradeNo,
response.getTradeNo(),
response.getTotalAmount()
);
if (updated) {
// 仅首次更新成功时执行发货、充值或开通服务
orderService.processPaidOrder(outTradeNo);
}
} else {
// 根据 WAIT_BUYER_PAY、TRADE_CLOSED 等状态分别处理
orderService.handleTradeStatus(outTradeNo, tradeStatus);
}
} else {
// 查询接口调用失败不等于交易失败,应记录信息并稍后重查
log.warn(
"支付宝交易查询失败,outTradeNo={}, code={}, subCode={}, subMsg={}",
outTradeNo,
response.getCode(),
response.getSubCode(),
response.getSubMsg()
);
}
幂等更新可以使用类似下面的条件:
UPDATE payment_order
SET status = 'PAID',
trade_no = ?,
paid_at = CURRENT_TIMESTAMP
WHERE out_trade_no = ?
AND status = 'UNPAID';
只有受影响行数为 1 时,才继续执行一次性的支付成功业务。如果受影响行数为 0,通常表示订单已经处理过,不能再次发货或充值。
注意事项
- 不要直接生成新的
out_trade_no让用户再次付款,否则同一业务订单可能被支付两次。 - 不要因为接口调用失败就直接认定交易失败。遇到网络超时或响应丢失时,交易仍可能已经成功,必须主动查询。
ACQ.TRADE_HAS_SUCCESS本身通常不需要退款。只有确认存在重复扣款,或业务确实需要撤销时,才进入退款流程。- 同步返回、异步通知和主动查询可能先后到达,这三条处理链路都必须支持幂等。
- 如果查询到的金额、订单号或收款方信息不一致,不要自动更新订单。应记录异常并转交人工核查。
- 如果本地订单并未支付,但同一个商户订单号持续返回该错误,应检查
out_trade_no是否被历史订单复用。
备注:内容仅供参考。