订单码支付如何获取支付回调?
结论
订单码支付的异步结果应由支付请求中指定的 notify_url 接收,应用网关通常不能替代 notify_url。如果两个地址都没有收到通知,请优先检查:
- 创建订单时是否确实传入了
notify_url。 - 订单是否已经支付成功,而不只是完成了订单创建或扫码。
notify_url能否从公网直接访问。- 回调接口是否按
POST表单方式接收参数。 - 服务端完成验签和业务处理后,是否返回纯文本
success。 - 请求是否被反向代理、WAF、登录认证或程序异常拦截。
异步通知只应作为状态变更信号。无论有没有收到通知,最终支付状态都应通过主动查询接口确认。
应用网关与 notify_url 的区别
应用网关是开放平台调用应用服务的入口,并不是支付异步通知地址。支付结果通常会发送到本次支付请求中指定的 notify_url。
以支付宝当面付接口为例,使用 SDK 时应在请求对象上设置:
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
request.setNotifyUrl("https://pay.example.com/callback/alipay");
request.setBizContent(bizContent);
不能只在平台后台配置应用网关,也不要把 notify_url 放进 biz_content。具体的传参位置应以所用支付接口及对应 SDK 的文档为准。
排查步骤
1. 确认订单请求中包含 notify_url
记录创建订单时发送的参数,确认最终请求中确实包含完整的回调地址:
https://pay.example.com/callback/alipay
建议使用固定的 HTTPS 地址,避免使用:
localhost、127.0.0.1或局域网 IP。- 临时地址或已经过期的内网穿透域名。
- 需要登录、验证码或访问鉴权的地址。
- 会发生多次
301、302跳转的地址。 - 必须依赖浏览器 Cookie 才能访问的接口。
如果 SDK 提供 setNotifyUrl 一类方法,应优先通过该方法设置。
2. 确认订单已经支付成功
“生成订单码”、“用户扫码”和”支付成功”是不同的交易状态。异步支付通知通常要等到交易状态发生相应变化后才会发送。
使用创建订单时的 out_trade_no 主动查询订单。支付宝场景可以调用:
alipay.trade.query
查询时重点核对:
out_trade_no是否与创建订单时一致。- 使用的应用、商户和运行环境是否正确。
- 订单是否已经达到支付成功状态。
- 用户是否中途取消、支付失败,或者订单是否已经关闭。
如果主动查询也找不到订单,应先检查下单请求,不必继续排查回调接口。
3. 从公网验证回调地址
在服务器外部测试 notify_url 能否访问,同时确认网络链路没有拦截 POST 请求:
curl -i -X POST 'https://pay.example.com/callback/alipay' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'out_trade_no=test_order&trade_status=TRADE_SUCCESS'
至少检查以下内容:
- DNS 是否解析到正确的公网地址。
- HTTPS 证书是否有效,证书链是否完整。
- 防火墙、安全组和 WAF 是否允许访问。
- Nginx、网关或负载均衡是否把请求转发到了正确的服务。
- 接口是否返回
404、403、405或500。 - Web 框架是否因为 CSRF、防重放或登录认证而拒绝请求。
这条测试请求没有合法签名,只能用来检查网络和路由是否通畅,不能按真实支付通知处理。
4. 按表单方式接收通知
支付回调通常不是 JSON,而是 application/x-www-form-urlencoded 格式的 POST 请求,因此不能只读取 JSON 请求体。
Spring Boot 示例:
@PostMapping(
value = "/callback/alipay",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
public ResponseEntity<String> alipayNotify(
@RequestParam Map<String, String> params) {
// 1. 验签
// 2. 校验订单号、金额、商户身份和应用身份
// 3. 幂等更新订单
// 4. 成功后返回纯文本 success
return ResponseEntity.ok()
.contentType(MediaType.TEXT_PLAIN)
.body("success");
}
如果框架获取到的是 Map<String, String[]>,验签前需要按照支付平台的要求转换参数,但不能擅自修改参数值、字符集或签名字段。
5. 正确执行验签
以支付宝 Java SDK 的 RSA2 验签方式为例:
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
验签通常使用支付宝公钥,或者采用与支付宝公钥证书相对应的验签方式,而不是应用私钥。普通公钥模式和证书模式的配置及验签方法可能不同,必须与应用当前使用的密钥模式一致。
验签失败时应重点检查:
- 是否使用了错误环境的公钥。
- 是否误把应用公钥当成支付宝公钥。
charset和sign_type是否与通知参数一致。- 验签前是否对参数做过 URL 解码、拼接、裁剪或字符替换。
- 是否遗漏了参数,或者错误处理了空值和多值参数。
- 应用是否更换过密钥,而服务器仍在使用旧配置。
即使请求来源 IP 看起来可信,也不能跳过验签。
6. 验签后继续校验业务数据
验签成功并不表示可以直接把任意订单改成已支付。至少还要校验:
out_trade_no是否存在,并且属于当前商户。- 通知中的金额是否与本地订单金额一致。
- 收款方、应用或商户标识是否符合预期。
- 当前交易状态是否属于业务认可的成功状态。
- 订单是否已经处理过,以免重复发货或重复记账。
金额建议使用 BigDecimal 比较,不要使用 double:
BigDecimal expectedAmount = order.getAmount();
BigDecimal notifiedAmount = new BigDecimal(params.get("total_amount"));
if (expectedAmount.compareTo(notifiedAmount) != 0) {
throw new IllegalStateException("支付金额不一致");
}
7. 返回平台要求的成功响应
业务处理完成后,应返回以下纯文本:
success
不要返回 JSON、HTML 页面或其他带包装的内容,例如:
{"code": 200, "data": "success"}
如果响应不符合要求,支付平台可能把这次通知判定为失败并重试。具体的重试策略以对应支付产品的当前文档为准。
推荐的完整处理结构
@PostMapping("/callback/alipay")
public ResponseEntity<String> handleNotify(
@RequestParam Map<String, String> params) {
try {
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!verified) {
return ResponseEntity.ok("failure");
}
String outTradeNo = params.get("out_trade_no");
String tradeStatus = params.get("trade_status");
String totalAmount = params.get("total_amount");
Order order = orderService.findByOutTradeNo(outTradeNo);
if (order == null) {
return ResponseEntity.ok("failure");
}
if (order.getAmount().compareTo(new BigDecimal(totalAmount)) != 0) {
return ResponseEntity.ok("failure");
}
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
orderService.markPaidIdempotently(
outTradeNo,
params.get("trade_no")
);
}
return ResponseEntity.ok("success");
} catch (Exception e) {
// 记录异常和通知标识,避免日志中泄露敏感信息
return ResponseEntity.ok("failure");
}
}
示例中的交易状态适用于常见的支付宝交易通知场景,实际处理时仍要以所调用接口的状态定义为准。生产环境还应校验商户、应用和收款方等身份字段。
仍收不到通知时的处理方式
如果公网访问、参数配置和订单状态都正常,但服务器日志里完全没有请求,可以继续检查:
- 从创建订单的请求日志中确认最终生效的
notify_url。 - 查看 CDN、WAF、负载均衡和 Nginx 的访问日志。
- 检查平台控制台是否提供异步通知记录或重发功能。
- 确认测试订单与应用、商户账号以及沙箱或生产环境完全对应。
- 使用订单查询接口补偿未收到的通知。
还可以增加定时补偿任务,主动查询长时间处于”待支付”状态的订单。查询到支付成功后,使用与回调相同的幂等逻辑更新订单。这样即使异步通知丢失、延迟或处理失败,已支付订单也不会长期停留在未支付状态。
备注:内容仅供参考。
