PAYATHON 2026

升级支付API是否影响旧系统并导致ILLEGLE_SIGN错误

支付小周

结论

ILLEGLE_SIGN(部分接口实际返回 ILLEGAL_SIGN)说明服务端未能通过签名校验,但不能据此判断 create_direct_pay_by_user 已废弃。常见原因包括签名类型、密钥、字符集、参数排序错误,或者请求网关与接入模式不匹配。

create_direct_pay_by_user 是较早的网页即时到账接入模式,通常使用商户合作伙伴身份和旧式签名方案。新的开放平台 API 通常通过 app_id 标识应用,并使用 RSA/RSA2 签名。两种模式的参数结构和密钥体系不同,不能通过添加 app_id 完成升级,也不能直接沿用旧接口的签名代码。

接入新 API 需要可用的应用和 app_id,但未必需要重新创建应用:

  • 已有开放平台应用,且具备相应支付能力,可以评估后继续使用。
  • 只有旧商户号或合作伙伴身份,没有可用应用时,需要创建并配置应用。
  • 单独新建应用通常不会影响现有手机端支付。
  • 修改手机端现用应用的密钥、签名类型、回调配置或支付产品权限,可能导致手机端支付失败。

为什么会出现 ILLEGLE_SIGN

签名类型与接口模式不匹配

旧接口可能使用 MD5、RSA 等历史签名方式,新开放平台接口一般使用 RSA2。如果请求中声明:

sign_type=RSA2

实际签名却由 MD5 或 SHA1withRSA 生成,服务端无法通过验签。反过来也一样,请求声明 MD5,实际却用 RSA 私钥签名,同样会失败。

手机端可以正常支付,只能说明它当前使用的接口、密钥和签名方式彼此匹配,不能证明网页端的签名没有问题。手机端和网页端可能使用不同的接口、应用、合作伙伴身份或网关。

混用了两套密钥体系

排查时需要分清:

  • 旧接口的商户合作伙伴身份和安全校验码
  • 开放平台应用的 app_id
  • 应用私钥
  • 应用公钥
  • 平台公钥或平台证书

常见错误有:

  • 使用平台公钥为请求签名
  • 使用应用公钥代替应用私钥
  • 使用了另一个应用的私钥
  • app_id 和应用私钥不属于同一个应用
  • 平台配置的应用公钥与本地私钥不成对
  • 在正式环境中使用沙箱密钥,或在沙箱环境中使用正式密钥

商户侧使用应用私钥生成签名,平台公钥主要用于验证平台返回的数据,两者不能互换。

待签名字符串构造错误

旧接口通常要求先过滤、排序,再拼接业务参数。下面任何一处不一致都可能导致验签失败:

  • 把 sign 本身放入待签名参数
  • 错误包含或排除了 sign_type
  • 双方处理空值参数的方式不同
  • 参数没有按规定排序
  • 签名前后的 URL 编码次数不同
  • +、%、空格、中文或换行发生转换
  • JSON 被重新格式化,导致字段顺序或转义方式改变
  • 使用 GBK 生成字节,却声明 utf-8
  • 表单提交前又修改了已经签名的值

签名校验针对具体的字节内容。两个字符串即使在页面上看起来相同,只要编码、空格或换行不同,签名结果就可能不同。

请求网关与参数版本不一致

升级时很容易把旧参数发往新网关,或者把新参数发往旧网关。例如:

  • 请求中仍有 service=create_direct_pay_by_user
  • 同时加入了新 API 的 app_id、method、version、biz_content
  • 使用新应用私钥签署旧接口参数
  • 使用旧安全校验码签署开放平台 API 请求

旧接口和新 API 不是简单的参数增补关系。两者需要分别按照各自的文档构造完整请求。

建议的排查步骤

1. 确认实际返回的错误码

先记录支付平台返回的原始错误码、子错误码和请求标识,确认实际返回的是:

ILLEGLE_SIGN

还是:

ILLEGAL_SIGN

不要只看页面上的中文提示,前端可能对原始错误做过二次包装。

2. 固定当前接入链路

先确定网页端使用的是哪套模式:

旧模式:
service=create_direct_pay_by_user
partner=...
sign_type=...

或者:

开放平台模式:
app_id=...
method=alipay.trade.page.pay
sign_type=RSA2
version=...
biz_content=...

同一次请求不能混用两套模式。

3. 核对签名身份

逐项检查以下对应关系:

请求 app_id
    ↕
平台后台中的应用
    ↕
该应用配置的应用公钥
    ↕
服务器实际使用的应用私钥

只要其中一项属于其他应用,即使签名代码没有问题,也会返回签名错误。

4. 输出待签名原文

在测试环境中记录:

接口名称
网关地址
app_id 或 partner
sign_type
charset
排序后的待签名字符串
签名结果
最终提交参数

