PAYATHON 2026

支付宝预支付返回code 0000且无法获取request_token

支付老李

明确结论

这个响应中的 code=0000 并不表示”交易成功”,而是支付宝网关返回的通用系统异常。相同代码和请求结构在更换商户号后可以正常取得 request_token,因此问题更可能与特定商户的签约权限、账户状态、商户配置或密钥对应关系有关,而非业务代码本身。

建议先检查以下三项:

  1. partner 对应的商户是否已签约并开通 alipay.wap.trade.create.direct。
  2. seller_account_name 是否属于该 partner,并与签约收款账户一致。
  3. 签名使用的 MD5 Key 是否属于当前 partner,是否误用了其他商户的密钥。

仅凭 code=0000 无法确定具体原因。如果这些项目都没有问题,需要带上完整请求时间、partner、req_id 和原始响应,联系支付宝技术支持查询网关日志。

为什么更像商户配置问题

请求已经到达支付宝网关,网关也返回了结构完整的 res_error:

<err>
    <code>0000</code>
    <sub_code>0000</sub_code>
    <msg>system exception</msg>
    <detail>系统异常</detail>
</err>

这表明错误发生在创建预支付交易的阶段,并非本地没有收到响应。另一个商户号使用同一套调用程序能够成功,基本可以先排除这些问题:

  • HTTP 客户端完全不可用;
  • 请求地址无法访问;
  • XML 结构存在必然错误;
  • 返回结果解析逻辑完全错误。

但这不能证明签名一定正确。程序可能会根据不同的 partner 加载不同密钥,而目前只有一个商户的映射是正确的。

建议的排查顺序

1. 核对产品签约和接口权限

先确认异常商户签约的产品是否包含相应的手机网站支付能力,签约状态是否正常。

具体需要检查:

  • partner 是否为该签约主体正确的合作伙伴 ID;
  • 商户是否开通了 alipay.wap.trade.create.direct;
  • 产品是否已经生效,而非仍在审核、已暂停、已到期或已关闭;
  • 商户账户是否受到风控限制、冻结,或存在身份认证异常;
  • 当前接口是否仍包含在该商户的签约范围内。

这是历史 WAP 支付接口。不同商户的签约时间和产品体系可能不同。一个商户能够调用,并不意味着另一个商户自动拥有相同权限。

2. 核对收款账户与 partner 的关系

请求中包含:

<seller_account_name>zfbyw-3@19lou.com</seller_account_name>

需要确认该支付宝账户确实是 partner=208811120397xxxx 对应的签约收款账户。常见问题有:

  • 更换了 partner,却仍在使用原商户的 seller_account_name;
  • 填写的是关联账户、操作员账户或其他支付宝登录账户;
  • 邮箱已经变更,程序配置却没有同步;
  • 商户签约主体与实际收款账户不一致。

如果测试时只替换了 partner 和密钥,没有同步修改 seller_account_name,应优先检查商户与卖家账户是否匹配。

可以为每个商户建立一套完整配置,避免参数串用:

<?php

$merchantConfigs = [
    'merchant_a' => [
        'partner' => '208811120397xxxx',
        'seller_account_name' => 'merchant-a@example.com',
        'md5_key' => getenv('ALIPAY_MERCHANT_A_MD5_KEY'),
    ],
    'merchant_b' => [
        'partner' => '2088xxxxxxxxxxxx',
        'seller_account_name' => 'merchant-b@example.com',
        'md5_key' => getenv('ALIPAY_MERCHANT_B_MD5_KEY'),
    ],
];

$config = $merchantConfigs['merchant_a'];

不要从多个配置文件分别读取 partner、收款账户和密钥,否则很容易组合出一套跨商户参数。

3. 核对 MD5 Key 和签名过程

sec_id=MD5 表示请求按照旧接口约定使用 MD5 签名。每个 partner 都应使用各自的 MD5 Key,不能混用。

排查时可以记录签名前的原始字符串,但不要记录 MD5 Key。下面的代码只用于说明排查思路,实际签名字段、空值处理和编码规则仍应以该商户的签约接口文档为准:

<?php

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

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

    ksort($params);

    $pairs = [];
    foreach ($params as $name => $value) {
        $pairs[] = $name . '=' . $value;
    }

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

$signContent = buildSignContent($params);
$sign = md5($signContent . $merchantMd5Key);

检查时重点关注:

  • 是否根据当前 partner 加载了正确的 MD5 Key;
  • 签名前是否误对参数值进行了 URL 编码;
  • 发送请求时是否发生了第二次 URL 编码;
  • 发送的 req_data 是否与签名时使用的字符串完全相同;
  • 参数排序、空值过滤和字符编码是否符合接口约定;
  • MD5 结果的大小写是否符合接口要求;
  • 密钥前后是否混入空格、换行符或其他不可见字符。

