PAYATHON 2026

支付宝扫码支付长URL经302重定向后默认签约状态改变

支付老李

结论

无论使用自建短链接、第三方短链、301/302/307/308、HTML 跳转还是 JavaScript 跳转,都不能保证支付宝支付页与“直接扫描原始 URL”完全一致,也不能保证“签约”开关仍默认开启。

支付宝 App 扫码时,处理的不只是最终 URL。二维码的原始内容、打开链路、来源域名、跳转次数、产品配置、用户状态和风控结果,都可能影响最终展示的页面。直接扫描支付宝网关地址,与扫描自有域名后再跳转,在支付宝看来属于不同的访问场景。

较为稳定的解决办法,是让第三方改用适合扫码支付的接口,例如 alipay.trade.precreate,并直接使用接口返回的 qr_code 生成二维码。也可以由第三方提供官方短链接。当前使用的 alipay.trade.page.pay 面向网页支付,并非专门为线下扫码场景设计。

如果第三方完全无法调整返回内容,只能在以下方案中选择:

  • 保留原始 URL,通过放大二维码、降低纠错级别和增加留白来提高识别率;
  • 使用短链接跳转,同时接受支付页状态可能变化;
  • 要求第三方调整支付宝产品的接入方式。这是同时解决链接长度和页面一致性问题的可靠方向。

为什么经过 302 后页面会变化

扫码入口发生了变化

直接扫描原始二维码时,二维码中的内容是:

https://openapi.alipay.com/gateway.do?...

支付宝 App 可以在扫码阶段识别出这是支付宝网关请求,再按内部规则进入相应的支付流程。

使用短链接后,二维码内容变成:

https://pay.example.com/s/abc123

支付宝最先识别到的是一个普通外部网站。即使服务器随后返回:

HTTP/1.1 302 Found
Location: https://openapi.alipay.com/gateway.do?...

最终地址虽然相同,访问上下文已经发生变化。支付宝可能把它当作浏览器重定向后的普通网页请求,而不是通过扫码直接进入的支付请求。

“默认开启签约”不一定只由 URL 决定

签约选项是否展示、默认处于什么状态,可能同时受到这些因素影响:

  • 商户已经签约的支付宝产品能力;
  • 支付接口和业务参数;
  • 用户是否已经签约,或是否满足签约条件;
  • 支付宝 App 识别到的入口类型;
  • 来源域名、重定向链路和安全策略;
  • 订单、用户及设备的实时风控结果;
  • 支付宝客户端版本和灰度策略。

所以,不能因为“最终 URL 相同”,就认定页面状态也一定相同。

从问题中展示的 biz_content 片段来看,目前没有足够信息证明签约开关的默认状态由某个明确参数控制,原始 URL 也已经被截断。缺少完整请求参数、第三方产品配置和支付宝侧日志,无法进一步确认究竟是哪项规则造成了差异。

302 无法替客户端保留扫码语义

HTTP 重定向只能通知客户端访问另一个地址,不能要求客户端继续把第二次请求视为“直接扫描支付宝二维码”。

换用其他跳转方式通常也无法解决这个问题:

  • 301:永久重定向,入口仍会改变;
  • 307、308:主要用于保留 HTTP 方法和请求体,不能恢复扫码入口;
  • HTML <meta refresh>:需要额外经过一个网页;
  • JavaScript location.href:仍属于页面内跳转;
  • iframe:可能受安全策略、跨域限制影响,支付页面也可能禁止嵌入;
  • 通用短链服务:本质上仍需要一次或多次重定向。

这些方式还可能增加安全提示、拦截概率和兼容性问题。

推荐解决方案

方案一:改用预创建扫码支付接口

如果业务目标是让用户通过支付宝 App 扫码付款,应让第三方评估使用:

alipay.trade.precreate

该接口用于创建扫码支付订单。响应中的 qr_code 可以直接用于生成二维码,不必将签名、通知地址和完整业务参数全部放入二维码。

典型流程如下:

业务系统创建订单
    ↓
服务端调用 alipay.trade.precreate
    ↓
支付宝返回 qr_code
    ↓
前端将 qr_code 生成二维码
    ↓
用户使用支付宝 App 扫码支付

下面是示意请求代码,具体写法应以第三方实际使用的支付宝 SDK 为准:

AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
request.setNotifyUrl("https://merchant.example.com/alipay/notify");

request.setBizContent("""
{
  "out_trade_no": "ORDER_202609090001",
  "total_amount": "0.32",
  "subject": "订购产品",
  "timeout_express": "10m"
}
""");

AlipayTradePrecreateResponse response = alipayClient.execute(request);

if (response.isSuccess()) {
    String qrCode = response.getQrCode();
    // 使用 qrCode 生成二维码
}

更换接口后能否继续展示签约选项,仍取决于第三方接入的签约产品,以及支付宝允许的业务组合,不能仅靠更换接口来保证。第三方需要向支付宝技术支持确认支付与签约应采用哪种组合方案。

