支付宝当面付扫码支付时 SDK 打印日志后无后续响应
结论
code="10003" 不表示 SDK 卡死,也不表示支付失败。它说明支付宝已经受理订单,但用户尚未完成付款,交易当前处于“等待用户支付”状态。
alipay.trade.pay 返回该结果后,商户系统应主动调用 alipay.trade.query 查询交易状态,不要继续等待当前 SDK 调用返回其他结果。如果超过业务设定的等待时间仍未支付,应调用 alipay.trade.cancel 撤销交易,再根据撤销结果决定是否继续查询。
为什么打印日志后没有后续响应
日志中已经包含了本次接口的同步响应:
{
"code": "10003",
"msg": "order success pay inprocess",
"out_trade_no": "110031667093219182",
"trade_no": "2022103022001418021410998340",
"buyer_pay_amount": "0.00",
"total_amount": "1882.33"
}
各字段含义如下:
10003:订单已受理,支付仍在处理中。buyer_pay_amount="0.00":返回响应时还没有完成扣款。out_trade_no:商户订单号,后续查询、撤销和对账都需要使用,应妥善保存。trade_no:支付宝交易号,可以用于后续查询。- 日志末尾的
8ms,630ms,nullms通常是 SDK 或业务代码记录的耗时信息,不能用来判断支付结果。 - 日志中的换行只是输出格式问题。只要原始响应可以正常解析和验签,一般不会影响交易。
条码支付本身就需要处理“用户支付中”这一状态。用户可能还没有输入密码或确认付款,也可能是支付渠道处理较慢,或者首次同步请求结束时,支付结果还没有确定。
正确的处理流程
1. 区分成功、处理中和明确失败
不要只用 response.isSuccess() 判断整个支付流程。处理 alipay.trade.pay 的返回结果时,应区分以下情况:
code=10000:接口调用成功,还需要结合响应内容确认业务结果。code=10003:支付处理中,应进入查询流程。- 其他错误码:结合
sub_code和sub_msg,判断是明确失败还是可以重试的异常。
尤其不能把 10003 直接更新为“支付失败”。如果用户稍后完成付款,商户订单状态就会与支付宝交易状态不一致。
2. 使用原订单号查询支付结果
收到 10003 后,调用 alipay.trade.query。查询时优先使用原来的 out_trade_no,不要为同一次付款重新生成订单号。
可以根据查询结果中的 trade_status 进行处理:
trade_status | 处理建议 |
|---|---|
TRADE_SUCCESS | 支付成功,以幂等方式更新订单并发货 |
TRADE_FINISHED | 交易完成,按成功状态处理 |
WAIT_BUYER_PAY | 用户尚未完成付款,稍后继续查询 |
TRADE_CLOSED | 交易已关闭,不能按支付成功处理 |
| 查询不到交易 | 短暂等待后重新查询;如果持续查不到,再进入异常处理流程 |
商户应根据收银场景设置查询间隔和总等待时间,既不要无间隔地高频循环,也不要无限查询。
3. 超时后撤销交易
如果超过收银台允许的等待时间,仍然无法确认支付结果,应调用:
alipay.trade.cancel
撤销时继续使用同一个 out_trade_no。由于撤销接口可能遇到交易状态尚未稳定等情况,不能因为一次网络异常就认定撤销成功。
可以按以下原则处理:
- 撤销明确成功:将本地订单更新为已撤销或已关闭。
- 撤销失败,但查询结果显示支付成功:按支付成功处理。
- 撤销结果不明确:继续调用
alipay.trade.query核实最终状态。 - 无法自动确认:记录订单号、支付宝交易号和接口响应,转入人工核查。
Java 处理示例
下面的示例展示了核心状态处理逻辑。客户端初始化以及证书模式或公钥模式的配置,应继续使用项目当前的支付宝 SDK 配置。
import com.alipay.api.AlipayClient;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradePayResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
public class AlipayTradeHandler {
private final AlipayClient alipayClient;
public AlipayTradeHandler(AlipayClient alipayClient) {
this.alipayClient = alipayClient;
}
public PayResult handlePayResponse(AlipayTradePayResponse payResponse)
throws Exception {
String code = payResponse.getCode();
if ("10000".equals(code)) {
return PayResult.SUCCESS;
}
if ("10003".equals(code)) {
return queryTrade(payResponse.getOutTradeNo());
}
// 保存 code、subCode、subMsg,便于定位明确失败的原因
return PayResult.FAILED;
}
private PayResult queryTrade(String outTradeNo) throws Exception {
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
request.setBizContent(
"{\"out_trade_no\":\"" + escapeJson(outTradeNo) + "\"}"
);
AlipayTradeQueryResponse response = alipayClient.execute(request);
if (!"10000".equals(response.getCode())) {
// 接口异常不等于交易失败,交给重试或人工核查流程
return PayResult.UNKNOWN;
}
String tradeStatus = response.getTradeStatus();
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
return PayResult.SUCCESS;
}
if ("WAIT_BUYER_PAY".equals(tradeStatus)) {
return PayResult.PAYING;
}
if ("TRADE_CLOSED".equals(tradeStatus)) {
return PayResult.CLOSED;
}
return PayResult.UNKNOWN;
}
private String escapeJson(String value) {
return value.replace("\\", "\\\\")
.replace("\"", "\\\"");
}
enum PayResult {
SUCCESS,
PAYING,
CLOSED,
FAILED,
UNKNOWN
}
}
实际项目中,建议使用 SDK 提供的业务模型类或可靠的 JSON 序列化库生成 biz_content,避免手工拼接 JSON。
轮询次数和间隔可以由业务层控制:
int maxAttempts = 10;
long intervalMillis = 2000L;
for (int attempt = 0; attempt < maxAttempts; attempt++) {
PayResult result = queryTrade(outTradeNo);
if (result == PayResult.SUCCESS) {
markOrderPaidIdempotently(outTradeNo);
break;
}
if (result == PayResult.CLOSED || result == PayResult.FAILED) {
markOrderFailed(outTradeNo);
break;
}
Thread.sleep(intervalMillis);
}
示例中的查询次数和间隔只用于说明流程,并非支付宝的官方要求。生产环境需要结合收银台超时时间、接口限流和用户体验进行配置,同时避免长期占用 Web 请求线程。后续查询更适合交给后台任务或延迟队列处理。
还需要检查的项目
如果业务代码打印日志后完全没有进入查询逻辑,可以重点排查以下问题:
- 代码是否只处理了
code=10000,导致10003进入未处理分支。 - 是否调用了 SDK 示例中的轮询方法,但线程提前结束,或者异常被吞掉。
- 是否在异步线程中执行查询,却没有记录线程异常。
- HTTP、线程池或业务超时时间是否设置得过短。
out_trade_no是否已正确保存和传递。- 是否把
10003当作普通错误直接return。 - 是否完整记录了
code、sub_code、sub_msg、out_trade_no和trade_no。 - 查询接口返回后,代码是否正确读取了
trade_status,而不是只检查isSuccess()。
注意事项
支付状态更新必须具备幂等性。同一个订单可能通过主动查询、异步通知或补偿任务被多次确认,但重复确认不能导致重复发货、重复记账或重复增加余额。
网络超时同样不表示支付失败。只要支付请求可能已经到达支付宝,就不能直接创建新的商户订单再次扣款,必须先查询原订单。
如果把异步通知作为补充,还需要验签,并核对 app_id、out_trade_no、金额和收款方信息。不过,收银台不能只依赖异步通知,还要通过主动查询确认最终交易状态,形成完整闭环。
这段日志是正常的“支付处理中”响应。需要修正的是业务状态机:收到 10003 后进入查询流程,查询超时后撤销交易。只要结果还不明确,就应继续核实,不能直接按支付失败处理。
备注:内容仅供参考。
