支付宝沙箱电脑端支付 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 验签成功,也不能立即把订单标记为已支付。用户可能关闭页面,网络也可能中断,同步跳转甚至可能根本没有发生。
建议按以下流程处理:
return_url验签后,页面显示“支付结果确认中”。- 收到
notify_url通知后,验签并核对订单号、金额、收款方和交易状态。 - 根据异步通知更新订单,同时做好幂等处理。
- 如果长时间没有收到异步通知,调用支付宝交易查询接口确认状态。
notify_url处理成功后,按照 SDK 要求返回success。
遇到这类问题,应先检查同步回跳参数和本地验签代码。只有在配置和请求链路均无问题、多个独立请求持续出现相同故障,并且支付宝官方渠道确认异常时,才可以较有把握地归因于沙箱维护。
备注:内容仅供参考。
