支付宝预支付返回code 0000且无法获取request_token
明确结论
这个响应中的 code=0000 并不表示”交易成功”,而是支付宝网关返回的通用系统异常。相同代码和请求结构在更换商户号后可以正常取得 request_token,因此问题更可能与特定商户的签约权限、账户状态、商户配置或密钥对应关系有关,而非业务代码本身。
建议先检查以下三项:
partner对应的商户是否已签约并开通alipay.wap.trade.create.direct。seller_account_name是否属于该partner,并与签约收款账户一致。- 签名使用的 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是否可以省略,以及必须填写哪种账户,应以该商户对应的接口协议为准。- 旧接口可能存在商户级别的兼容差异。如果支付宝确认当前产品已不再受支持,应按照官方迁移方案接入现行支付接口,不要继续依赖旧网关。
备注:内容仅供参考。