不要把私钥、安全校验码和完整的敏感业务数据写入日志。排查时要对比“签名前的原文”和“实际发送的参数”,不能只比较签名结果。

5. 使用平台 SDK 交叉验证

如果手写签名一直失败,可以使用平台当前提供的官方 SDK,根据同一组参数生成一次请求:

  • SDK 请求成功,问题很可能出在自定义签名或编码逻辑中。
  • SDK 同样失败,需要继续检查应用状态、密钥配置、支付产品权限和运行环境。
  • 不要把示例中的 app_id、密钥或网关直接用于生产环境。

旧接口签名检查示例

下面只演示旧式参数签名的检查思路。是否需要包含 sign_type、使用哪种摘要算法以及采用什么字符集,必须以当前账户对应的旧接口文档为准。

<?php

function buildLegacySignContent(array $params): string
{
    unset($params['sign'], $params['sign_type']);

    $params = array_filter(
        $params,
        static fn ($value) => $value !== null && $value !== ''
    );

    ksort($params, SORT_STRING);

    $pairs = [];
    foreach ($params as $key => $value) {
        // 不要在这里对 value 进行 urlencode,除非对应接口明确要求。
        $pairs[] = $key . '=' . $value;
    }

    return implode('&', $pairs);
}

$params = [
    'service'        => 'create_direct_pay_by_user',
    'partner'        => 'YOUR_PARTNER_ID',
    '_input_charset' => 'utf-8',
    'out_trade_no'   => 'ORDER_202609150001',
    'subject'        => '测试订单',
    'payment_type'   => '1',
    'total_fee'      => '0.01',
];

$content = buildLegacySignContent($params);

// 旧接口如果明确要求 MD5,其计算形式通常还会涉及商户安全校验码。
// 不要根据此示例猜测规则,应严格采用对应接口文档或原有 SDK。
error_log('legacy sign content: ' . $content);

这个示例用于检查参数过滤、排序、编码和最终提交内容是否一致,不建议以此为依据重新实现生产支付签名。

新 API 接入示例

新的网页支付通常应使用开放平台 SDK,由 SDK 组织公共参数、完成签名并生成表单。下面只展示代码结构,类名和属性应以实际安装的 SDK 版本为准:

<?php

$aop = new AopClient();
$aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
$aop->appId = 'YOUR_APP_ID';
$aop->rsaPrivateKey = file_get_contents('/secure/path/app_private_key.pem');
$aop->alipayrsaPublicKey = file_get_contents('/secure/path/alipay_public_key.pem');
$aop->signType = 'RSA2';
$aop->postCharset = 'UTF-8';
$aop->format = 'json';

$request = new AlipayTradePagePayRequest();

$request->setBizContent(json_encode([
    'out_trade_no' => 'ORDER_202609150001',
    'product_code' => 'FAST_INSTANT_TRADE_PAY',
    'total_amount' => '0.01',
    'subject'      => '测试订单',
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));

$request->setReturnUrl('https://www.example.com/payment/return');
$request->setNotifyUrl('https://www.example.com/payment/notify');

$form = $aop->pageExecute($request);
echo $form;

生产环境中的私钥应保存在受控的密钥服务或服务器安全目录中,不能写入网页代码、前端 JavaScript、代码仓库或日志。

新接入是否会影响手机端

如果新建网页支付应用,同时保留手机端的原有配置,两套接入通常可以并行运行:

现有手机端
  → 原应用或原商户配置
  → 原接口和原密钥

新网页端
  → 新应用 app_id
  → 新 API 和新应用私钥

需要避免:

  • 覆盖手机端正在使用的应用公钥
  • 替换服务端共用的私钥文件
  • 把全局 app_id 直接改为网页端的新 app_id
  • 修改共用回调地址后,没有区分移动端和网页端订单
  • 让两个应用共用一套配置变量,导致运行时读取错误的密钥
  • 未经验证就停用旧应用或旧支付产品

较稳妥的做法是按支付渠道分别保存配置:

PAY_MOBILE_APP_ID
PAY_MOBILE_PRIVATE_KEY
PAY_MOBILE_PLATFORM_PUBLIC_KEY

PAY_WEB_APP_ID
PAY_WEB_PRIVATE_KEY
PAY_WEB_PLATFORM_PUBLIC_KEY

还应在测试环境中分别完成下单、支付、同步跳转、异步通知验签、退款和订单查询测试,再逐步切换网页端流量。

create_direct_pay_by_user 在当前账户和平台中能否继续使用,应以平台后台显示的产品状态、合同权限和当前官方文档为准。即使旧接口暂时仍可调用,也建议把网页端迁移到当前支持的开放平台支付 API。迁移需要按独立接入处理,不能只修改旧请求中的几个参数。

备注:内容仅供参考。