PAYATHON 2026

PC 转二维码唤起支付宝支付并签约时 signStr 应取什么内容?

支付小周

结论

这里的 signStr 不是请求字符串中的 sign 参数,也不是支付宝支付接口返回的某个业务字段。

它指的是调用 alipay.trade.app.pay 时,由支付宝 SDK 生成的完整已签名订单请求字符串,通常就是 AlipayTradeAppPayResponse#getBody() 返回的内容,例如:

app_id=...&biz_content=...&charset=...&method=alipay.trade.app.pay&sign=...&sign_type=RSA2&timestamp=...&version=1.0

具体处理步骤如下:

  1. 由 SDK 生成完整请求字符串 orderInfo。
  2. 对整个 orderInfo 做一次 URL Encode,并将结果作为外层 orderStr 的值。
  3. 拼接成:
alipays://platformapi/startApp?appId=60000157&orderStr=<URL Encode 后的完整 orderInfo>
  1. 将这条完整的 alipays:// URL 写入二维码。

问题中那段以:

alipay_sdk%3D...%26app_id%3D...%26biz_content%3D...

开头的长字符串,从结构来看,就是完整订单请求字符串经过一次外层 URL Encode 后的结果。字符串很长并不代表它有问题。

三种内容不要混淆

假设 SDK 生成的完整请求字符串是:

app_id=2021000000000000&biz_content=%7B%22subject%22%3A%22test%22%7D&charset=UTF-8&method=alipay.trade.app.pay&sign=AbCd%2BEf%3D&sign_type=RSA2&timestamp=2026-09-09+10%3A00%3A00&version=1.0

其中:

  • sign=AbCd%2BEf%3D 只是完整请求中的签名参数。
  • 整条字符串才是 SDK 生成的 orderInfo。
  • 对整条 orderInfo 再编码一次,得到的才是要放入 orderStr 的内容。

不能只取:

AbCd+Ef=

也不能只取:

sign=AbCd%2BEf%3D

支付宝 App 还需要 app_id、method、biz_content、timestamp、sign_type 等参数,才能还原并校验完整的支付请求。

为什么会出现 %25 这样的双重编码

SDK 输出的 orderInfo 通常已经采用表单参数格式,各参数值也已经过编码。例如:

biz_content=%7B%22subject%22%3A%22test%22%7D

把完整的 orderInfo 作为 orderStr 的值再次编码时,原有的 % 会变成 %25:

biz_content%3D%257B%2522subject%2522%253A%2522test%2522%257D

虽然看起来像编码了两次,但这两次编码分别属于不同层级:

  • 第一层是 SDK 对 alipay.trade.app.pay 请求参数的编码。
  • 第二层是把完整请求字符串放入 orderStr 查询参数时进行的外层编码。

这种情况通常是正常的。不要先解码 SDK 的输出,也不要对外层连续执行两次 URL Encode。

Java 示例

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;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

public class AlipayQrCodeExample {

    public static String buildAlipayQrContent() throws Exception {
        AlipayClient alipayClient = new DefaultAlipayClient(
                "https://openapi.alipay.com/gateway.do",
                "YOUR_APP_ID",
                "YOUR_MERCHANT_PRIVATE_KEY",
                "json",
                "UTF-8",
                "ALIPAY_PUBLIC_KEY",
                "RSA2"
        );

        AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
        model.setOutTradeNo("202609090001");
        model.setSubject("测试商品");
        model.setTotalAmount("0.31");
        model.setProductCode("QUICK_MSECURITY_PAY");

        // 根据实际签约产品设置 agreement_sign_params。
        // 字段名称和取值必须以当前接入产品文档及商户签约能力为准。

        AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
        request.setBizModel(model);
        request.setNotifyUrl("https://merchant.example.com/alipay/pay-notify");

        AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);

        if (!response.isSuccess()) {
            throw new IllegalStateException(
                    "生成支付宝订单字符串失败:"
                            + response.getCode() + " / "
                            + response.getSubCode() + " / "
                            + response.getSubMsg()
            );
        }

        // 完整、已签名的 App 支付请求字符串
        String orderInfo = response.getBody();

        // 只对完整 orderInfo 做一次外层编码
        String encodedOrderInfo =
                URLEncoder.encode(orderInfo, StandardCharsets.UTF_8);

        return "alipays://platformapi/startApp"
                + "?appId=60000157"
                + "&orderStr="
                + encodedOrderInfo;
    }
}

