PAYATHON 2026

支付宝H5支付传递mc_create_trade_ip后为何其他IP也能支付?

支付阿杰

结论

mc_create_trade_ip 记录的是创建交易时的客户端 IP,通常用于记录交易来源或辅助风险识别。它不是支付 IP 白名单,也不会将订单绑定到该 IP。

因此,即使用户实际付款时使用了其他 IP,支付宝也可能正常受理并完成支付。能否付款主要取决于订单状态、支付凭证和风控校验等因素,与支付 IP 是否和 mc_create_trade_ip 一致没有直接关系。

为什么更换 IP 后仍能支付

一次 H5 支付通常包含以下步骤:

  1. 商户服务器创建订单,并向支付宝发起支付请求。
  2. 请求中传入 mc_create_trade_ip,记录创建交易时获取的客户端 IP。
  3. 用户跳转到支付宝收银台。
  4. 支付宝根据订单信息、用户身份和风险策略处理付款。

进入收银台后,用户的网络环境可能发生变化,例如:

  • 手机从 Wi-Fi 切换到移动网络;
  • 运营商重新分配出口 IP;
  • 多个用户共用 NAT 出口;
  • 用户使用代理、VPN 或企业网络;
  • 下单请求和付款请求经过不同的网络链路。

如果强制要求支付 IP 与下单 IP 完全一致,很多正常交易也会被误拦截。因此,一般不能把 mc_create_trade_ip 理解为支付阶段的强制 IP 限制条件。

不同支付宝产品和接入版本对参数的定义可能有所不同,应以当前产品对应的支付宝开放平台接口文档为准。如果接口文档没有说明该参数会“绑定付款 IP”或“限制付款 IP”,就不应将它用于访问控制。

如果业务必须限制支付来源

这类限制需要由商户系统自行实现,不能只依赖 mc_create_trade_ip。

1. 创建支付请求前校验 IP

生成支付宝支付链接或表单前,可以比较当前请求 IP 与订单创建时保存的 IP:

String orderIp = order.getCreateIp();
String currentIp = getClientIp(request);

if (!orderIp.equals(currentIp)) {
    throw new IllegalStateException("当前网络与下单网络不一致,无法发起支付");
}

// 校验通过后,再调用支付宝 H5 支付接口

这种方式只能限制用户获取支付入口时使用的 IP。用户取得支付链接后再切换网络,支付宝仍不一定会阻止付款。

2. 使用一次性支付令牌

更可靠的做法是为支付入口设置短期有效的一次性令牌,并将令牌与订单、用户绑定:

String token = paymentTokenService.create(
    order.getOrderNo(),
    currentUser.getId(),
    Duration.ofMinutes(10)
);

发起支付前验证令牌:

PaymentToken paymentToken = paymentTokenService.verify(token);

if (!paymentToken.getOrderNo().equals(orderNo)) {
    throw new IllegalArgumentException("支付令牌与订单不匹配");
}

if (!paymentToken.getUserId().equals(currentUser.getId())) {
    throw new SecurityException("当前用户无权支付该订单");
}

if (paymentToken.isUsed() || paymentToken.isExpired()) {
    throw new IllegalStateException("支付令牌已失效");
}

令牌验证通过后,应立即将其标记为已使用,防止支付链接被复制后重复利用。

3. 综合校验用户和订单状态

与单独限制 IP 相比,更适合检查以下内容:

  • 当前登录用户是否为订单所属用户;
  • 订单是否处于“待支付”状态;
  • 支付金额、商户订单号是否与服务端记录一致;
  • 支付入口是否过期;
  • 是否存在设备、地区或请求频率异常;
  • 同一订单是否被重复发起支付。

收到异步通知后,还要验证支付宝签名,并核对订单号、金额、商户身份和交易状态。不要因为回调请求的来源 IP 与下单 IP 不同,就拒绝正常通知。

正确获取客户端 IP

如果服务部署在反向代理或负载均衡器后面,直接读取 request.getRemoteAddr() 得到的可能是代理服务器地址。只有代理由商户自己控制,并且会清理外部请求头时,才能信任 X-Forwarded-For 等字段。

public String getClientIp(HttpServletRequest request) {
    String forwardedFor = request.getHeader("X-Forwarded-For");

    if (forwardedFor != null && !forwardedFor.isBlank()) {
        return forwardedFor.split(",")[0].trim();
    }

    return request.getRemoteAddr();
}

这段代码仅用于示意。如果客户端能够直接访问应用服务器,或者可以自行伪造 X-Forwarded-For,就不能把该字段作为安全判断依据。生产环境应由可信网关统一覆盖请求头,应用再读取网关确认后的客户端地址。

注意事项

  • mc_create_trade_ip 不是订单支付 IP 锁。
  • 不要只凭 IP 判断付款人身份,因为 IP 可能共享、变化或经过代理。
  • 严格绑定 IP 会影响移动网络用户,容易导致正常支付失败。
  • 支付安全应主要依靠登录身份、订单归属、短期令牌、金额校验、签名验证和风控策略。
  • 支付金额和订单状态都应以商户服务端数据为准,不能信任前端提交的值。
  • 在高风险场景中,可以将 IP 变化视为风险信号,触发短信验证、重新登录或人工审核,而不是直接将 mc_create_trade_ip 作为拦截规则。

备注:内容仅供参考。