PAYATHON 2026

iOS 调用 AlipaySDK 报 isv.invalid-signature

支付阿杰

结论

这不是 iOS 端缺少支付参数或 URL Scheme 配置造成的。isv.invalid-signature 表示支付宝收到的订单参数,与服务端生成签名时使用的参数不一致。

从支付宝返回的验签字符串来看,iOS 在调用 payOrder:fromScheme:callback: 前,很可能对整个 orderString 做了 URL 解码,或者网络层、模型转换层自动执行了解码。这样一来,notify_url 中经过编码的 %26 会被还原为 &,其中的 duration、payment_id、persona_id 等查询参数也会被支付宝当作订单的一级参数,最终导致验签失败。

服务端返回支付宝订单字符串后,iOS 应将其原样传给 AlipaySDK,不要对整个字符串进行 URL 编码或解码。

为什么可以判断订单字符串被解码了

服务端返回的 notify_url 是:

notify_url=https%3A%2F%2Ftest.xiaohongmei.net%2Fapi%2Fpay%2Fcallback%3Fuser_id%3D...%26plan_template_id%3D...%26persona_template_id%3D...%26payment_id%3D...%26persona_id%3D...%26duration%3D2592000

其中:

  • %3F 表示 ?
  • %3D 表示 =
  • %26 表示 &

这些编码不能省略,因为 notify_url 本身带有查询参数,需要作为一个完整的支付宝请求参数传递。

支付宝返回的验签字符串中却出现了:

notify_url=https://test.xiaohongmei.net/api/pay/callback?user_id=...
&payment_id=...
&persona_id=...
&persona_template_id=...
&plan_template_id=...
&duration=2592000

原本属于 notify_url 的参数已经被拆成支付宝订单的一级参数。支付宝实际参与验签的内容因此发生了变化,无法通过服务端生成的 RSA2 签名。

签名字段本身也可能被同时破坏。例如:

%2F
%2B
%3D

提前解码后会变成:

/
+
=

尤其是 +,在后续表单解析过程中还可能被当作空格,进一步改变签名内容。

正确的调用方式

服务端应完成以下工作:

  1. 组装业务参数。
  2. 使用应用私钥生成 RSA2 签名。
  3. 对各参数值进行必要的 URL 编码。
  4. 返回可以直接提交给 AlipaySDK 的完整订单字符串。

iOS 收到后直接调用:

NSString *appScheme = @"shilaixiaohongmei";

// orderString 必须是服务端返回的原始字符串
[[AlipaySDK defaultService] payOrder:orderString
                          fromScheme:appScheme
                            callback:^(NSDictionary *resultDic) {
    NSLog(@"result = %@", resultDic);
}];

不要执行下面这类处理:

// 错误:会破坏 notify_url 和 sign 等参数
orderString = [orderString stringByRemovingPercentEncoding];

也不要再次编码完整的订单字符串:

// 错误:可能造成二次编码
orderString = [orderString stringByAddingPercentEncodingWithAllowedCharacters:
               [NSCharacterSet URLQueryAllowedCharacterSet]];

如果接口返回 JSON,只需用 JSON 解析器解析 JSON,不要再对其中的订单字符串做 URL 解码:

NSDictionary *responseObject = ...;
NSString *orderString = responseObject[@"data"][@"order_string"];

if (![orderString isKindOfClass:[NSString class]] || orderString.length == 0) {
    NSLog(@"Invalid Alipay order string");
    return;
}

[[AlipaySDK defaultService] payOrder:orderString
                          fromScheme:@"shilaixiaohongmei"
                            callback:^(NSDictionary *resultDic) {
    NSLog(@"result = %@", resultDic);
}];

建议的排查方法

服务端生成订单字符串后,先计算一次 SHA-256。iOS 调用 SDK 前,再对收到的字符串计算一次 SHA-256。两端摘要必须完全相同。

还要检查以下内容:

  • 字符串长度是否一致。
  • %26 是否仍然存在。
  • %2B、%2F、%3D 是否被提前还原。
  • + 是否被转换成空格。
  • 是否调用过 stringByRemovingPercentEncoding。
  • 网络封装层是否自动执行了 URL decode。
  • 是否先将订单字符串拆成字典,再重新拼接。
  • 是否使用 NSURLComponents、queryItems 等 API 重新处理过订单字符串。

调试时,可以临时输出调用 SDK 前的内容,并与服务端的原始响应逐字符比较:

NSLog(@"order length = %lu", (unsigned long)orderString.length);
NSLog(@"order string = %@", orderString);

生产环境中不要长期记录完整的订单字符串,其中包含签名和交易信息。

URL Scheme 是否有问题

当前 CFBundleURLSchemes 与调用时使用的 appScheme 一致:

<string>shilaixiaohongmei</string>

URL Scheme 主要用于支付宝完成支付后唤起应用,不参与订单的 RSA2 验签,因此不会引发 isv.invalid-signature。

配置中的空节点没有作用,可以删除:

<dict/>

整理后的配置可以写成:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>alipay</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>shilaixiaohongmei</string>
        </array>
    </dict>
</array>

删除空节点只是清理配置,不能解决当前的验签错误。

还需要注意什么

Android 可以正常支付,说明服务端生成的原始订单字符串大概率没有问题,也进一步将排查范围缩小到了 iOS 收到订单字符串后的处理链路。

服务端签名时还要遵循两个原则:

  • 签名必须使用与 app_id 对应的应用私钥。
  • 最终传给支付宝的参数值,必须与签名时使用的参数值完全一致。

不要在客户端解析、修改或重新拼接已经签名的订单字符串。即使只改动 URL、空格、中文编码或 JSON 格式,也可能造成验签失败。

当前案例应优先检查 iOS 端是否对整个 orderString 调用了 URL 解码。支付宝返回的验签字符串已经表明 notify_url 被拆分,这是最明确的异常。将处理方式改为”服务端返回什么,iOS 就原样传什么”后,通常不需要再增加其他 iOS 支付参数。

备注:内容仅供参考。