不同版本的 Java SDK 在方法重载和返回对象方面可能略有差异,但判断标准相同:要取得 SDK 为 alipay.trade.app.pay 生成的完整请求正文,不能只读取 sign 字段。

正确的数据流

业务参数
    ↓
Alipay SDK 组装参数并使用商户私钥签名
    ↓
完整 orderInfo
    ↓
对整个 orderInfo 做一次 URL Encode
    ↓
alipays://platformapi/startApp?appId=60000157&orderStr=...
    ↓
将完整 URL 写入二维码

最终格式应类似:

alipays://platformapi/startApp?appId=60000157&orderStr=app_id%3D2021000000000000%26biz_content%3D%257B...%257D%26charset%3DUTF-8%26method%3Dalipay.trade.app.pay%26sign%3D...%26sign_type%3DRSA2%26timestamp%3D...%26version%3D1.0

不要把最终 URL 整体再次编码成:

alipays%3A%2F%2Fplatformapi%2FstartApp...

二维码中保存的应是能够直接识别的 alipays:// URL。

常见错误

只拼接 sign 参数

下面这种格式是错误的:

alipays://platformapi/startApp?appId=60000157&orderStr=<RSA2签名值>

单独的签名值不能代表完整订单。

对 SDK 返回值重复编码

错误示例:

String value1 = URLEncoder.encode(orderInfo, StandardCharsets.UTF_8);
String value2 = URLEncoder.encode(value1, StandardCharsets.UTF_8);

如果把 value2 放入 orderStr,支付宝 App 解码一层后得到的仍是编码文本,无法按照正常的 key=value&key=value 格式解析请求。

手工拼装或改写 SDK 返回字符串

SDK 完成签名后,不要调整参数顺序、替换字符或修改 biz_content,也不要手工转换 +、%2B、空格等字符。签名校验以签名前的规范参数为依据,改动内容可能造成验签失败。

使用错误字符集

业务参数、SDK 配置、外层编码和二维码文本建议统一使用 UTF-8:

URLEncoder.encode(orderInfo, StandardCharsets.UTF_8)

示例中出现 charset=GBK 不一定会直接导致失败,但可能造成商品名称、签约参数或回调数据使用不同字符集。除非现有接入明确要求 GBK,否则优先统一使用 UTF-8。

把 JSON 中的 URL 转义错了

sign_notify_url 和 notify_url 应当是合法且可通过公网访问的回调地址。需要检查签名前的实际 JSON,确认其中没有多余的反斜杠,例如:

"http:\\/\\/www.merchant.com\\/receive_sign_notify"

JSON 序列化时对 / 的转义有时可以正常解析,但不要人为添加额外的反斜杠。最好交给 SDK 或正规的 JSON 序列化库处理。

二维码过长时的检查方向

完整支付订单中包含业务 JSON 和 RSA2 签名,所以生成的二维码内容确实可能很长。遇到问题时,可优先检查:

  • 二维码生成器是否完整保留了 alipays:// URL。
  • 是否误做了第二次外层编码。
  • 二维码尺寸是否足够,内容过长是否导致模块过密。
  • 页面是否对字符串做了 HTML 转义、截断或字符替换。
  • 是否使用支付宝 App 扫码。部分通用扫码工具无法正确处理 alipays:// Scheme。
  • SDK 返回的 response.getBody() 是否为空,接口生成过程是否成功。
  • agreement_sign_params 是否符合已开通产品的字段要求。
  • external_agreement_no、sign_scene、personal_product_code 等值是否符合当前商户的签约配置。
  • 支付回调地址和签约回调地址是否使用可访问的 HTTPS 地址。

调试时,可以分别保存并比较以下三个值的长度和内容:

System.out.println("orderInfo: " + orderInfo);
System.out.println("encodedOrderInfo: " + encodedOrderInfo);
System.out.println("qrContent: " + qrContent);

它们应当满足以下条件:

  • orderInfo 中可以看到普通的 & 参数分隔符。
  • encodedOrderInfo 中的参数分隔符变为 %26,等号变为 %3D。
  • qrContent 仍以未编码的 alipays://platformapi/startApp? 开头。

signStr 这个名称很容易让人误以为它只包含签名。更准确的命名是 orderInfo 或 signedOrderString,因为它表示 SDK 生成的完整已签名请求字符串,而不是单独的 RSA2 签名。

备注:内容仅供参考。