支付宝分账报错 ACQ.ALREADY_CONFIRM_SETTLE 的原因
结论
ACQ.ALREADY_CONFIRM_SETTLE 表示这笔支付宝交易已经完成“结算确认”,不能再次提交结算确认请求。
这通常与分账金额或接收方配置无关,而是结算确认接口被重复调用。第一次请求可能已经成功,但业务系统没有正确保存结果。超时重试、并发任务和消息重复消费也可能导致这个问题。
常见原因
1. 同一笔交易被重复确认
结算确认通常是终态操作。支付宝受理成功后,如果再次使用同一个 trade_no 发起确认,会返回:
sub_code: ACQ.ALREADY_CONFIRM_SETTLE
sub_msg: 已经确认结算,不需要重复发起
即使更换 out_request_no,已经确认结算的交易也不能重新进入分账流程。
2. 第一次请求成功,但本地记录失败
可能出现以下情况:
- 支付宝已经处理成功,但调用方读取响应时超时;
- 网络中断,导致业务系统误以为请求失败;
- 支付宝返回成功后,本地数据库事务回滚;
- 程序只检查了 HTTP 状态,没有正确解析支付宝的业务响应;
- 调用成功后没有保存结算状态。
业务系统再次发起请求时,就会收到“已经确认结算”的错误。
3. 出现并发调用
定时任务、消息消费者或人工补单同时处理同一笔交易时,可能有一个请求率先成功,其他请求随后全部报错。
4. 分账流程顺序错误
如果一笔交易需要多次分账,应先完成所有允许的分账操作,最后再确认结算。过早调用结算确认接口后,通常无法继续追加分账。
能否多次分账,以及具体的次数和金额限制,应以当前支付宝产品的签约能力和对应接口文档为准。
处理步骤
第一步:确认交易号是否正确
检查请求中的 trade_no,确认没有误用其他订单的支付宝交易号,也没有把已经处理完的交易再次放入任务队列。
示例响应如下:
{
"alipay_trade_settle_confirm_response": {
"msg": "Business Failed",
"code": "40004",
"sub_msg": "已经确认结算,不需要重复发起",
"sub_code": "ACQ.ALREADY_CONFIRM_SETTLE",
"trade_no": "",
"out_request_no": ""
}
}
失败响应中的 trade_no 和 out_request_no 为空,并不代表请求没有到达支付宝。排查时应查看调用前记录的原始请求参数和支付宝网关响应。
第二步:检查首次调用记录
按照 trade_no 检查以下信息:
- 是否存在成功的结算确认记录;
- 是否发生过超时或网络异常;
- 是否使用过多个不同的
out_request_no; - 是否有多个服务实例同时处理;
- 消息队列是否重复投递;
- 人工补单是否与自动任务重叠。
如果之前已经确认成功,应将当前错误视为重复请求,不要继续重试。
第三步:核对交易当前状态
可以通过支付宝提供的交易查询能力和商户后台账务记录核对交易状态。不同产品返回的结算字段可能不同,不要只根据某个自定义字段作出判断。
如果查询接口无法确认分账明细,应保存并核对第一次结算请求、网关响应和支付宝账单。必要时,可携带 trade_no、请求时间及支付宝请求流水,向支付宝技术支持核查。
第四步:修正本地订单状态
确认交易确实已经结算后,将本地状态更新为已确认,例如:
SETTLE_CONFIRMED
同时停止对该 trade_no 的自动重试。反复生成新的 out_request_no 并重新调用接口,无法改变交易的终态。
代码处理示例
下面的处理逻辑不依赖具体 SDK。实际使用时,字段名称应根据接入的支付宝 SDK 和接口文档调整。
String tradeNo = order.getAlipayTradeNo();
if (settleRepository.isConfirmed(tradeNo)) {
return;
}
String outRequestNo = settleRepository.getOrCreateRequestNo(tradeNo);
try {
AlipayResponse response = alipayClient.settleConfirm(
tradeNo,
outRequestNo,
settleDetailInfos
);
if (response.isSuccess()) {
settleRepository.markConfirmed(
tradeNo,
outRequestNo,
response.getBody()
);
return;
}
if ("ACQ.ALREADY_CONFIRM_SETTLE".equals(response.getSubCode())) {
// 支付宝侧已经确认,停止无意义重试。
// 如本地缺少首次成功记录,应先核对交易和账务数据。
settleRepository.markConfirmedAfterVerification(
tradeNo,
outRequestNo,
response.getBody()
);
return;
}
throw new SettleException(
response.getCode(),
response.getSubCode(),
response.getSubMsg()
);
} catch (NetworkException e) {
// 网络异常不能直接等同于业务失败。
// 应先查询或核账,再决定是否使用原 out_request_no 重试。
retryQueue.scheduleVerification(tradeNo, outRequestNo);
}
可以在调用接口前完整记录请求参数,例如:
{
"trade_no": "支付宝交易号",
"out_request_no": "商户结算请求号",
"settle_detail_infos": [
{
"amount": "1.00",
"trans_in_type": "defaultSettle"
}
]
}
日志中不要记录支付宝私钥、应用证书内容或其他敏感信息。
幂等与并发控制
建议以 trade_no 建立唯一处理约束,并在同一次结算流程中固定使用同一个 out_request_no。
CREATE UNIQUE INDEX uk_settle_trade_no
ON settle_record (trade_no);
执行结算前,还可以通过更新数据库状态来抢占处理权:
UPDATE settle_record
SET status = 'PROCESSING'
WHERE trade_no = ?
AND status = 'PENDING';
只有受影响行数为 1 的任务可以继续调用支付宝接口,其他任务应直接退出。调用成功后,再将状态更新为 CONFIRMED。
注意事项
code = 40004表示业务处理失败,具体原因要看sub_code。- 不要对
ACQ.ALREADY_CONFIRM_SETTLE进行无限重试。 - 网络超时不等于支付宝处理失败,重试前应先查询或核账。
- 同一次业务重试应复用原来的
out_request_no,不要每次随机生成新值。 - 所有分账步骤都应在最终结算确认前完成。
- 如果本地没有成功记录,但支付宝持续返回该错误,应先排查并发调用、重复消息和历史请求日志。
- 能否撤销结算或追加分账,取决于具体产品能力和交易状态,更换请求号无法实现这些操作。
备注:内容仅供参考。
