PAYATHON 2026

支付宝APP支付报错 Invalid Arguments 和 invalid-signature

支付小周

结论

这类报错通常不是签名算法算错了,而是 sign 生成后又被错误编码、解码或拼接,导致支付宝客户端收到的签名与服务端生成的签名不一致。

开发助手验证通过,只能证明私钥、待签名字符串和签名算法在当时的输入条件下没有问题,不能证明最终传给支付宝 APP 的完整 orderString 未被修改。建议优先检查:

  • 是否对整个支付参数重复调用了 urlencode()、urldecode() 或 http_build_query()。
  • sign 中的 + 是否变成了空格。
  • JSON 序列化、接口转发、数据库存取或前端处理是否改动了参数。
  • 服务端是否使用 APP 支付对应的 SDK 方法生成订单字符串。
  • 应用私钥、支付宝公钥、app_id 和签名方式是否来自同一套配置。

为什么签名验证正确,APP 支付仍然失败

APP 支付通常分为两个阶段:

  1. 服务端根据业务参数生成待签名字符串并完成签名。
  2. 服务端将完整订单字符串返回客户端,客户端再原样交给支付宝 SDK。

开发助手通常只验证第一个阶段。如果订单字符串在第二个阶段出现以下变化,支付宝实际收到的内容就会与签名时不同:

  • sign 被二次 URL 编码。
  • + 经过表单解码后变成空格。
  • %2B 被再次编码为 %252B。
  • 参数被重新排序、删除或补充。
  • JSON 字符串中的转义符、斜杠或 Unicode 表示发生变化。
  • 客户端拆分订单字符串后重新拼接。
  • 后端将订单字符串放进查询参数,又被 Web 框架自动解码。

例如,原始签名可能包含:

abc+def/ghi==

正确进行一次 URL 编码后可能是:

abc%2Bdef%2Fghi%3D%3D

再次编码后则会变成:

abc%252Bdef%252Fghi%253D%253D

支付宝收到这样的签名时,可能直接返回 Invalid Arguments 或 invalid-signature。

正确生成 APP 支付订单字符串

使用支付宝 PHP SDK 时,服务端应调用对应的 APP 支付请求,并通过 SDK 的 sdkExecute() 生成完整订单字符串。不要提取 sign 后自行拼装。

<?php

require_once 'AopSdk.php';

$aop = new AopClient();
$aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
$aop->appId = '你的 app_id';
$aop->rsaPrivateKey = '应用私钥';
$aop->alipayrsaPublicKey = '支付宝公钥';
$aop->signType = 'RSA2';
$aop->format = 'json';
$aop->charset = 'UTF-8';

$request = new AlipayTradeAppPayRequest();

$bizContent = [
    'out_trade_no' => 'ORDER_202609090001',
    'total_amount' => '0.01',
    'subject' => '测试商品',
    'product_code' => 'QUICK_MSECURITY_PAY',
];

