支付宝支付成功回调的主机地址 host 是什么?
明确结论
支付宝支付成功后,会向商户下单时配置的 notify_url 发送异步通知。请求中的 Host 通常是商户自己的回调域名,没有一个固定的“支付宝回调 Host”可供加入白名单。
例如,商户配置的地址是:
notify_url=https://pay.example.com/alipay/notify
支付宝发送通知时,请求目标为:
POST /alipay/notify HTTP/1.1
Host: pay.example.com
这里的 Host 是 pay.example.com,不是支付宝的域名。
如果请求还要经过 CDN、WAF、反向代理或网关,代理层可能会改写 Host。应用最终读到什么值,取决于商户自己的网络架构和代理配置。
为什么不能依赖支付宝 Host 判断通知来源
HTTP 请求中的 Host 表示请求要访问的目标主机。支付宝调用商户的 notify_url 时,目标主机就是商户服务器,因此 Host 不能证明请求来自支付宝。
也不建议只根据来源 IP 判断通知是否合法:
- 支付平台的出口 IP 可能发生调整;
- 请求可能经过代理、负载均衡或安全网关;
- IP 白名单只能作为辅助防护,不能代替支付宝签名验证;
- 即使
Host和来源 IP 都符合预期,未经签名验证的请求也不能用于更新订单状态。
判断通知是否合法,应以支付宝异步通知参数及其签名为准。
推荐处理步骤
1. 固定商户自己的回调域名
将 notify_url 配置为可通过公网访问的 HTTPS 地址,例如:
https://pay.example.com/alipay/notify
如果需要限制 Host,应放行 notify_url 中配置的商户域名,而不是查找所谓的支付宝回调域名:
pay.example.com
回调地址不应包含登录验证、浏览器跳转或验证码等需要交互的流程。
2. 验证支付宝通知签名
收到请求后,使用支付宝公钥和官方 SDK 验证 sign。验签需要使用支付宝提交的完整通知参数,并根据当前接入模式设置正确的字符集和签名类型。
下面是 Java 示例。具体的 SDK 类名和调用方式,以项目实际使用的支付宝 SDK 版本为准:
@PostMapping("/alipay/notify")
public String handleNotify(HttpServletRequest request) throws Exception {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((name, values) -> {
if (values != null && values.length > 0) {
params.put(name, String.join(",", values));
}
});
boolean valid = AlipaySignature.rsaCheckV1(
params,
ALIPAY_PUBLIC_KEY,
"UTF-8",
"RSA2"
);
if (!valid) {
return "failure";
}
// 验签通过后继续核对业务参数
return processPaymentNotification(params) ? "success" : "failure";
}
验签前不要删除参数、重新编码参数或自行拼接参数,否则合法通知也可能验签失败。
3. 核对关键业务参数
验签通过后,仍不能直接将订单改为已支付。还需要检查以下内容:
app_id是否等于当前商户应用的 App ID;out_trade_no是否存在,并且属于当前系统;seller_id或其他收款方信息是否符合预期;total_amount是否与订单应付金额一致;- 币种等交易信息是否符合业务要求;
trade_status是否属于系统允许处理的状态。
比较金额时应使用精确十进制类型,不要使用 double:
BigDecimal notifiedAmount = new BigDecimal(params.get("total_amount"));
BigDecimal expectedAmount = order.getTotalAmount();
if (notifiedAmount.compareTo(expectedAmount) != 0) {
return false;
}
是否把 TRADE_SUCCESS、TRADE_FINISHED 视为业务成功,要根据具体的支付宝产品和订单流程确定。不能仅因为参数存在就更新订单。
4. 保证回调处理幂等
支付宝可能重复发送同一笔交易的通知。回调处理必须保证幂等,可以结合订单状态检查、唯一索引或事务锁,避免重复记账和重复发货。
@Transactional
public boolean processPaymentNotification(Map<String, String> params) {
String orderNo = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
Order order = orderRepository.findForUpdate(orderNo);
if (order == null) {
return false;
}
if (order.isPaid()) {
return tradeNo.equals(order.getAlipayTradeNo());
}
if (!verifyBusinessFields(order, params)) {
return false;
}
order.markPaid(tradeNo);
orderRepository.save(order);
return true;
}
处理成功后,按照支付宝接口要求返回纯文本:
success
不要返回 JSON、HTML 页面或包含额外说明的字符串。处理失败时返回:
failure
Host 限制示例
如果限制 Host 是为了阻止异常域名访问应用,可以校验商户自己的域名:
String host = request.getServerName();
if (!"pay.example.com".equalsIgnoreCase(host)) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return "failure";
}
在反向代理环境中,request.getServerName() 可能来自代理转发后的请求。不要直接信任客户端可以伪造的 X-Forwarded-Host。可信代理应覆盖转发头,并在网关或 Web 服务器层固定允许访问的域名。
例如,可以在 Nginx 中只为指定域名配置回调服务:
server {
listen 443 ssl;
server_name pay.example.com;
location = /alipay/notify {
proxy_set_header Host pay.example.com;
proxy_pass http://payment_service;
}
}
注意事项
notify_url是支付宝服务器发送异步通知的地址,浏览器跳转地址不能作为支付到账的可靠依据。Host校验只能限制请求访问的商户域名,不能证明请求来自支付宝。- 不要只检查
trade_status,也不要根据前端页面显示的“支付成功”更新订单。 - 支付宝公钥、应用公钥和应用私钥的用途不同。验签时必须使用与接入方式匹配的支付宝公钥。
- 如果还要设置 IP 白名单,应通过当前支付宝官方文档或技术支持渠道获取适用于所接入产品的信息,并为 IP 变更做好处理机制。无论是否限制 IP,都必须保留签名验证和业务参数校验。
备注:内容仅供参考。
