notify_url 收不到回调:http 0、3xx、403、405、500 逐层定位
notify_url 是支付平台服务端主动发起的 POST 请求,不是浏览器跳转。浏览器打开地址通常发 GET,平台发的是 application/x-www-form-urlencoded 的 POST;GET 返回 200 不代表 POST 能通。支付宝异步通知要求外网 POST 访问返回 HTTP 200,非 200 视为失败;微信支付回调要求 5 秒内应答,否则判失败重发。应答语义和重试时间表不在本文展开,这里只查一件事:这个地址本身能不能收到回调。
一、地址格式硬要求
两家规则不同,照抄容易出错。先把地址本身能通过平台校验的条件列清楚:
| 检查项 | 支付宝 | 微信支付 V3 |
|---|---|---|
| 协议前缀 | 必须带 http:// 或 https://,例 https://your.domain/notify_url.php | 公网域名必须 HTTPS;走专线接入时,用专线 NAT IP 或私有回调域名可用 HTTP |
| 自定义参数 | 不能带 ?id=123 这类参数,错误示例 https://your.domain/notify_url.php?id=123&test=abc | 不能携带参数 |
| 本地/内网地址 | 不能以 http://localhost 开头,必须外网可访问 | 不能是 localhost、127.0.0.1、192.168.x.x 等本地/内网地址 |
| 重定向 | 不能是重定向的网址 | — |
| IPv6 | 异步通知不支持 IPv6(AAAA 记录),只支持 IPv4 | — |
| IP 方式 | 支持 IP 方式,推荐用域名 | 写 IP 属常见错误 |
| 长度限制 | — | notify_url 字段长度限制 255(V3 下单接口) |
| 其他 | — | 未设置 APIv3 密钥则不会发送回调通知(V3 解密需要) |
微信支付常见错误还包括:URL 只有域名缺路径(https://www.weixin.qq.com)、不是 https 开头、缺域名(./PayNotify.aspx)、写 IP、非 URL 字符串。这些在后台配置时就会被挡,或直接导致投递失败。
二、http 0:连接层就失败
支付宝官方“异步 http0 排查方案”把这一类归为连接层失败,请求根本没到应用。常见原因分几组:
- 服务连接超时/失败:地址是内网、防火墙禁用端口、调试时打了断点。
- 未开放公网访问:云安全组未放行、系统防火墙拦截、应用只监听
127.0.0.1、Docker 未映射端口。 - 域名解析失败:
- 格式错误,如
http://http://alipay.com(多写一个http://); - DNS 故障:表现为用域名收不到、换成 IP 就正常;
- 不支持 IPv6/AAAA 记录,解析到 AAAA 就连不上。
- 格式错误,如
notify_url传值时做了 encode,也可能导致 http0。- HTTPS 收不到、换 HTTP 能收到:SSL 证书不符合要求。官方要求证书根证书在 JDK1.5 缺省内置列表内(推荐 WoSign/Verisign/GlobalSign/Geotrust/Entrust 等),也可能是部署有问题或未开 SNI。
- 非标准端口(如 11111)需逐项检查:安全组放行、系统防火墙、监听
0.0.0.0、容器映射。
三、3xx:重定向
地址被 301/302/307/308 重定向,常见来源:
- HTTP 强制跳 HTTPS;
- 框架把
/paymentNotify/alipayNotify重定向到带尾斜杠的/paymentNotify/alipayNotify/; - 域名换了,旧域名 301 到新域名。
支付宝明确说不能重定向:若重定向,收不到 success,被判异常而重发;平台未必按预期跟随,跟随也可能改方法或丢 body。配置地址应直接命中最终处理接口,不要再让中间层跳一次。
四、4xx / 5xx:到了服务器但被挡或报错
- 405:路由只注册了 GET,没开 POST。
- 404:路径拼错;或被统一登录/CSRF/权限过滤器拦截返回 404/403。
- 403:WAF、CC 防护、爬虫指纹、IP 白名单。微信侧有回调出口 IP 段白名单,如
101.226.103.0/25、140.207.54.0/25、121.51.58.128/25、183.3.234.0/25、58.251.80.0/25、121.51.30.128/25、203.205.219.128/25等,退款/分账另有 IP。 - 500:业务代码抛异常;或接口只接收 JSON(
@RequestBody)导致表单参数解析失败。
五、200 但“以为没收到”
- 改过地址后通知仍发往旧地址(地址漂移),查商户后台配置与请求实际传值。
- 验签失败被静默吞掉,没有日志。
- 全局响应包装器把
success变成{"code":200,"message":"success"},平台仍判失败。 notify_url只写在前端或配置文件里没随请求传给 SDK;支付宝notify_url属公共参数,不能塞进biz_content。
六、可复现验证
先发一条 POST,看状态码与是否 200:
curl -i -X POST 'https://pay.example.com/paymentNotify/alipayNotify/' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'out_trade_no=TEST001&trade_status=TRADE_SUCCESS'
再看有没有 Location:
curl -v -X POST 'https://pay.example.com/paymentNotify/alipayNotify/' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'out_trade_no=TEST001&trade_status=TRADE_SUCCESS'
关键点:从与服务器不同网络、不同云的机器发起;本机或内网 curl 成功不代表外网通。用 curl -4 强制 IPv4,验证 AAAA 记录陷阱:
curl -4 -i -X POST 'https://pay.example.com/paymentNotify/alipayNotify/' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'out_trade_no=TEST001&trade_status=TRADE_SUCCESS'
用 --resolve 模拟平台侧 DNS 解析:
curl --resolve pay.example.com:443:1.2.3.4 -i -X POST \
'https://pay.example.com/paymentNotify/alipayNotify/' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'out_trade_no=TEST001&trade_status=TRADE_SUCCESS'
沙箱和生产两套地址要成套换,不要只换其中一处。
七、Nginx 反代推荐做法
server {
listen 443 ssl;
server_name pay.example.com;
ssl_certificate /path/fullchain.pem;
ssl_certificate_key /path/private.key;
location = /paymentNotify/alipayNotify/ {
proxy_pass http://127.0.0.1:11111/paymentNotify/alipayNotify/;
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;
}
}
正式环境不建议公网 IP + 高位端口 + 纯 HTTP,用已备案域名 + 443 反代到内网。
八、日志分层
按请求经过的层逐级看,不要一上来就翻业务代码:
- 安全组/LB 访问日志:有没有这一条连接。
- Nginx/WAF 日志:有没有请求、状态码多少、有没有 3xx/403。
- 应用入口日志:路由有没有命中、方法是不是 POST。
- 验签与订单处理日志:验签结果、金额、订单号。
判据:Nginx 无记录 = 网络/地址/格式问题;Nginx 有、应用无 = 反代/路由/容器;应用有但订单没变 = 业务/验签/事务。
九、兜底
不保证通知最终送达,必须接主动查询(支付宝 alipay.trade.query;微信查单接口)对账补单。