支付宝免密支付成功或失败后是否有消息回调?
支付阿杰
结论
免密支付结束后,支付宝不会分别发送”成功回调”和”失败回调”。
支付宝免密支付通常仍由具体的支付接口完成,例如 alipay.trade.pay。不同结果的处理方式如下:
- 支付成功:请求中正确配置
notify_url后,支付宝会向该地址发送支付结果异步通知,常见的交易状态是TRADE_SUCCESS。 - 支付失败:失败原因通常会直接出现在支付接口的同步响应中,包括
code、sub_code、sub_msg等字段,一般不会另行发送”支付失败”异步通知。 - 结果不明确:发生超时、网络中断或系统仍在处理时,不能直接判定支付失败,需要调用
alipay.trade.query查询交易状态。
商户系统不能依赖”失败回调”更新订单。完整的状态处理需要结合接口同步响应、异步通知和主动查询。
为什么失败通常没有异步通知
异步通知主要用于告知商户,交易状态已经发生确定的变化。许多支付失败在接口调用阶段就能确认,比如余额不足、签约关系无效、风控拦截或参数错误,支付宝会通过同步响应返回这些信息。
接口调用超时不等于支付失败。商户没有收到同步响应时,支付宝可能已经受理请求,甚至已经完成扣款。如果此时直接将订单标记为失败,支付宝交易和商户订单的状态可能出现不一致。
推荐的处理流程
1. 先处理同步响应
调用免密支付接口后,可以按响应字段分类处理:
code == 10000:接口调用成功,但最终交易结果仍应以异步通知或主动查询为准。- 返回明确的业务失败码:记录
sub_code和sub_msg,将本次支付尝试标记为失败。 - 出现超时、网络异常或状态不明确:将订单标记为”处理中”,再调用查询接口确认结果。
不能只根据 HTTP 状态码判断支付是否成功。
2. 接收支付成功异步通知
发起支付请求时,需要配置可从公网访问的 notify_url。收到通知后,应依次完成以下检查:
- 验证支付宝签名;
- 核对
app_id; - 核对
out_trade_no; - 核对
total_amount; - 确认通知中的交易状态;
- 幂等更新订单;
- 处理成功后返回纯文本
success。
支付成功时,常见的 trade_status 包括:
TRADE_SUCCESS:交易支付成功,可以将订单更新为已支付。TRADE_FINISHED:交易已经结束且不可退款。当前产品和交易流程是否会收到该状态,应以所接入产品的官方接口文档为准。
WAIT_BUYER_PAY、TRADE_CLOSED 等状态可能出现在交易查询结果中,但这并不表示支付宝一定会针对每种状态发送异步通知。
3. 对不明确结果主动查询
如果支付接口超时、连接中断或返回处理中状态,应使用原支付请求的 out_trade_no 调用 alipay.trade.query。
示意请求如下:
{
"out_trade_no": "ORDER_202609130001"
}
查询后可以根据 trade_status 处理:
TRADE_SUCCESS -> 支付成功
TRADE_FINISHED -> 交易完成
WAIT_BUYER_PAY -> 尚未支付或仍在处理中
TRADE_CLOSED -> 交易关闭
如果首次查询仍得不到确定结果,可以按业务需要进行有限次数的延迟重试,并设置最终关单或人工核对机制。
异步通知处理示例
下面是一个简化的 Java 风格示例。具体的 SDK 类名和初始化方式,应以项目所用的支付宝 Java SDK 版本为准。
@PostMapping("/alipay/notify")
public String handleAlipayNotify(HttpServletRequest request) throws Exception {
Map<String, String> params = new HashMap<>();
for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
params.put(entry.getKey(), String.join(",", entry.getValue()));
}
boolean verified = AlipaySignature.rsaCheckV1(
params,
ALIPAY_PUBLIC_KEY,
"UTF-8",
"RSA2"
);
if (!verified) {
return "failure";
}
String appId = params.get("app_id");
String outTradeNo = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
String tradeStatus = params.get("trade_status");
String totalAmount = params.get("total_amount");
Order order = orderService.findByOutTradeNo(outTradeNo);
if (order == null) {
return "failure";
}
if (!EXPECTED_APP_ID.equals(appId)) {
return "failure";
}
if (!order.getTotalAmount().toPlainString().equals(totalAmount)) {
return "failure";
}
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
// updatePaid 必须具备幂等性,避免重复通知导致重复入账。
orderService.updatePaid(outTradeNo, tradeNo);
}
return "success";
}
同步调用可以按以下逻辑处理:
try {
AlipayTradePayResponse response = alipayClient.execute(request);
if ("10000".equals(response.getCode())) {
// 不要只依赖同步响应完成最终入账。
// 可暂记为处理中,等待异步通知或主动查询。
paymentService.markProcessing(outTradeNo);
} else {
paymentService.markFailed(
outTradeNo,
response.getSubCode(),
response.getSubMsg()
);
}
} catch (Exception e) {
// 网络异常不代表支付失败。
paymentService.markUnknown(outTradeNo);
paymentService.scheduleQuery(outTradeNo);
}
注意事项
- “免密”指的是用户授权和扣款方式,不表示支付结果一定有成功、失败两类异步回调。
- 异步通知可能重复发送,因此订单更新、记账和发货等操作必须保证幂等。
- 验签成功只能证明通知通过了签名校验,仍需核对商户订单号、金额和应用身份,确认通知属于当前订单。
- 回调接口处理成功后应返回纯文本
success,否则支付宝可能按规则重试通知。 - 网络超时、SDK 异常或没有收到通知,都不能直接视为支付失败。
- 不能只凭前端结果、同步响应或通知中的某一个字段发货。发货前应完成签名和业务字段校验。
- 如果使用的不是
alipay.trade.pay,而是周期扣款、代扣或其他免密产品,通知名称和状态字段可能有所不同,应以对应产品的接口文档和签约能力为准。
备注:内容仅供参考。
