PAYATHON 2026

支付宝沙箱电脑端支付 return_url.php 验证失败

支付小周

结论

不能只凭 return_url.php 验签失败,就判断支付宝沙箱正在维护。既然 notify_url.php 可以正常验签,通常说明商户私钥、支付宝公钥和签名类型的基本配置没有问题。更可能的原因是同步回跳参数被修改、重复解码、混入自定义参数,或者 return_url 请求经过代理、网关后发生了变化。

return_url 只用于浏览器同步跳转,不能作为支付成功的最终依据。订单状态应以 notify_url 的异步通知为准,也可以通过交易查询接口主动确认。

为什么异步通知能通过,同步回跳却失败

两种通知经过的链路不同:

  • notify_url 由支付宝服务器直接发起,通常使用 POST。
  • return_url 经过用户浏览器跳转,通常从 GET 参数中读取。
  • 浏览器、反向代理、Web 应用防火墙或业务代码都有可能改写同步回跳参数。
  • PHP 已经对 $_GET 完成 URL 解码。如果业务代码再次调用 urldecode(),可能破坏签名值。
  • 如果 return_url 自带查询参数,验签时又将这些参数与支付宝返回的参数合并,待验签内容也会发生变化。
  • sign_type、支付宝公钥或网关环境配置不一致,同样会导致验签失败。

沙箱偶尔会出现临时异常,但如果没有支付宝官方公告或可复现的证据,不宜直接判断为环境维护。

排查步骤

1. 直接使用原始回跳参数验签

不要修改、转码或补充 $_GET 中的字段:

<?php

require_once __DIR__ . '/config.php';
require_once __DIR__ . '/pagepay/service/AlipayTradeService.php';

$params = $_GET;

$alipayService = new AlipayTradeService($config);
$verified = $alipayService->check($params);

if (!$verified) {
    http_response_code(400);
    exit('验签失败');
}

$outTradeNo = $params['out_trade_no'] ?? '';
$tradeNo    = $params['trade_no'] ?? '';
$totalAmount = $params['total_amount'] ?? '';

echo '验签成功';

不同版本的支付宝 PHP SDK 可能使用不同的接口名称。应优先采用当前 SDK 示例中的验签方法,不要自行重新拼接待签名字符串。

2. 不要重复 URL 解码

下面的写法可能破坏签名参数:

$params = array_map('urldecode', $_GET);

也不要对整个查询字符串多次执行 urldecode()。PHP 解析请求时,已经生成了解码后的 $_GET。

3. 检查支付宝公钥

验签时需要配置与当前应用对应的“支付宝公钥”,不能使用以下密钥:

  • 商户应用公钥;
  • 商户私钥;
  • 生产环境应用的支付宝公钥;
  • 其他沙箱应用的支付宝公钥。

还要确认发起支付和回跳验签加载的是同一份配置文件。

4. 检查签名类型

如果支付请求使用 RSA2,验签配置也应使用 RSA2:

$config['sign_type'] = 'RSA2';

不要在请求阶段使用 RSA2,回跳阶段却加载了写有 RSA 的旧配置。

5. 排除额外参数干扰

建议先使用不带查询参数的地址:

https://example.com/alipay/return_url.php

排查期间暂时不要使用:

https://example.com/alipay/return_url.php?source=test

如果业务必须携带额外信息,需要确保自定义参数不会被纳入支付宝返回参数的验签过程。更稳妥的做法是根据订单号在服务端查询业务上下文。

6. 检查请求链路是否改写参数

对比 Web 服务器收到的原始查询字符串与 PHP 的解析结果:

<?php

error_log('QUERY_STRING=' . ($_SERVER['QUERY_STRING'] ?? ''));
error_log('GET_KEYS=' . implode(',', array_keys($_GET)));

不要长期在日志中保存完整的支付参数。排查结束后应关闭调试日志,避免泄露订单信息和签名数据。

需要重点检查:

  • 请求是否经过 CDN、反向代理或 Web 应用防火墙;
  • 框架中间件是否过滤了未知参数;
  • + 是否被转换成空格;
  • 是否发生了二次 URL 编码或解码;
  • 页面跳转脚本是否重新拼接过 URL;
  • 参数是否被截断。

验签通过后仍要校验业务数据

签名正确只能说明参数大概率来自支付宝,不能代替订单校验。至少还要检查以下内容:

<?php

if (!$verified) {
    exit('验签失败');
}

$order = findOrderByOutTradeNo($params['out_trade_no'] ?? '');

if (!$order) {
    exit('订单不存在');
}

if ((string) $order['total_amount'] !== (string) ($params['total_amount'] ?? '')) {
    exit('订单金额不一致');
}

if ($order['seller_id'] !== ($params['seller_id'] ?? '')) {
    exit('收款方不一致');
}

比较金额时,最好统一转换为以“分”为单位的整数,以免受到浮点数精度问题影响。

正确处理支付结果

即使 return_url 验签成功,也不能立即把订单标记为已支付。用户可能关闭页面,网络也可能中断,同步跳转甚至可能根本没有发生。

建议按以下流程处理:

  1. return_url 验签后,页面显示“支付结果确认中”。
  2. 收到 notify_url 通知后,验签并核对订单号、金额、收款方和交易状态。
  3. 根据异步通知更新订单,同时做好幂等处理。
  4. 如果长时间没有收到异步通知,调用支付宝交易查询接口确认状态。
  5. notify_url 处理成功后,按照 SDK 要求返回 success。

遇到这类问题,应先检查同步回跳参数和本地验签代码。只有在配置和请求链路均无问题、多个独立请求持续出现相同故障,并且支付宝官方渠道确认异常时,才可以较有把握地归因于沙箱维护。

备注:内容仅供参考。