支付宝异步通知验证成功但订单查询不到付款状态
结论
异步通知验签成功,只能说明通知参数的签名通过了支付宝公钥验证,不能据此认定订单已经付款。
确认支付结果时,还需要检查以下内容:
trade_status是否为TRADE_SUCCESS或TRADE_FINISHEDapp_id、seller_id是否属于当前商户out_trade_no、total_amount是否与本地订单一致- 查询所用的应用、网关环境和订单号类型是否正确
alipay.trade.query返回的code、sub_code和sub_msg是否表明查询成功
如果验签成功,但在支付宝侧查不到交易,通常是因为正式环境与沙箱环境混用、商户账号或应用不一致,或者混淆了支付宝交易号 trade_no 和商户订单号 out_trade_no。
异步通知参数是怎样生成的
支付状态变化后,支付宝会根据交易记录生成通知参数,再通过 HTTP POST 请求发送到支付请求中指定的 notify_url。
这些参数通常分为三类:
- 商户在请求中提供并由支付宝回传的信息,例如
out_trade_no、total_amount、subject - 支付宝交易系统生成的信息,例如
trade_no、trade_status、gmt_payment - 用于识别和验证通知的信息,例如
app_id、seller_id、charset、sign_type、sign
支付宝按照签名规则对待签名参数排序、拼接,再使用支付宝私钥生成 sign。商户收到通知后,需要使用支付宝公钥验签。
不同支付产品和接口包含的参数可能不同,应以对应支付接口的“异步通知参数”说明为准。可以在支付宝开放平台文档中心搜索:
异步通知异步通知验签alipay.trade.query电脑网站支付 异步通知手机网站支付 异步通知当面付 异步通知
开放平台的文档路径可能调整,不建议长期依赖非官方转载页面。
为什么验签成功却查不到付款状态
1. 只验证了签名,没有验证交易状态
验签成功不代表 trade_status 一定是支付成功。支付宝标准交易中常见的状态包括:
| 状态 | 含义 |
|---|---|
WAIT_BUYER_PAY | 等待买家付款 |
TRADE_CLOSED | 交易关闭 |
TRADE_SUCCESS | 交易支付成功 |
TRADE_FINISHED | 交易结束,不可退款 |
业务系统通常只在收到 TRADE_SUCCESS 或 TRADE_FINISHED 时,才把订单更新为已支付。
2. 查询时用错了订单号
支付宝交易中有两个容易混淆的编号:
out_trade_no:商户创建订单时生成的商户订单号trade_no:支付宝创建交易后生成的支付宝交易号
调用 alipay.trade.query 时,可以使用其中任意一种编号查询,但参数名和值必须对应。不能把 trade_no 的值传给 out_trade_no。
3. 沙箱和正式环境混用
沙箱交易只能使用沙箱网关和沙箱应用查询,正式交易必须通过正式网关查询。
常见的配置错误有:
- 交易发生在沙箱,查询请求却发到了正式网关
- 交易发生在正式环境,查询时使用了沙箱应用
- 回调服务混用了两套环境的配置
- 查询时加载了另一个环境的
app_id、私钥或支付宝公钥
4. 查询使用了不同的应用或商户身份
异步通知中的 app_id、seller_id 应当与当前订单所属的应用和商户一致。
如果平台维护了多个支付宝应用,或者采用服务商代商户调用模式,还需要确认查询请求使用了正确的商户授权关系和授权令牌。否则,即使订单号正确,也可能无法查询到当前调用身份下的交易。
5. 门户后台的商户账号或筛选条件不正确
支付宝开放平台应用、商家平台账号和实际收款账号并不是同一个概念。应根据通知中的 seller_id 确认实际收款方,同时检查后台设置的时间范围、交易状态和订单号类型。
6. 忽略了查询接口的业务错误
接口正常返回不代表订单查询成功。以下信息都需要记录并检查:
codemsgsub_codesub_msg- 原始响应报文
订单不存在、参数错误、调用身份不匹配等问题,通常可以从 sub_code 和 sub_msg 中找到线索。不能只看 HTTP 状态码或 SDK 的 isSuccess() 来判断付款状态。
7. 通知与查询之间存在短暂时序差异
正常情况下,收到支付成功通知后应当能够查询到交易。但受网络重试、系统延迟或异常链路影响,短时间内也可能查询失败。
可以按有限次数延迟重试,例如分别间隔 1 秒、3 秒和 10 秒查询。如果持续查不到,应保存完整的通知和查询响应,再通过支付宝开放平台的问题排查渠道核实,不能直接把订单改为已支付。
推荐的处理步骤
- 保存收到的完整通知参数,并做好脱敏,不要记录私钥等敏感配置。
- 使用当前应用对应的支付宝公钥验证
sign。 - 核对通知中的
app_id和seller_id。 - 根据
out_trade_no查询本地订单。 - 使用精确十进制方式比较
total_amount,不要直接用double比较金额。 - 确认
trade_status是否为支付成功状态。 - 使用相同环境和相同应用调用
alipay.trade.query。 - 记录查询接口返回的完整业务状态码。
- 将支付处理设计成幂等操作,防止重复通知引起重复发货或重复记账。
- 本地事务提交成功后,向支付宝返回纯文本
success;如果处理失败,不要提前返回成功。
Java 验签与业务校验示例
下面的示例用于说明处理结构,字段值和 SDK 初始化方式需要按照实际项目配置。
import com.alipay.api.internal.util.AlipaySignature;
import java.math.BigDecimal;
import java.util.Map;
public class AlipayNotifyHandler {
public String handle(
Map<String, String> params,
String alipayPublicKey,
String expectedAppId,
String expectedSellerId,
LocalOrder order
) throws Exception {
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!verified) {
return "failure";
}
if (!expectedAppId.equals(params.get("app_id"))) {
return "failure";
}
if (!expectedSellerId.equals(params.get("seller_id"))) {
return "failure";
}
if (!order.getOutTradeNo().equals(params.get("out_trade_no"))) {
return "failure";
}
BigDecimal notifiedAmount =
new BigDecimal(params.get("total_amount"));
if (order.getAmount().compareTo(notifiedAmount) != 0) {
return "failure";
}
String tradeStatus = params.get("trade_status");
boolean paid = "TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus);
if (!paid) {
return "success";
}
// 必须实现幂等:订单已经支付时不能重复记账或发货。
orderService.markPaidIfUnpaid(
order.getOutTradeNo(),
params.get("trade_no"),
notifiedAmount
);
return "success";
}
}
构造参数集合时,应以 Web 框架实际收到的表单参数为准。多数框架已经完成 URL 解码。在没有确认的情况下不要再次解码,否则可能造成验签失败或字段内容发生变化。
alipay.trade.query 查询示例
import com.alipay.api.AlipayClient;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;
public AlipayTradeQueryResponse queryByOutTradeNo(
AlipayClient alipayClient,
String outTradeNo
) throws Exception {
AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setOutTradeNo(outTradeNo);
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
request.setBizModel(model);
AlipayTradeQueryResponse response = alipayClient.execute(request);
if (!response.isSuccess()) {
System.err.println("code=" + response.getCode());
System.err.println("msg=" + response.getMsg());
System.err.println("subCode=" + response.getSubCode());
System.err.println("subMsg=" + response.getSubMsg());
}
return response;
}
如果要使用支付宝交易号查询,应改为:
AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setTradeNo(tradeNo);
不要把不确定的值同时填入 trade_no 和 out_trade_no。应先检查异步通知的原始参数,确认编号类型后再查询。
注意事项
- 验签时必须使用支付宝公钥,不能使用应用公钥或商户应用私钥。
- 除了校验
sign,还必须核对app_id、seller_id、订单号和金额。 passback_params等字段可能经过 URL 编码,应按对应产品文档处理。- 金额应使用
BigDecimal、整数分或其他精确方式保存和比较。 - 异步通知可能重复发送,订单更新、积分、库存和发货操作都需要具备幂等性。
- 不要只根据前端同步跳转结果判断支付成功。
- 查询接口只是确认支付结果的手段之一。如果查询结果长期与通知冲突,应暂停履约、保存原始报文并进行排查。
- 日志应保留
out_trade_no、trade_no、app_id、seller_id、trade_status、查询返回码和请求环境,但不能输出私钥及其他敏感信息。
备注:内容仅供参考。