方案二:由第三方在源头生成短标识

另一种可靠的设计,是让二维码只包含不可猜测的订单标识,例如:

https://pay.vendor.example/q/8Mf2Kp7x

用户访问后,再由第三方服务端创建或恢复支付请求。签名、订单参数和跳转规则都由支付能力提供方统一管理。

这个方案需要第三方配合。你方无法在不改变入口上下文的前提下,对已经完成签名的支付宝网关 URL 进行等价压缩。

URL 压缩并不是无损文本压缩,客户端最终仍要获得完整的目标地址。除非支付宝或第三方提供短标识解析服务,否则短 URL 必然要经过中间解析或重定向。

方案三:保留原始地址,优化二维码

如果第三方暂时无法改造,同时又必须保留“默认开启签约”的状态,建议继续将原始 URL 直接编码进二维码,并通过调整展示方式提高识别率:

  • 增大二维码的实际尺寸;
  • 保证四周留白不少于 4 个模块;
  • 使用纯黑白或其他高对比度配色;
  • 避免使用渐变、圆点、美化边框和过大的中心 Logo;
  • 不要对二维码进行有损压缩;
  • 避免通过低分辨率截图传播;
  • 在实际设备允许的情况下采用较低的纠错级别,以降低二维码密度;
  • 确保生成器使用 UTF-8,并原样编码完整 URL。

例如使用 ZXing 生成二维码:

Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
hints.put(EncodeHintType.MARGIN, 4);

BitMatrix matrix = new MultiFormatWriter().encode(
    originalUrl,
    BarcodeFormat.QR_CODE,
    800,
    800,
    hints
);

MatrixToImageWriter.writeToPath(
    matrix,
    "PNG",
    Paths.get("alipay.png")
);

降低纠错级别可以减少一部分冗余,但二维码受到污损或遮挡时,容错能力也会下降。实际采用哪种级别,需要结合打印介质和扫码距离测试。

如果仍需保留短链接

短链接服务可以按下面的方式实现,但它只能缩短二维码中的内容,无法保证签约开关的状态不变:

@GetMapping("/s/{token}")
public ResponseEntity<Void> redirect(@PathVariable String token) {
    String originalUrl = shortLinkService.findOriginalUrl(token);

    if (originalUrl == null) {
        return ResponseEntity.notFound().build();
    }

    return ResponseEntity.status(HttpStatus.FOUND)
        .location(URI.create(originalUrl))
        .cacheControl(CacheControl.noStore())
        .build();
}

实现时需要注意:

  1. Location 中的原始 URL 应原样保存并返回,不要先解析参数再重新拼接。
  2. 不要对 %2F、%2B、%3D 等内容重复编码或解码。
  3. URL 中的 + 可能表示空格,也可能是原始签名内容的一部分。错误转换会导致验签失败。
  4. 不要修改任何参与过签名的参数。
  5. 短链接应设置有效期并绑定具体订单,避免长期复用。
  6. 跳转服务应使用 HTTPS,同时防止接口被利用为开放重定向。
  7. 不要根据用户传入的任意 url 参数直接跳转。应在服务端保存 token → 原始 URL 的映射。

即使实现了字节级的原样转发,也只能避免签名损坏,不能让支付宝恢复“直接扫描原始网关地址”时的入口状态。

排查建议

可以先判断差异来自参数损坏,还是支付宝对入口的识别:

  1. 记录服务器实际返回的 Location。
  2. 将它与第三方提供的原始 URL 逐字节比较。
  3. 检查是否存在重复 URL 编码、参数截断或 + 号转换。
  4. 确认链路中是否还有第二次重定向。
  5. 使用同一个支付宝账号、同一台设备,并在相近时间内重复测试。
  6. 将两种入口的录屏、完整请求参数、订单号和发生时间交给第三方。
  7. 由第三方使用其支付宝商户身份提交技术工单,确认签约开关究竟由产品配置、业务参数还是客户端风控决定。

如果两种方式发出的最终请求确实完全一致,但页面默认状态仍有差异,基本可以判断问题出在支付宝对扫码入口或访问上下文的处理,而不是 HTTP 重定向参数本身。

安全注意事项

示例 URL 中包含 app_id、订单号、回调地址和请求签名,不建议将真实可用的完整支付 URL 发布到公开页面。签名私钥虽然不会因此直接暴露,但支付链接可能被重复访问、分析或滥用。

示例中的 return_url 和 notify_url 还使用了 HTTP 和非标准端口。生产环境应优先使用可信的 HTTPS 域名,并确认回调地址符合支付宝及企业自身的安全要求。

“默认开启签约”属于支付产品能力和支付宝页面策略的一部分,不是普通 URL 跳转可以控制的前端状态。如果第三方完全不配合,目前没有一种通用短链技术能够同时让二维码明显变短、保持扫码入口不变,并强制保留签约开关的默认状态。

备注:内容仅供参考。