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 的字符串中也包含字面量 &,签名一定会失效。
建议按顺序检查
- 确认
app_id与开放平台中配置密钥的应用完全一致。 - 确认当前应用采用普通公钥模式还是证书模式。
- 确认
PRIVATE_KEY对应的是该应用的应用公钥,而不是其他环境或其他应用的私钥。 - 如果重新生成过密钥,确认新的应用公钥已保存到开放平台并生效。
- 普通公钥模式使用支付宝公钥,证书模式使用三份证书文件,不要混用。
- 使用
RSA2时,确认开放平台上传的也是对应的 RSA2 应用公钥或应用公钥证书。 - 检查后端返回给前端的字符串是否与
response.getBody()完全一致。 - 检查前端是否混用了测试环境、沙箱环境和正式环境的
app_id、私钥或公钥。 - 不要只根据两段日志判断问题。题目中的订单号和时间戳不同,说明展示的生成结果和报错结果可能并非来自同一次请求。
其他注意事项
GBK 不一定会直接导致验签失败,因为 SDK 会按照声明的字符集参与签名。不过,实际项目更建议统一使用 UTF-8。如果要修改字符集,后端生成、传输以及支付宝请求中的 charset 必须保持一致,不能只修改其中一处。
total_amount 建议使用字符串:
bizContent.put("total_amount", "0.01");
这样可以避免浮点数在序列化过程中发生精度或格式变化。当前示例中的 0.01 通常不会单独造成这个错误,但金额字段使用字符串更稳妥。
这个问题应先从密钥模式和密钥配对关系入手:应用私钥用于签名,与它匹配的应用公钥供支付宝验签;支付宝公钥或支付宝公钥证书只用于商户验证支付宝返回的数据。
备注:内容仅供参考。
