PAYATHON 2026

alipay.trade.app.pay 返回 302 且无跳转地址

支付老李

结论

alipay.trade.app.pay 的正确调用流程是:服务端生成已签名的 orderString,返回给 App,再由 App 调用支付宝客户端 SDK 发起支付。服务端不应在请求网关后等待网页跳转。

因此,HTTP 302 不是该接口正常的业务结果,也不能用来判断签名是否正确。两段签名一致,只能说明两种签名实现在处理同一份原文时得到了相同结果,无法证明请求地址、传输方式、参数编码和应用配置都没有问题。

建议先检查以下两点:

  1. 网关必须使用 HTTPS:
https://openapi.alipay.com/gateway.do

如果使用了 http://openapi.alipay.com/gateway.do、错误的域名,或请求经过反向代理、WAF、统一登录网关,可能会先收到 302。

  1. 不要自行请求网关并查找 Location。App 支付应通过 Java SDK 的 sdkExecute() 生成订单字符串,然后将 response.getBody() 原样交给支付宝客户端 SDK。

推荐的服务端实现

使用支付宝 Java SDK 生成支付参数,可以减少排序、签名和 URL 编码顺序出错的可能:

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;

public class AlipayOrderService {

    private static final String GATEWAY =
            "https://openapi.alipay.com/gateway.do";

    public String createOrderString() throws AlipayApiException {
        AlipayClient alipayClient = new DefaultAlipayClient(
                GATEWAY,
                "你的应用APP_ID",
                "你的应用私钥",
                "json",
                "UTF-8",
                "支付宝公钥",
                "RSA2"
        );

        AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
        model.setOutTradeNo("唯一商户订单号");
        model.setTotalAmount("0.01");
        model.setSubject("支付测试");
        model.setProductCode("QUICK_MSECURITY_PAY");

        AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
        request.setBizModel(model);
        request.setNotifyUrl("https://你的域名/alipay/notify");

        AlipayTradeAppPayResponse response =
                alipayClient.sdkExecute(request);

        String orderString = response.getBody();
        if (orderString == null || orderString.isEmpty()) {
            throw new IllegalStateException(
                    "未生成支付宝 App 支付订单字符串,subCode="
                            + response.getSubCode()
                            + ", subMsg="
                            + response.getSubMsg()
            );
        }

        return orderString;
    }
}

服务端将 orderString 作为普通字符串返回给 App。App 收到后,直接把它传给支付宝客户端 SDK,不要解析后重新排序,也不要再次进行 URL 编码。

手动签名时的正确顺序

如果确实需要手动生成订单字符串,必须分清“待签名原文”和“最终传输字符串”。

1. 构造待签名参数

排除 sign 参数和空值参数,再按参数名升序排列。此时不能预先对参数值进行 URL 编码:

app_id=...&biz_content={"out_trade_no":"..."}&charset=utf-8&format=JSON&method=alipay.trade.app.pay&sign_type=RSA2&timestamp=...&version=1.0

2. 计算签名

使用以下算法:

SHA256WithRSA

先对 UTF-8 编码后的待签名原文进行签名,再对签名结果进行 Base64 编码。

3. 拼装订单字符串

完成签名后,再分别对每个参数值进行 UTF-8 URL 编码,并加入 sign:

app_id=...&biz_content=...&charset=utf-8&...&sign=...

不要用已经编码过的 biz_content 参与签名,也不要重复编码完整的订单字符串,否则容易导致 % 再次变成 %25,或空格在 %20 和 + 之间发生变化。

示例代码:

String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8);

实际拼接时,应分别编码每个参数值,而不是一次性编码整条查询字符串。

为什么签名一致仍然可能失败

两段签名完全一致,说明手动代码和 SDK 使用了相同的私钥、算法及签名原文,但以下项目仍需逐一检查:

  • 是否使用正式环境的 HTTPS 网关。
  • app_id、应用私钥和支付宝公钥是否属于同一个应用及同一环境。
  • 应用是否已经签约并开通 App 支付能力。
  • timestamp 是否为当前时间,服务器时钟是否准确。
  • out_trade_no 是否符合要求并保持唯一。
  • total_amount 建议以字符串 "0.01" 传入,避免 JSON 数字格式发生变化。
  • product_code 是否为 QUICK_MSECURITY_PAY。
  • 请求编码和签名编码是否统一使用 UTF-8。
  • 签名完成后是否修改过 biz_content。
  • 请求是否经过代理、网关或负载均衡,并被这些中间环节改写。
  • 是否混用了证书模式和公钥模式。

问题中的时间戳是 2022-10-20 17:45:14。如果这不是历史日志,而是实际重放的请求,就必须换成当前时间并重新生成签名。

如何定位 302 的来源

先关闭 HTTP 客户端的自动跳转,查看最初返回的状态行和响应头:

curl -v --max-redirs 0 \
  -X POST \
  'https://openapi.alipay.com/gateway.do' \
  -H 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8' \
  --data-raw '完整请求参数'

重点检查:

HTTP/1.1 302
Location:
Server:
Via:
Set-Cookie:

如果 Location 指向 HTTPS、登录页、代理地址或公司内部域名,这个 302 很可能来自 HTTP 跳转或中间代理,而不是支付宝业务接口。

如果程序中看不到 Location,可能是 HTTP 库自动跟随了跳转,或日志只记录了响应体,没有记录响应头。可以暂时关闭自动跳转,并输出以下信息:

  • 最终请求 URL
  • HTTP 状态码
  • 全部响应头
  • 响应体
  • 是否经过代理
  • 重定向历史

记录日志时,需要对 sign、应用私钥、用户信息及其他敏感字段进行脱敏。

关于“跳转地址”

正常情况下,alipay.trade.app.pay 不会返回一个供服务端访问的 Location 地址。它返回的是客户端支付所需的订单字符串,之后的跳转和收银台拉起由支付宝客户端 SDK 处理。

如果需要在浏览器中打开支付宝收银台,应先确认业务场景是否适合使用手机网站支付或电脑网站支付接口,而不是 App 支付接口。不同接口的调用流程和返回形式并不相同。

仅凭一个缺少响应头和响应体的 302,无法判断它的具体来源。但有一点可以确定:不要再从这个状态码中寻找 App 支付订单结果。应改用官方 Java SDK 的 sdkExecute() 生成 orderString,同时检查 HTTPS 网关和中间代理,以便定位问题。

备注:内容仅供参考。