alipay.trade.app.pay 调用 my.tradePay 后验签失败
结论
网关显示的待验签字符串没有 sign 字段,这是正常的。sign 是需要校验的签名值,不能参与待验签字符串的拼接。
isv.invalid-signature 表示支付宝使用当前应用配置的公钥校验 sign 失败。常见原因包括:
- 前端没有把 SDK 返回结果中的
body原样传给my.tradePay。 orderStr在传输过程中被 URL 解码、重新编码、拼接或修改。- 服务端签名所用的应用私钥,与支付宝开放平台中该
app_id配置的应用公钥不配对。 - 混用了沙箱、正式环境或不同应用的配置。
charset、sign_type等参数与实际签名过程不一致。
为什么验签字符串中没有 sign
支付宝验签的大致过程是:
- 从请求中取出
sign,将其作为待校验的签名值。 - 排除
sign字段。 - 按规则将其余参数拼成待验签字符串。
- 使用当前应用配置的公钥验证签名。
所以,错误信息中显示的内容:
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 的请求签名应使用商户自己的应用私钥。支付宝验签时,则使用该应用在开放平台配置的应用公钥。
请逐项核对:
- 服务端使用的
app_id是否正确。 - 私钥是否属于这个
app_id。 - 支付宝开放平台配置的应用公钥是否与该私钥配对。
- 当前配置使用的是“应用公钥”还是公钥证书模式,是否与 SDK 的初始化方式一致。
- 是否误把“支付宝公钥”当成了“应用公钥”。
- 更新密钥后,服务端是否仍在使用旧私钥。
- 沙箱和正式环境是否分别使用了对应的应用及密钥。
可以在本地从私钥导出公钥,再与开放平台配置的应用公钥比较。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 配置的应用公钥严格匹配。
备注:内容仅供参考。
