ACQ.PAYMENT_FAIL 等余额不足错误码有什么区别?
支付小周
结论
这三个错误码对应不同的失败层级:
| 错误码 | 含义 | 常见场景 |
|---|---|---|
ACQ.PAYMENT_FAIL | 通用支付失败 | 支付渠道只返回“支付失败”,或平台无法将原因归类为更具体的错误码 |
ACQ.BUYER_BALANCE_NOT_ENOUGH | 买家的支付账户余额不足 | 买家使用余额类资金渠道付款,但可用余额不足 |
ACQ.BUYER_BANKCARD_BALANCE_NOT_ENOUGH | 买家银行卡可用余额或额度不足 | 买家使用银行卡付款,发卡行返回余额不足、可用额度不足等结果 |
ACQ.PAYMENT_FAIL 并不专指余额不足,而是一个范围更广的失败结果。即使实际原因是余额不足,只要银行或支付渠道没有返回可识别的具体信息,最终也可能被映射为 ACQ.PAYMENT_FAIL。
为什么实际更容易遇到 ACQ.PAYMENT_FAIL
支付请求通常会经过支付平台、收单机构和银行等多个环节。错误码能具体到什么程度,取决于下游渠道提供的信息,以及支付平台采用的映射策略。
常见情况包括:
- 买家使用支付账户余额付款,系统能够明确判断余额不足时,可能返回
ACQ.BUYER_BALANCE_NOT_ENOUGH。 - 买家使用银行卡付款,银行明确返回余额不足或信用卡可用额度不足时,可能返回
ACQ.BUYER_BANKCARD_BALANCE_NOT_ENOUGH。 - 银行只返回交易失败、拒绝交易等笼统结果,或平台无法准确识别失败原因时,可能返回
ACQ.PAYMENT_FAIL。 - 部分支付产品不会通过接口同步响应提供具体的资金渠道失败原因,而是统一返回支付失败。
- 支付方式、银行、接口产品或风控策略不同,同一类失败也可能被映射成不同的错误码。
错误码如何映射取决于支付渠道的实际行为,不能只看名称,就认为所有银行和支付产品都会稳定返回后两个细分错误码。
建议的处理方式
1. 将三个错误码统一归入“买家需要更换支付方式”
业务提示可以有所区别,但对应的后续操作基本相同:
ACQ.BUYER_BALANCE_NOT_ENOUGH:提示买家充值或改用其他支付方式。ACQ.BUYER_BANKCARD_BALANCE_NOT_ENOUGH:提示买家检查银行卡余额或额度,或者更换银行卡。ACQ.PAYMENT_FAIL:提示支付未完成,建议更换支付方式,不要直接断定是余额不足。
不建议遇到这类错误后无条件自动重试。余额不足、额度不足、银行拒绝或风控问题,通常不会因为立即重试而消失。反复发起支付还可能造成重复下单,或触发渠道限制。
2. 结合完整响应判断
处理接口结果时,不要只读取 sub_code,还应记录并检查:
codesub_codesub_msg- 商户订单号
- 支付平台交易号
- 接口名称
- 请求时间
- 后续订单查询结果
支付接口中的 ACQ.* 通常位于业务错误字段 sub_code,外层 code 可能只表示业务处理失败。具体字段结构仍应以当前接口的响应模型为准。
3. 无法确定支付结果时主动查询订单
遇到超时、网络中断或响应内容不足以确认交易终态时,不要直接创建新订单并重新支付。应先使用原商户订单号查询交易状态:
- 查询结果为成功:按支付成功处理。
- 查询结果为明确关闭或失败:允许买家重新支付。
- 查询结果仍为处理中:按合理间隔继续查询,并等待异步通知。
- 查询不到交易:确认原请求没有被受理后,再决定是否重新发起。
这样可以避免接口返回失败或超时,但买家实际已经被扣款,从而造成重复支付。
代码示例
下面的示例只演示错误码分类逻辑。响应类名和查询接口需要替换成项目实际使用的 SDK 类型。
public PaymentAction handlePaymentFailure(
String code,
String subCode,
String subMsg) {
if ("ACQ.BUYER_BALANCE_NOT_ENOUGH".equals(subCode)) {
return PaymentAction.fail(
"账户可用余额不足,请充值或更换支付方式"
);
}
if ("ACQ.BUYER_BANKCARD_BALANCE_NOT_ENOUGH".equals(subCode)) {
return PaymentAction.fail(
"银行卡余额或可用额度不足,请更换银行卡或支付方式"
);
}
if ("ACQ.PAYMENT_FAIL".equals(subCode)) {
return PaymentAction.fail(
"支付未完成,请更换支付方式后重试"
);
}
// 超时、系统异常等不能确认支付终态的情况,
// 应先查询订单,而不是直接重新发起支付。
if (isPaymentStatusUnknown(code, subCode)) {
return PaymentAction.queryOrder();
}
return PaymentAction.fail(
subMsg != null ? subMsg : "支付失败,请稍后重试"
);
}
业务系统也可以统一分类:
private static final Set<String> CHANGE_PAYMENT_METHOD_CODES = Set.of(
"ACQ.PAYMENT_FAIL",
"ACQ.BUYER_BALANCE_NOT_ENOUGH",
"ACQ.BUYER_BANKCARD_BALANCE_NOT_ENOUGH"
);
public boolean shouldChangePaymentMethod(String subCode) {
return CHANGE_PAYMENT_METHOD_CODES.contains(subCode);
}
前端提示和内部日志应分别处理。给买家的提示要简洁,内部日志则应保留原始错误信息:
log.warn(
"Payment failed: outTradeNo={}, code={}, subCode={}, subMsg={}",
outTradeNo,
response.getCode(),
response.getSubCode(),
response.getSubMsg()
);
注意事项
- 不要将
ACQ.PAYMENT_FAIL固定解释为余额不足。它还可能包含银行拒绝、渠道异常、风控拦截等没有进一步细分的原因。 ACQ.BUYER_BANKCARD_BALANCE_NOT_ENOUGH中的“不足”,既可能指借记卡余额不足,也可能指信用卡可用额度不足,具体取决于银行返回的结果。- 不要依赖某个细分错误码必然出现。不同银行、支付渠道和产品提供的错误信息粒度可能不同。
- 不要直接向买家展示原始
sub_msg,其中可能含有内部渠道信息。应将其转换成稳定、易懂的业务提示。 - 支付结果不确定时,应通过订单查询和异步通知确认终态,并保证通知处理的幂等性。
- 如需确认某个接口在特定支付产品中的准确映射规则,应以该接口当前版本的官方错误码文档和实际响应为准。
备注:内容仅供参考。
