alipay.trade.query 查询数据有误及金额不一致
结论
alipay.trade.query 的查询结果与异步通知金额不一致,通常不是金额精度导致的,常见原因有:
- 查询结果和异步通知对应的不是同一笔支付宝交易;
- 对比的是业务含义不同的金额字段;
- 查询前订单状态已发生变化,例如产生了退款;
- 异步通知没有通过验签,或业务系统重复、错误地更新了订单数据。
排查时,先用异步通知中的 trade_no 发起查询,并核对 app_id、seller_id 和 out_trade_no。确认两边对应同一笔交易后,再比较同名金额字段。
先确认是否为同一笔交易
alipay.trade.query 支持按 trade_no 或 out_trade_no 查询。如果请求中同时传入两者,需要确保它们指向同一笔交易。排查金额差异时,建议直接使用支付宝异步通知中的 trade_no:
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setTradeNo(notifyTradeNo);
request.setBizModel(model);
AlipayTradeQueryResponse response = alipayClient.execute(request);
查询完成后,逐项核对这些字段:
| 核对项 | 异步通知 | 查询响应 |
|---|---|---|
| 支付宝交易号 | trade_no | trade_no |
| 商户订单号 | out_trade_no | out_trade_no |
| 应用 | app_id | 当前查询使用的 app_id |
| 收款账号 | seller_id | seller_id |
| 订单金额 | total_amount | total_amount |
| 商家实收金额 | receipt_amount | receipt_amount |
如果 trade_no 不一致,两边的金额就不能直接比较。这类问题常见于商户订单号被重复使用、查询时连接了错误的应用、沙箱和生产环境混用,或业务系统将另一笔通知关联到了当前订单。
不要混淆金额字段
支付宝交易包含多个金额字段,各自含义不同:
total_amount:订单总金额;receipt_amount:商家实际收到的金额;buyer_pay_amount:买家实际支付的金额;point_amount:通过积分等渠道支付的金额;invoice_amount:可开具发票的金额;refund_amount:已经退款的金额,是否返回以及具体字段应以实际接口响应为准。
交易使用优惠、积分、红包或平台补贴时,buyer_pay_amount、receipt_amount 与 total_amount 可能并不相等。因此,不能拿异步通知中的 total_amount 与查询响应中的 receipt_amount 比较。
应当比较同名字段:
BigDecimal notifyReceiptAmount =
new BigDecimal(notifyParams.get("receipt_amount"));
BigDecimal queryReceiptAmount =
new BigDecimal(response.getReceiptAmount());
if (notifyReceiptAmount.compareTo(queryReceiptAmount) != 0) {
// 记录 trade_no、out_trade_no、app_id、seller_id 及完整查询响应,
// 暂停自动更新金额,进入差异排查流程。
}
金额应使用 BigDecimal,也可以转换成以“分”为单位的整数处理,不要使用 double:
long amountInCent = new BigDecimal(amount)
.movePointRight(2)
.longValueExact();
检查订单是否发生退款等状态变化
异步通知记录的是通知产生时的交易信息,alipay.trade.query 返回的则是查询当时的订单状态。如果付款完成后发生退款、撤销或其他资金变动,由于两次读取的时间不同,部分金额或状态字段可能存在差异。
判断时需要结合以下信息:
trade_status;- 退款相关字段;
- 商户退款记录;
- 支付宝账单或资金明细;
- 异步通知的接收时间和查询时间。
不要因为某一个金额字段发生变化,就覆盖本地已经确认的支付记录。支付金额与退款金额应分别保存,避免将“退款后的净额”误认为原始支付金额。
验证异步通知真实性
只有通过验签的异步通知才能用于更新订单。以 Java SDK 为例:
boolean signVerified = AlipaySignature.rsaCheckV1(
notifyParams,
alipayPublicKey,
charset,
signType
);
if (!signVerified) {
throw new IllegalArgumentException("支付宝异步通知验签失败");
}
验签成功后,还要校验关键业务字段:
if (!expectedAppId.equals(notifyParams.get("app_id"))) {
throw new IllegalStateException("app_id 不匹配");
}
if (!expectedSellerId.equals(notifyParams.get("seller_id"))) {
throw new IllegalStateException("seller_id 不匹配");
}
if (!expectedOutTradeNo.equals(notifyParams.get("out_trade_no"))) {
throw new IllegalStateException("out_trade_no 不匹配");
}
BigDecimal expectedAmount = new BigDecimal(orderAmount);
BigDecimal notifiedAmount = new BigDecimal(notifyParams.get("total_amount"));
if (expectedAmount.compareTo(notifiedAmount) != 0) {
throw new IllegalStateException("订单金额不匹配");
}
通知处理还需要保证幂等。同一个 trade_no 可能触发多次通知,重复接收时不能反复累加或覆盖金额。
推荐排查顺序
- 保存异步通知的原始参数,并完成验签。
- 读取通知中的
trade_no。 - 仅使用该
trade_no调用alipay.trade.query。 - 核对
trade_no、out_trade_no、app_id和seller_id。 - 比较同名金额字段,注意区分
total_amount、receipt_amount和buyer_pay_amount。 - 检查付款后是否发生退款、撤销等操作。
- 检查本地通知处理是否存在重复消费、串单或金额字段被覆盖的问题。
- 如果同一
trade_no的同名字段仍出现无法解释的差异,保留完整请求、响应和通知时间,提交支付宝技术支持核查。
日志中不要写入私钥、应用密钥或完整的敏感信息,也不要通过四舍五入强行消除金额差异。
备注:内容仅供参考。
