Alipay payment gateway integration 返回 ILLEGAL_SIGN 错误
结论
ILLEGAL_SIGN 表示支付宝使用商户的 MD5 密钥复算签名后,得到的结果与请求中的签名不同。问题通常与支付金额或回调地址本身无关,而是出在以下某个环节:
- 使用了错误的密钥,例如把
partner、登录密码或 RSA 密钥当作 MD5 密钥。 - 测试环境的
partner、MD5 密钥和网关不属于同一套账号。 - 签名参数有遗漏、重复,或者排序方式不正确。
- 计算签名后又修改了金额、URL、字符集等参数。
- 签名原文与实际发送的参数值不同,例如签名时使用了 URL 编码后的值。
仅凭示例 URL 无法验证签名是否正确,因为复算 MD5 必须知道该测试账号对应的私密 MD5 key。
正确的 MD5 签名流程
对于这类旧版 create_forex_trade 接口,通常需要按以下顺序处理。具体规则仍以该测试账号对应的接口文档为准。
- 准备全部业务参数。
- 删除
sign和sign_type。 - 删除值为
null或空字符串的参数。 - 按参数名进行 ASCII 升序排列。
- 将参数写成
key=value,并使用&连接。 - 在字符串末尾直接追加商户 MD5 key。
- 按
_input_charset指定的字符集转换为字节,然后计算 MD5。 - 将计算结果写入
sign,最后对各参数进行 URL 编码并发送。
签名原文应采用以下形式:
_input_charset=UTF-8¤cy=USD¬ify_url=http://www.alipay.com&out_trade_no=1115919735609649&partner=2088621921925641&return_url=http://www.alipay.com&sendFormat=normal&service=create_forex_trade&subject=Beautylish&total_fee=2.2YOUR_MD5_KEY
密钥通常直接拼接在字符串末尾:
...&total_fee=2.2YOUR_MD5_KEY
不能写成:
...&total_fee=2.2&key=YOUR_MD5_KEY
PHP 示例
<?php
$params = [
'service' => 'create_forex_trade',
'partner' => '2088621921925641',
'_input_charset' => 'UTF-8',
'subject' => 'Beautylish',
'out_trade_no' => '1115919735609649',
'currency' => 'USD',
'total_fee' => '2.2',
'notify_url' => 'http://www.alipay.com',
'return_url' => 'http://www.alipay.com',
'sendFormat' => 'normal',
];
$md5Key = 'YOUR_TEST_ENVIRONMENT_MD5_KEY';
// 排除空值以及 sign、sign_type
$filtered = array_filter(
$params,
static function ($value, $name) {
return $name !== 'sign'
&& $name !== 'sign_type'
&& $value !== null
&& $value !== '';
},
ARRAY_FILTER_USE_BOTH
);
// 按参数名升序排列
ksort($filtered);
// 签名时使用原始值,不提前进行 URL 编码
$signContent = http_build_query($filtered, '', '&', PHP_QUERY_RFC3986);
// http_build_query 会编码 URL,不能直接作为旧版 MD5 接口的签名原文。
// 因此显式重建未编码的 key=value 字符串。
$pairs = [];
foreach ($filtered as $name => $value) {
$pairs[] = $name . '=' . $value;
}
$signContent = implode('&', $pairs);
$sign = md5($signContent . $md5Key);
// 签名完成后再添加这两个字段
$params['sign_type'] = 'MD5';
$params['sign'] = $sign;
$requestUrl = 'https://mapi.alipaydev.com/gateway.do?'
. http_build_query($params, '', '&', PHP_QUERY_RFC3986);
echo "Sign content:\n" . $signContent . "\n\n";
echo "Sign:\n" . $sign . "\n\n";
echo "Request URL:\n" . $requestUrl . "\n";
重点检查项
1. 测试账号必须与测试网关匹配
如果请求发送到:
https://mapi.alipaydev.com/gateway.do
那么 partner 和 MD5 key 也必须来自同一个测试环境账号。以下信息不能混用:
- 正式环境的
partner - 正式环境的 MD5 key
- 另一个测试账号的密钥
- RSA 私钥或公钥
- 支付宝登录密码
partner 只是商户身份标识,不是签名密钥。
2. 不要使用编码后的 URL 计算签名
签名时应使用:
notify_url=http://www.alipay.com
而不是:
notify_url=http%3A%2F%2Fwww.alipay.com
URL 编码要在签名计算完成后进行。否则,支付宝解码参数并复算签名时,使用的原文可能与商户签名时使用的原文不同。
3. 实际发送的参数必须全部参与签名
除了 sign、sign_type 和空值,实际发送的参数一般都要参与签名,包括:
_input_charset
sendFormat
service
partner
currency
total_fee
生成签名后,不要再增加、删除或修改参数。
4. 参数名区分大小写
以下参数名必须保持接口规定的拼写和大小写:
_input_charset
out_trade_no
total_fee
sign_type
sendFormat 是否是该接口的有效参数,需要根据当前测试账号对应的接口文档确认。如果请求中包含这个参数,签名原文中也必须使用完全相同的名称和值。
5. 金额应作为字符串处理
不要让浮点数格式化过程将:
2.2
自动转换为:
2.20
也要避免产生二进制浮点误差。签名原文中的金额字符串必须与最终请求里的值逐字一致。
6. 打印签名原文并逐项比对
排查时,不要只打印最终 URL,还应记录以下内容:
排序后的参数列表
最终签名原文
签名结果
实际发送的参数
请求所用网关
partner
不要将 MD5 key 写入日志。需要逐项确认签名原文中的值与支付宝实际收到的值一致。
如果确认上述算法无误后仍然返回 ILLEGAL_SIGN,最可能的原因是测试环境的 partner 与 MD5 key 不匹配、密钥尚未正确配置,或者该账号实际要求的签名类型不是 MD5。此时应在测试商户后台重新确认签名方式和密钥,并使用后台为该测试账号生成或分配的对应凭据。
备注:内容仅供参考。
