PAYATHON 2026

alipay.trade.app.pay 调用 my.tradePay 后验签失败

支付老李

结论

网关显示的待验签字符串没有 sign 字段,这是正常的。sign 是需要校验的签名值,不能参与待验签字符串的拼接。

isv.invalid-signature 表示支付宝使用当前应用配置的公钥校验 sign 失败。常见原因包括:

  • 前端没有把 SDK 返回结果中的 body 原样传给 my.tradePay。
  • orderStr 在传输过程中被 URL 解码、重新编码、拼接或修改。
  • 服务端签名所用的应用私钥,与支付宝开放平台中该 app_id 配置的应用公钥不配对。
  • 混用了沙箱、正式环境或不同应用的配置。
  • charset、sign_type 等参数与实际签名过程不一致。

为什么验签字符串中没有 sign

支付宝验签的大致过程是:

  1. 从请求中取出 sign,将其作为待校验的签名值。
  2. 排除 sign 字段。
  3. 按规则将其余参数拼成待验签字符串。
  4. 使用当前应用配置的公钥验证签名。

所以,错误信息中显示的内容:

alipay_sdk=...&app_id=...&biz_content=...&charset=GBK&...&sign_type=RSA2&...

是支付宝重新构造的待验签字符串。它不包含 sign 才是正确的,不能由此判断支付宝没有收到 sign。

错误信息中还可能出现下面这类变化:

%7B%22out_trade_no%22...%7D

显示为:

{"out_trade_no":"..."}

以及:

2023-06-04+14%3A21%3A56

显示为:

2023-06-04 14:21:56

这些通常只是 URL 编码前后的不同表现。

首先检查前端传参

示例中的服务端返回值外面还有一层 JSON:

{
  "body": "alipay_sdk=...&sign=...&sign_type=RSA2&..."
}

调用 my.tradePay 时,应传入 body 字段的值,不能把整个 JSON 对象或序列化后的 JSON 字符串作为 orderStr。

正确写法:

my.request({
  url: 'https://example.com/createOrder',
  method: 'POST',
  success(res) {
    const orderStr = res.data.body;

    my.tradePay({
      orderStr,
      success(result) {
        console.log('支付结果:', result);
      },
      fail(error) {
        console.error('调用失败:', error);
      }
    });
  }
});

以下写法是错误的:

my.tradePay({
  orderStr: res.data
});

以及:

my.tradePay({
  orderStr: JSON.stringify(res.data)
});

还要确认前端没有对 orderStr 做过这些处理:

decodeURIComponent(orderStr);
encodeURIComponent(orderStr);
orderStr.replace(...);
orderStr.split('&').sort().join('&');

服务端生成的支付参数必须原样传给 my.tradePay。

服务端应直接返回 SDK 生成的字符串

使用 Java SDK 时,可以通过 sdkExecute 生成 APP 支付参数:

AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();

AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo("1665242463830409216");
model.setTotalAmount("0.01");
model.setSubject("aa");
model.setProductCode("QUICK_MSECURITY_PAY");

request.setBizModel(model);
request.setNotifyUrl("https://example.com/alipay/notify");

AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);

if (!response.isSuccess()) {
    throw new IllegalStateException(
        response.getCode() + ": " + response.getSubMsg()
    );
}

String orderStr = response.getBody();

接口可以把结果包装在一个含义明确的字段中:

Map<String, String> result = new HashMap<>();
result.put("orderStr", orderStr);
return result;

前端读取对应字段:

const orderStr = res.data.orderStr;

my.tradePay({
  orderStr
});

不要自行拼接或重新签名 response.getBody()。

核对应用私钥和应用公钥

alipay.trade.app.pay 的请求签名应使用商户自己的应用私钥。支付宝验签时,则使用该应用在开放平台配置的应用公钥。

请逐项核对:

  1. 服务端使用的 app_id 是否正确。
  2. 私钥是否属于这个 app_id。
  3. 支付宝开放平台配置的应用公钥是否与该私钥配对。
  4. 当前配置使用的是“应用公钥”还是公钥证书模式,是否与 SDK 的初始化方式一致。
  5. 是否误把“支付宝公钥”当成了“应用公钥”。
  6. 更新密钥后,服务端是否仍在使用旧私钥。
  7. 沙箱和正式环境是否分别使用了对应的应用及密钥。

可以在本地从私钥导出公钥,再与开放平台配置的应用公钥比较。RSA 私钥为 PKCS#8 格式时,可以使用:

openssl pkey -in app_private_key.pem -pubout -out app_public_key.pem

如果私钥内容缺少 PEM 头尾,需要先整理成合法的 PEM 文件。不要把私钥上传到在线验签网站,也不要将其发送到前端。

检查字符集和签名参数

当前 orderStr 中包含:

charset=GBK
sign_type=RSA2

签名所用的字符集、SDK 配置和请求参数必须一致。如果服务端实际按 UTF-8 处理,参数中却声明为 GBK,中文字段对应的签名字节可能不一致。

建议统一使用 UTF-8,并让 SDK 完整生成参数:

AlipayClient alipayClient = new DefaultAlipayClient(
    gatewayUrl,
    appId,
    appPrivateKey,
    "json",
    "UTF-8",
    alipayPublicKey,
    "RSA2"
);

如果当前应用必须使用 GBK,就要保证 SDK 初始化、签名过程和参数声明全部使用 GBK。不能只把 charset=GBK 改成 charset=UTF-8,因为任何签名参数发生变化后,都必须重新生成 sign。

推荐的定位方式

分别记录下面两个值,并在日志中妥善保护敏感内容:

服务端调用 sdkExecute 后得到的 response.getBody()
前端传给 my.tradePay 前的 orderStr

比较时重点检查:

  • 字符串长度是否相同。
  • sign= 是否存在。
  • %2F、%2B、%3D 是否被提前还原。
  • + 是否变成空格。
  • 是否多了引号、反斜杠或 JSON 外层。
  • 参数是否被重新排序。
  • biz_content 是否被重新序列化。
  • 字符串前后是否多出空格或换行。

如果两端字符串完全一致,应优先检查私钥、公钥、app_id、网关环境和证书模式。如果两端不一致,问题就在接口响应解析或前端传参环节。

不要把“网关验签字符串不包含 sign”当作故障点。需要确认的是,前端是否原样传递了 SDK 生成的 body,以及生成签名的应用私钥是否与当前 app_id 配置的应用公钥严格匹配。

备注:内容仅供参考。