小程序周期扣款签约时报 isv.invalid-signature 如何解决?
结论
isv.invalid-signature 表示支付宝无法使用当前应用配置的公钥验证请求签名。这个错误通常与周期扣款的业务参数无关,常见原因包括:
- 请求中的
app_id与私钥所属应用不一致。 - 服务端使用了错误的应用私钥。
- 开放平台配置的应用公钥与当前私钥不匹配。
- 混用了公钥模式和证书模式。
- 签名算法、字符集或待签名字符串的拼装方式有误。
- 完成签名后又修改了请求参数。
sign被重复 URL 编码,或者参数经过 URL 编码后才参与签名。
排查时,先核对密钥和应用配置,再检查整个签名过程。反复修改周期扣款的业务参数通常解决不了这类签名错误。
首先核对应用和密钥
确认签约请求使用的以下配置全部属于同一个支付宝开放平台应用:
app_id
应用私钥
开放平台中配置的应用公钥
签名方式
网关环境
需要注意:
- 服务端使用“应用私钥”签名。
- 支付宝使用开放平台保存的“应用公钥”验签。
- 服务端验证支付宝响应时使用“支付宝公钥”,不能用它给请求签名。
- 沙箱应用和正式应用有各自的
app_id、密钥及网关,不能交叉使用。
可以通过支付宝开放平台提供的密钥工具,检查应用私钥和应用公钥是否属于同一密钥对。如果近期重新生成过密钥,还需要确认服务端已经更新私钥,同时新的应用公钥已提交到开放平台并生效。
检查公钥模式和证书模式
支付宝接口通常支持公钥模式和证书模式,两种模式的客户端初始化方式不同。
普通公钥模式需要配置:
应用私钥
支付宝公钥
证书模式通常还需要:
应用公钥证书
支付宝公钥证书
支付宝根证书
使用证书模式时,不能只替换一个公钥字符串,然后继续按普通公钥模式调用。使用普通公钥模式时,也不应携带根据错误证书计算出的证书序列号。
如果项目使用官方 SDK,优先采用 SDK 提供的证书初始化方法,不要手工计算 app_cert_sn 或 alipay_root_cert_sn。
检查请求签名规则
如果没有使用官方 SDK,需要自行签名,应按以下顺序处理:
- 移除
sign参数。 - 忽略值为空的参数,具体规则以当前接口的签名规范为准。
- 按参数名排序。
- 使用原始参数值拼接待签名字符串。
- 使用应用私钥和请求声明的算法进行签名。
- 对最终请求参数进行 URL 编码并发送。
示意代码如下:
Map<String, String> params = new HashMap<>();
params.put("app_id", appId);
params.put("method", "alipay.user.agreement.page.sign");
params.put("format", "JSON");
params.put("charset", "UTF-8");
params.put("sign_type", "RSA2");
params.put("timestamp", timestamp);
params.put("version", "1.0");
params.put("biz_content", bizContent);
List<String> names = new ArrayList<>(params.keySet());
Collections.sort(names);
String content = names.stream()
.filter(name -> params.get(name) != null && !params.get(name).isEmpty())
.map(name -> name + "=" + params.get(name))
.collect(Collectors.joining("&"));
String sign = AlipaySignature.rsa256Sign(
content,
appPrivateKey,
"UTF-8"
);
params.put("sign", sign);
这段代码仅用于展示签名顺序。实际项目中,建议直接使用与当前接入环境匹配的支付宝官方 SDK,让 SDK 负责参数排序、签名和请求编码。
使用官方 SDK 时的检查方式
以 Java SDK 的普通公钥模式为例,客户端的各项配置必须保持一致:
AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl,
appId,
appPrivateKey,
"JSON",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayUserAgreementPageSignRequest request =
new AlipayUserAgreementPageSignRequest();
request.setBizContent(bizContent);
// 根据小程序周期扣款接口的实际接入文档,
// 调用对应的 execute、pageExecute 或 sdkExecute 方法。
最容易混淆的是下面两个参数:
appPrivateKey // 应用私钥,用于请求签名
alipayPublicKey // 支付宝公钥,用于响应验签
把“应用公钥”填入 alipayPublicKey,一般不会导致请求签名失败,但会造成响应验签失败。使用“支付宝公钥”或其他应用的私钥给请求签名,则可能直接收到 isv.invalid-signature。
排查待签名字符串是否被改变
确认密钥无误后,可以记录并对比以下信息:
app_id
method
charset
sign_type
签名前的完整参数集合
最终待签名字符串
生成的 sign
实际发送的 HTTP 请求参数
检查时尤其要注意:
biz_content是否在签名后被重新序列化。- JSON 中的空格、转义字符、斜杠或中文是否发生变化。
timestamp等公共参数是否在签名后更新。charset声明为UTF-8,实际却采用其他编码。- Base64 签名中的
+是否被表单解析为空格。 sign是否经过两次 URL 编码。- 参数值是否先经过 URL 编码,再参与签名。
- 网关实际收到的参数是否与本地签名参数完全一致。
例如,签名时使用的是:
biz_content={"product_code":"...","external_agreement_no":"A001"}
发送前却被格式化成:
biz_content={ "product_code": "...", "external_agreement_no": "A001" }
这两段 JSON 表达的业务含义相同,但原始字符串不同,生成的签名也会不同。
小程序接入时的常见误区
周期扣款签约同时涉及小程序端和商户服务端时,开放接口的签名通常应由商户服务端完成。不要把应用私钥放入小程序代码,也不要让小程序自行拼接支付宝开放接口的请求签名。
建议按以下流程处理:
小程序请求商户服务端
↓
商户服务端调用签约接口并使用应用私钥签名
↓
服务端将接口要求的签约字符串或结果返回小程序
↓
小程序调用对应签约能力
服务端生成签约字符串后,如果小程序将其拆解、重新排序或重新编码,也可能破坏原有签名。前端应按照接口要求,原样传递服务端返回的签约内容。
仍无法解决时
可以使用同一套 app_id 和密钥,通过官方 SDK 发起一次最小化请求:
- 如果官方 SDK 可以成功,问题很可能出在自定义签名、参数编码或代理转发环节。
- 如果官方 SDK 仍返回相同错误,重点检查应用公钥是否配置正确、密钥是否属于当前应用,以及沙箱环境和正式环境是否混用。
- 如果只有部分服务器失败,检查各实例使用的私钥、环境变量和配置中心版本是否一致。
向支付宝技术支持反馈时,可以提供请求时间、接口名称、app_id、支付宝返回的请求标识,以及脱敏后的公共参数。不要将应用私钥、完整签名原文或用户敏感数据发送给他人,也不要把这些内容写入公开日志。
备注:内容仅供参考。