PAYATHON 2026

小程序周期扣款签约时报 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,需要自行签名,应按以下顺序处理:

  1. 移除 sign 参数。
  2. 忽略值为空的参数,具体规则以当前接口的签名规范为准。
  3. 按参数名排序。
  4. 使用原始参数值拼接待签名字符串。
  5. 使用应用私钥和请求声明的算法进行签名。
  6. 对最终请求参数进行 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、支付宝返回的请求标识,以及脱敏后的公共参数。不要将应用私钥、完整签名原文或用户敏感数据发送给他人,也不要把这些内容写入公开日志。

备注:内容仅供参考。