PAYATHON 2026

alipay.trade.app.pay 调用提示 isv.invalid-signature

支付阿杰

明确结论

isv.invalid-signature 通常不是由 alipayCertPublicKey_RSA2.crt 是否删除换行引起的。更常见的原因是:

后端签名使用的 PRIVATE_KEY,与支付宝开放平台当前应用配置的“应用公钥”或“应用公钥证书”不匹配。

先分清以下三类密钥材料:

  • 应用私钥:由商户保存,用于给请求签名。
  • 应用公钥 / 应用公钥证书:配置在支付宝开放平台,支付宝用它验证商户签名。
  • 支付宝公钥 / 支付宝公钥证书:商户用它验证支付宝返回的数据,不能代替应用公钥。

alipayCertPublicKey_RSA2.crt 是支付宝公钥证书,不是应用公钥。把它传入 DefaultAlipayClient 的 PUBLIC_KEY 参数,无法解决支付宝校验请求签名失败的问题。

另外,sdkExecute() 在 alipay.trade.app.pay 中主要负责生成签名后的 orderStr,不表示支付宝已经受理或验证了这笔支付。前端把 orderStr 交给 my.tradePay 后,支付宝才会进行验签。

为什么修改 PUBLIC_KEY 没有效果

当前代码使用的是普通公钥模式:

AlipayClient alipayClient = new DefaultAlipayClient(
    "https://openapi.alipay.com/gateway.do",
    "xx",
    PRIVATE_KEY,
    "json",
    "GBK",
    PUBLIC_KEY,
    "RSA2"
);

其中:

  • PRIVATE_KEY 用于生成请求中的 sign。
  • PUBLIC_KEY 应当是支付宝公钥,用于校验支付宝响应。
  • 支付宝验证请求签名时,使用的是开放平台中为该 app_id 配置的应用公钥。

因此,收到 isv.invalid-signature 时,应重点确认 PRIVATE_KEY 与开放平台中的应用公钥是否属于同一对密钥,而不是反复调整 PUBLIC_KEY 的换行格式。

如果 PUBLIC_KEY 来自 alipayCertPublicKey_RSA2.crt,客户端却仍按普通公钥模式初始化,就混用了证书模式和普通公钥模式。

正确配置方式

普通公钥模式和证书模式只能选择一种,不能交叉配置。

方案一:普通公钥模式

普通公钥模式需要以下材料:

  • 应用私钥:配置在后端。
  • 与应用私钥配对的应用公钥:上传到支付宝开放平台。
  • 支付宝公钥:从支付宝开放平台获取并配置在后端。

示例:

String gateway = "https://openapi.alipay.com/gateway.do";
String appId = "实际的应用 APPID";
String appPrivateKey = "应用私钥";
String alipayPublicKey = "支付宝公钥";

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

AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();

JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", orderNo);
bizContent.put("total_amount", "0.01");
bizContent.put("subject", "aa");

request.setBizContent(bizContent.toString());

AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
String orderStr = response.getBody();

这里的 alipayPublicKey 是开放平台提供的支付宝公钥,不是应用公钥,也不是直接读取 .crt 文件得到的整段证书文本。

应用私钥建议采用 Java SDK 要求的 PKCS#8 格式。具体格式仍需以项目当前使用的支付宝 Java SDK 版本为准。

方案二:证书模式

如果开放平台选择了证书模式,就应使用完整的证书初始化方式,通常需要:

  • 应用私钥。
  • appCertPublicKey_*.crt:应用公钥证书。
  • alipayCertPublicKey_RSA2.crt:支付宝公钥证书。
  • alipayRootCert.crt:支付宝根证书。

典型配置如下:

CertAlipayRequest certRequest = new CertAlipayRequest();
certRequest.setServerUrl("https://openapi.alipay.com/gateway.do");
certRequest.setAppId("实际的应用 APPID");
certRequest.setPrivateKey(APP_PRIVATE_KEY);
certRequest.setFormat("json");
certRequest.setCharset("UTF-8");
certRequest.setSignType("RSA2");

certRequest.setCertPath("/path/to/appCertPublicKey_xxx.crt");
certRequest.setAlipayPublicCertPath("/path/to/alipayCertPublicKey_RSA2.crt");
certRequest.setRootCertPath("/path/to/alipayRootCert.crt");

AlipayClient alipayClient = new DefaultAlipayClient(certRequest);

不同版本的支付宝 Java SDK 在证书初始化类和构造方法上可能略有差异,应以项目实际依赖版本提供的 API 为准。不过,各项配置的对应关系不会改变:

  • certPath 是应用公钥证书。
  • alipayPublicCertPath 是支付宝公钥证书。
  • rootCertPath 是支付宝根证书。
  • privateKey 必须与应用公钥证书中的公钥配对。

不能从 alipayCertPublicKey_RSA2.crt 中取出“第一段内容”,再把它当成普通模式的应用公钥使用。

前端必须原样传递 orderStr

后端生成 orderStr 后,应直接返回给前端:

AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
return response.getBody();

前端调用时也要原样传入:

my.tradePay({
  orderStr: orderStr,
  success: (res) => {
    console.log(res);
  },
  fail: (err) => {
    console.error(err);
  }
});

传递过程中不要进行以下处理:

  • 不要拆分 orderStr 中的参数并重新排序。
  • 不要修改 biz_content。
  • 不要删除或替换其中的 +、%2B、%3D 等字符。
  • 不要先执行 URL decode,再自行执行 URL encode。
  • 不要把 & 转换成 & 后传给 my.tradePay。
  • 不要再次对整个 orderStr 进行 JSON 序列化。
  • 不要在日志或接口响应处理时加入额外的引号、反斜杠或转义字符。

错误信息中的 & 可能只是页面或日志展示时产生的 HTML 转义。但如果实际传入 my.tradePay 的字符串中也包含字面量 &,签名一定会失效。

建议按顺序检查

  1. 确认 app_id 与开放平台中配置密钥的应用完全一致。
  2. 确认当前应用采用普通公钥模式还是证书模式。
  3. 确认 PRIVATE_KEY 对应的是该应用的应用公钥,而不是其他环境或其他应用的私钥。
  4. 如果重新生成过密钥,确认新的应用公钥已保存到开放平台并生效。
  5. 普通公钥模式使用支付宝公钥,证书模式使用三份证书文件,不要混用。
  6. 使用 RSA2 时,确认开放平台上传的也是对应的 RSA2 应用公钥或应用公钥证书。
  7. 检查后端返回给前端的字符串是否与 response.getBody() 完全一致。
  8. 检查前端是否混用了测试环境、沙箱环境和正式环境的 app_id、私钥或公钥。
  9. 不要只根据两段日志判断问题。题目中的订单号和时间戳不同,说明展示的生成结果和报错结果可能并非来自同一次请求。

其他注意事项

GBK 不一定会直接导致验签失败,因为 SDK 会按照声明的字符集参与签名。不过,实际项目更建议统一使用 UTF-8。如果要修改字符集,后端生成、传输以及支付宝请求中的 charset 必须保持一致,不能只修改其中一处。

total_amount 建议使用字符串:

bizContent.put("total_amount", "0.01");

这样可以避免浮点数在序列化过程中发生精度或格式变化。当前示例中的 0.01 通常不会单独造成这个错误,但金额字段使用字符串更稳妥。

这个问题应先从密钥模式和密钥配对关系入手:应用私钥用于签名,与它匹配的应用公钥供支付宝验签;支付宝公钥或支付宝公钥证书只用于商户验证支付宝返回的数据。

备注:内容仅供参考。