沙箱环境 APP 支付使用 https + 域名后收不到异步通知
结论
https + 域名通常不会阻止 APP 支付异步通知。Apifox 可以访问、nginx 有日志,只能说明回调接口在当前网络环境下可用,不能证明支付平台拿到了正确的 notify_url,也不能证明支付平台的服务器可以正常完成 DNS 解析、TLS 握手和 HTTP 请求。
建议先确认以下事项:
- 创建 APP 支付订单时,是否实际传入了
notify_url。 - APP 是否直接使用服务端返回的完整支付参数,而不是自行拼装。
- 回调域名是否能从公网访问,DNS、证书链和 443 端口是否正常。
- nginx 日志中是否只有 Apifox 请求,没有支付平台发起的请求。
- 回调接口是否接受
POST表单请求,并在验签成功后返回纯文本success。 - 支付交易是否确实成功。异步通知不是判断支付结果的唯一方式,必要时还要主动查询交易状态。
第一步:确认下单请求携带了 notify_url
异步通知地址通常要在服务端创建支付请求时设置。它不是业务参数 JSON 中的普通字段,也不能只配置在 APP 客户端。
以支付宝 Java SDK 的常见写法为例:
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
request.setNotifyUrl("https://pay.example.com/alipay/notify");
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo(outTradeNo);
model.setTotalAmount("0.01");
model.setSubject("测试订单");
model.setProductCode("QUICK_MSECURITY_PAY");
request.setBizModel(model);
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
String orderString = response.getBody();
重点检查以下内容:
request.setNotifyUrl(...)是否确实执行。notify_url是否是包含协议、域名和路径的完整 URL。- 是否误把通知地址写进了
biz_content。 - APP 发起支付时,使用的是否就是
sdkExecute返回的完整orderString。 - 当前下单请求是否发往沙箱网关,并使用沙箱应用对应的
app_id和密钥。 - 是否存在多套配置,导致测试环境下单时读到了空地址或其他环境的地址。
建议在服务端记录脱敏后的下单参数,确认最终待签名参数中确实包含:
notify_url=https://pay.example.com/alipay/notify
不要只看配置文件。配置存在,不代表最终请求真的使用了它。
第二步:区分“没有通知”和“收到后处理失败”
查看 nginx 日志时,要结合支付完成时间、请求路径和请求来源进行检查。
如果日志中只有 Apifox 的访问记录,没有支付平台的请求,问题通常发生在请求到达应用之前。此时应检查:
- 下单时没有传入
notify_url。 - 沙箱订单并未真正支付成功。
- 域名无法从公网解析。
- HTTPS 证书链不完整或证书已经过期。
- 服务只对内网、VPN、白名单或 IPv6 环境开放。
- CDN、WAF、安全组或防火墙拦截了请求。
- 回调地址发生跳转,例如 HTTP 跳转到 HTTPS、域名跳转或路径自动补
/。 - 使用了非标准端口,外部网络无法访问。
如果 nginx 已经记录到支付平台发出的 POST 请求,但业务系统仍显示“没有回调”,则要继续检查:
- nginx 是否正确转发了请求体。
- 应用路由是否匹配。
- 请求是否被登录鉴权、CSRF、验证码或网关权限拦截。
- 程序是否错误地按 JSON 解析请求。
- 验签是否失败。
- 程序是否发生异常,或者没有返回
success。
可以为回调地址单独配置访问日志:
location = /alipay/notify {
access_log /var/log/nginx/alipay_notify_access.log combined;
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
排查期间应记录请求是否到达,但不要长期将完整通知参数、签名、用户信息或敏感订单数据写入公开日志。
第三步:检查公网和 HTTPS 配置
本机或 Apifox 请求成功,并不能说明支付平台服务器也具备相同的访问条件。应从另一台公网机器测试:
curl -v -X POST \
"https://pay.example.com/alipay/notify" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "out_trade_no=test&trade_status=TRADE_SUCCESS"
检查 DNS:
dig pay.example.com A
dig pay.example.com AAAA
检查 TLS 证书链:
openssl s_client \
-connect pay.example.com:443 \
-servername pay.example.com \
-showcerts
需要确认:
- 域名解析到了正确的公网地址。
- 证书的 SAN 包含当前回调域名。
- 服务器配置了完整证书链,而不只是站点证书。
- 证书没有过期,也不是自签名证书。
- TLS 协议和加密套件没有采用过于特殊的配置。
- 如果配置了 AAAA 记录,IPv6 服务也必须可达。否则应修复 IPv6 服务,或移除错误的 AAAA 记录。
- 443 端口已对公网开放,并且没有来源 IP 白名单限制。
回调地址最好直接返回业务处理结果,不要依赖 301、302 或 307 跳转。
第四步:正确接收表单并验签
支付异步通知通常使用 POST 请求,内容类型一般是:
application/x-www-form-urlencoded
不能直接按 JSON 请求体解析。
Spring Boot 示例:
@PostMapping(
value = "/alipay/notify",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
public String handleNotify(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((key, values) -> {
params.put(key, String.join(",", values));
});
try {
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!verified) {
return "failure";
}
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");
String appId = params.get("app_id");
// 1. 校验 app_id 是否属于当前应用
// 2. 根据 out_trade_no 查询本地订单
// 3. 校验订单金额、商户身份及其他关键字段
// 4. 仅在允许的状态流转条件下更新订单
// 5. 使用 out_trade_no 或 trade_no 保证幂等
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
return "success";
}
return "success";
} catch (Exception e) {
return "failure";
}
}
实际使用时,要根据所采用的 SDK、密钥模式或证书模式调用相应的验签方法。alipayPublicKey 应填写支付平台公钥,不能使用应用公钥或应用私钥。
验签前不要修改通知参数,也不要再次对参数进行 URL 编码。字符集和签名类型必须与通知参数一致。
第五步:按正确格式返回响应
处理成功后,响应体应为纯文本:
success
建议同时满足:
HTTP/1.1 200 OK
Content-Type: text/plain
不要返回这类内容:
{"code":200,"message":"success"}
也不要返回:
SUCCESS
如果支付平台没有识别到约定的成功响应,可能会继续重试。因此,回调逻辑必须支持重复通知,不能因为重复收到通知而重复发货、重复加款或重复更新库存。
第六步:主动查询支付结果
异步通知可能延迟,也可能因网络或配置问题暂时无法送达。订单系统不能一直等待通知,应设置主动查询或补偿任务。
以支付宝交易查询为例:
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setOutTradeNo(outTradeNo);
request.setBizModel(model);
AlipayTradeQueryResponse response = alipayClient.execute(request);
查询结果同样需要经过业务校验。只有确认商户订单号、金额、应用身份和交易状态都符合预期,才能将本地订单更新为已支付。
推荐排查顺序
按以下顺序排查通常更快:
- 查询交易,确认沙箱订单确实支付成功。
- 检查服务端最终下单参数中是否包含正确的
notify_url。 - 确认 APP 使用的是服务端生成的完整支付参数。
- 查看支付完成时间附近的 nginx 独立访问日志。
- 如果没有 nginx 记录,检查 DNS、TLS、端口、WAF 和网络策略。
- 如果有 nginx 记录,检查反向代理、表单解析、验签和应用异常。
- 确认处理成功后返回纯文本
success。 - 增加主动查询、幂等处理和失败补偿机制。
如果这些配置都正确,但沙箱仍未发出通知,应保留订单号、下单时间、网关响应和服务端日志,再通过对应支付平台的沙箱支持渠道核查。沙箱行为可能与正式环境不同,但在没有具体平台说明时,不应直接将问题归因于 HTTPS,或认定沙箱“不支持回调”。
备注:内容仅供参考。
