支付宝沙箱支付成功后notify回调出现301重定向如何解决?
结论
支付宝异步通知地址 notify_url 必须能由支付宝服务器直接访问。处理成功后,接口需要返回 HTTP 200 和纯文本 success,整个回调链路不能出现 301、302 等重定向。
域名已经备案、SSL 证书正常、浏览器可以打开网站,只能说明网站可供普通用户访问,并不代表回调地址符合要求。排查时应重点确认 notify_url 是否触发了 HTTP 跳转 HTTPS、域名规范化、路径补 /、登录鉴权或网关转发。
常见原因
HTTP 自动跳转到 HTTPS
代码中配置的是:
http://pay.example.com/alipay/notify
服务器却将所有 HTTP 请求重定向到:
https://pay.example.com/alipay/notify
浏览器会自动跟随跳转,因此访问时看不出异常,但支付宝收到的第一次响应仍然是 301。
解决方法是直接将 notify_url 配置为最终的 HTTPS 地址。
域名发生跳转
例如:
https://example.com/alipay/notify
被重定向到:
https://www.example.com/alipay/notify
也可能从带 www 的域名跳转到不带 www 的域名。支付宝请求的域名需要与最终处理回调的域名一致。
路径自动补斜杠
某些 Web 框架或服务器会将:
/alipay/notify
重定向为:
/alipay/notify/
反向处理也可能发生。需要检查路由定义,并在 notify_url 中填写不会触发跳转的最终路径。
回调接口经过统一鉴权
如果通知接口被登录中间件、单点登录、权限过滤器或 WAF 拦截,请求可能会被重定向到登录页或认证页面。支付宝发送异步通知时不会携带网站用户的 Cookie,因此通知接口必须允许匿名访问,再通过支付宝签名验证请求是否真实。
反向代理配置不正确
Nginx、Ingress、负载均衡或 CDN 对协议判断错误时,也可能反复执行 HTTPS 跳转。例如,代理已经接收到 HTTPS 请求,但应用层仍将其识别为 HTTP,于是继续返回重定向。
排查步骤
先确认程序实际提交给支付宝的 notify_url,不要只查看配置文件。生成最终请求参数时,环境变量覆盖、域名拼接或 URL 编码都可能改变这个地址。
接着使用 curl 模拟 POST 请求。不要添加 -L,否则 curl 会自动跟随跳转,掩盖第一次响应:
curl -i -X POST 'https://pay.example.com/alipay/notify' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'out_trade_no=test123&trade_status=TRADE_SUCCESS'
正确的网络层响应应类似:
HTTP/1.1 200 OK
Content-Type: text/plain
success
如果收到下面这样的响应,应根据 Location 查找跳转来源:
HTTP/1.1 301 Moved Permanently
Location: https://www.example.com/alipay/notify
还可以分别检查几个可能涉及的地址:
curl -I 'http://pay.example.com/alipay/notify'
curl -I 'https://pay.example.com/alipay/notify'
curl -I 'https://pay.example.com/alipay/notify/'
需要注意,支付宝通知通常使用 POST,而 curl -I 发送的是 HEAD 请求。最终判断仍应以 POST 测试结果和服务器日志为准。
还应检查这些日志:
- CDN、WAF 或负载均衡访问日志
- Nginx/Apache 访问日志
- 应用网关日志
- 回调接口入口日志
- 框架鉴权和异常日志
如果 Nginx 日志已经记录了 301,跳转发生在代理层。如果 Nginx 返回 200,但应用日志显示重定向,则需要检查框架路由或鉴权配置。
Nginx 配置示例
站点需要强制使用 HTTPS 时,可以保留 HTTP 跳转,但支付宝的 notify_url 必须直接填写 HTTPS 地址:
server {
listen 80;
server_name pay.example.com;
return 301 https://pay.example.com$request_uri;
}
server {
listen 443 ssl;
server_name pay.example.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/private.key;
location = /alipay/notify {
proxy_pass http://127.0.0.1:8080/alipay/notify;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
}
对应配置应直接使用:
https://pay.example.com/alipay/notify
不要继续使用会返回 301 的 HTTP 地址。
如果应用根据 X-Forwarded-Proto 判断原始协议,还要确认框架已经正确信任反向代理头,否则可能形成重复跳转。
回调处理示例
下面的示例只说明处理结构。签名验证必须使用支付宝官方 SDK,并使用支付宝公钥完成,不能自行拼接参数或简化验证逻辑。
@PostMapping(
value = "/alipay/notify",
produces = "text/plain;charset=UTF-8"
)
@ResponseBody
public String notify(HttpServletRequest request) {
Map<String, String> params = extractAlipayParams(request);
boolean verified = verifyWithAlipaySdk(params);
if (!verified) {
return "failure";
}
String tradeStatus = params.get("trade_status");
String outTradeNo = params.get("out_trade_no");
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
processOrderIdempotently(outTradeNo, params);
}
return "success";
}
接口处理成功后,应直接输出:
success
不要返回 JSON:
{"status":"success"}
也不要返回 HTML、页面跳转或下面这样的内容:
SUCCESS
注意事项
notify_url是服务端异步通知地址,不是用户支付完成后由浏览器跳转的页面。- 回调接口必须支持支付宝实际发送的 POST 请求,不能只测试浏览器 GET 访问。
- 接口应排除登录校验和 CSRF 页面拦截,但仍须严格执行支付宝签名验证。
- 收到通知后,还要校验
app_id、商户订单号、金额等业务字段,不能只根据trade_status更新订单。 - 支付宝可能重复发送回调,订单处理必须具备幂等性。
- 回调接口不宜执行耗时任务。完成必要的校验和落库后,应尽快返回
success,后续业务可以交给队列处理。 - 如果 HTTPS 地址本身没有重定向,但支付宝仍报告异常,应继续检查证书链是否完整、TLS 兼容性、DNS 解析、防火墙、WAF/CDN 策略,以及公网 IPv4 和 IPv6 的实际访问结果。
排查这类问题时,重点不是备案或证书是否已经配置,而是支付宝请求的精确 URL 在第一次响应时是否返回 200。消除首次响应中的 301,并确保接口返回纯文本 success,异步通知才能正常完成。
备注:内容仅供参考。