$request->setBizContent(
    json_encode($bizContent, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
);

$request->setNotifyUrl('https://example.com/alipay/notify');

// sdkExecute() 返回可直接交给支付宝 APP SDK 的订单字符串
$orderString = $aop->sdkExecute($request);

// 通过 JSON 接口返回时,将它当作一个普通字符串,不再 urlencode
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
    'order_string' => $orderString,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

客户端取得 order_string 后,应直接调用支付宝支付接口,不要解析、排序或重新拼接其中的参数。

伪代码如下:

orderString = response.order_string
AlipaySDK.pay(orderString)

以下处理通常是错误的:

$orderString = urlencode($aop->sdkExecute($request));
$orderString = http_build_query([
    'order_string' => $aop->sdkExecute($request),
]);
const orderString = encodeURIComponent(response.order_string);

除非具体接口协议明确要求,否则不要再次编码 SDK 已生成的订单字符串。

建议的排查步骤

1. 比较服务端生成值与客户端实收值

分别记录以下内容:

  • sdkExecute() 刚返回的原始字符串。
  • 服务端接口最终响应中的字符串。
  • 客户端收到但尚未处理的字符串。
  • 客户端实际传给支付宝 SDK 的字符串。

不要只靠肉眼比较,可以计算哈希:

$hash = hash('sha256', $orderString);
error_log('alipay order hash: ' . $hash);

客户端也应对实际提交的字符串计算 SHA-256。两端哈希不同,就说明订单字符串在传输或处理过程中被改动了。

生产环境日志不宜长期保存完整订单参数和签名,排查结束后应及时关闭相关日志。

2. 单独检查 sign 参数

重点检查以下变化:

+   → 空格
%2B → +
%2B → %252B
/   → %2F 或被截断
=   → %3D 或被删除

如果代码使用了 parse_str(),尤其要注意 + 会按照表单编码规则被解释为空格:

parse_str('sign=abc+def', $params);

var_dump($params['sign']);
// 可能得到 "abc def"

不要用 parse_str() 拆解订单字符串后再重新组装。如果确实需要诊断,应保留原始字符串,并明确区分 RFC 3986 URL 编码与表单编码的行为。

3. 检查接口返回链路

如果订单字符串经过以下环节,需要逐层核对:

  • Nginx、API 网关或反向代理。
  • PHP 框架的请求参数转换。
  • JSON 序列化和反序列化。
  • 数据库或缓存。
  • WebView、JavaScript Bridge 或跨端框架。
  • 客户端网络库的查询参数编码。
  • 客户端支付插件的二次封装。

建议通过 JSON 响应体传输订单字符串,不要直接将其放在 URL 查询参数中:

{
  "order_string": "app_id=...&biz_content=...&sign=..."
}

即使 JSON 中显示了转义字符,也要以反序列化后的实际字符串为准,不要再手动执行 URL 解码。

4. 核对密钥和应用配置

确认订单字符串从生成到提交始终一致后,再检查:

  • app_id 是否属于当前应用。
  • 应用私钥是否与开放平台配置的应用公钥配对。
  • 验签配置中使用的是否为支付宝公钥,而不是应用公钥。
  • sign_type 是否一致,常见配置为 RSA2。
  • 是否混用了沙箱环境与正式环境的网关、应用或密钥。
  • 最近是否更换过密钥、应用配置或部署环境。
  • 多台服务器是否加载了不同的配置文件或环境变量。

使用开发助手验证时,app_id、密钥、签名方式和待签名内容必须与线上完全一致。否则,即使验证正确,也不能排除配置错配。

5. 检查近期环境变化

“上周还能使用”并不代表代码之外没有发生变化。需要检查近期是否有以下调整:

  • PHP 或 OpenSSL 升级。
  • 容器镜像、服务器或部署节点切换。
  • SDK 文件更新或依赖重新安装。
  • 私钥换行、空格或文件读取方式改变。
  • API 网关或客户端网络层调整。
  • 前端、跨端框架或支付插件升级。
  • 字符集或 JSON 序列化选项变化。

可以临时将流量固定到单个节点测试。如果只有部分请求失败,应重点比较各节点使用的 SDK、密钥和运行环境。

关于 SDK 版本

alipay-sdk-php-20200415 版本较早,但不能只凭版本号认定此次错误由 SDK 直接引起。应先确认项目实际加载的是哪个 SDK 文件,同时检查是否存在本地修改、重复依赖或部署节点版本不一致。

可以在测试环境中升级到支付宝当前提供的 PHP SDK 进行对照。升级时不要同时修改密钥、参数组装方式和客户端逻辑,否则很难判断真正的原因。升级前还要核对 PHP 版本要求、命名空间、请求类和方法调用方式。

最常见的修复方式

多数情况下,可以按以下方式修复:

  1. 服务端使用 sdkExecute() 一次生成完整的 APP 支付订单字符串。
  2. 通过 JSON 将订单字符串返回客户端,不再调用 urlencode()。
  3. 客户端将字符串原样交给支付宝 SDK,不解析、不解码,也不重新拼接。

如果处理后仍然报错,应保存同一笔请求在“签名前、SDK 生成后、接口返回后、客户端提交前”四个位置的数据或哈希。逐段比较后,就能判断问题出在签名配置、服务端生成、网络传输还是客户端调用。

备注:内容仅供参考。