XML 中的中文和 URL 尤其需要注意。签名时使用 UTF-8,发送时也必须保持完全相同的字节内容。

4. 对两个商户逐项比较

不要只对比最终的请求 URL。建议输出经过脱敏的结构化诊断信息:

partner
seller_account_name
service
sec_id
_input_charset
v
format
req_id
out_trade_no
signContent 的 SHA-256 摘要
实际使用的配置名称
密钥指纹
请求时间
原始响应

通过密钥指纹可以确认程序实际加载了哪个密钥,但不能输出密钥本身:

<?php

$keyFingerprint = substr(hash('sha256', $merchantMd5Key), 0, 12);

如果两个商户之间只有 partner、卖家账户和密钥不同,而其中一个始终失败,就应该进一步检查该商户的签约权限和账户状态。

5. 保证请求号和商户订单号唯一

还需要检查:

<out_trade_no>b9e5a170bbe04de5b4d4f0462719f344</out_trade_no>

以及:

req_id=01215405716

重试时不要直接复用旧的 req_id。out_trade_no 通常也需要在对应商户范围内保持唯一。重复请求未必都会返回明确的”订单重复”错误,旧系统在某些异常路径中可能只返回通用错误。

每次进行独立测试时,可以生成新的请求标识:

<?php

$reqId = date('YmdHis') . random_int(100000, 999999);
$outTradeNo = bin2hex(random_bytes(16));

如果是在查询或恢复已有交易,不要直接创建新订单。应先确认原订单状态,以免造成重复支付。

6. 检查网关地址和传输方式

当前请求地址是:

http://wappaygw.alipay.com/service/rest.htm

这是旧版接口地址。需要根据该商户实际签约的文档确认:

  • 是否仍可调用该网关;
  • 是否要求使用 HTTPS;
  • 是否已经迁移到其他产品或新版接口;
  • 是否混用了生产环境和测试环境。

其他商户能够访问旧地址,并不能证明当前商户也具备相同的接口路由和兼容配置。

正确判断响应是否成功

不能把 code=0000 作为成功标志。当前结果位于 res_error 中,已经说明调用失败。只有成功响应中包含合法的业务数据,并且可以从相应字段解析出 request_token,才能继续后续支付流程。

示意代码如下:

<?php

parse_str($rawResponse, $response);

if (!empty($response['res_error'])) {
    $errorXml = html_entity_decode(
        $response['res_error'],
        ENT_QUOTES | ENT_XML1,
        'UTF-8'
    );

    throw new RuntimeException(
        'Alipay pre-create failed: ' . $errorXml
    );
}

if (empty($response['res_data'])) {
    throw new RuntimeException(
        'Alipay response contains neither res_error nor res_data'
    );
}

$xml = simplexml_load_string($response['res_data']);
if ($xml === false || empty($xml->request_token)) {
    throw new RuntimeException(
        'Alipay response does not contain request_token'
    );
}

$requestToken = (string) $xml->request_token;

实际返回字段和签名验证方式应以对应的旧接口文档为准。在解析响应之前,还应验证支付宝的返回签名,不能只检查字段内容。

仍无法解决时需要提供哪些信息

如果已经确认商户权限、收款账户和密钥都没有问题,建议向支付宝技术支持提交以下信息:

  • 脱敏后的 partner;
  • 请求发生的准确时间,最好精确到秒;
  • 完整的 req_id;
  • out_trade_no;
  • service;
  • 请求网关地址;
  • 原始请求参数,但必须隐藏签名密钥;
  • 原始响应字符串;
  • 成功商户与失败商户在产品签约方面的差异;
  • 每次请求是否使用了新的 req_id 和 out_trade_no。

0000 / system exception 本身没有足够的业务含义,无法从外部判断支付宝内部具体在哪个环节失败。要确认问题究竟来自产品权限、账户路由还是内部风控,只能查询支付宝侧日志。

注意事项

  • 不要在日志、工单或代码仓库中暴露 MD5 Key。
  • 不要靠反复更换签名算法或随意增删参数来碰运气。应固定原始请求,再逐项比较。
  • 更换商户时,必须同时切换 partner、收款账户、密钥和签约产品配置。
  • seller_account_name 是否可以省略,以及必须填写哪种账户,应以该商户对应的接口协议为准。
  • 旧接口可能存在商户级别的兼容差异。如果支付宝确认当前产品已不再受支持,应按照官方迁移方案接入现行支付接口,不要继续依赖旧网关。

备注:内容仅供参考。