支付宝当面付扫码支付无反应并出现白屏是什么原因?
结论
支付宝当面付出现“能扫码,但随后白屏或页面没有反应”,问题通常不在二维码显示组件,而在支付流程中的某个环节。程序没有正确处理失败结果,也没有记录相关错误,就容易只剩下一片白屏。
先区分两种情况:
- 支付宝客户端扫码后白屏,通常与二维码内容错误、预下单失败、接口返回值未经校验,或服务端异常被隐藏有关。
- 付款后网站支付页没有变化,是因为当面付的异步通知不会自动刷新浏览器。网站需要处理
notify_url,再通过轮询或 WebSocket 获取订单状态。
除了核对 App ID、密钥等后台配置,还要检查预下单响应、服务器错误日志、异步通知和前端订单状态更新。
先确认白屏出现在哪里
排查前,先确定白屏是在手机支付宝中出现,还是电脑浏览器中的支付页面没有变化。
支付宝客户端扫码后白屏
如果手机支付宝扫码后出现白屏,先检查二维码实际编码的内容。二维码必须使用 alipay.trade.precreate 接口成功响应中的 qr_code 生成,不能把支付页面地址、空字符串或错误信息直接编码成二维码。
可以临时记录二维码的原始内容:
error_log('Alipay QR content: ' . $qrCode);
正常情况下,这里应该记录支付宝返回的支付地址。如果日志内容为空、本站 URL、HTML 错误页面或 JSON 错误信息,说明程序在生成二维码之前就已经失败。
付款成功,但电脑网页不变化
notify_url 是支付宝服务器请求网站服务器的地址,它不会直接通知用户当前打开的浏览器页面。
完整流程通常如下:
- 网站调用
alipay.trade.precreate创建订单。 - 页面显示接口返回的
qr_code。 - 用户在支付宝中完成付款。
- 支付宝服务器向
notify_url发送异步通知。 - 网站验签,并将本地订单更新为已支付。
- 支付页面定时查询本地订单状态,发现已支付后跳转。
如果没有第 6 步,即使后台已经收到款,网页也可能一直停在二维码页面。
检查预下单接口的真实响应
不能只确认 SDK 调用是否执行,还要检查支付宝返回的 code、sub_code、sub_msg 和 qr_code。
下面的示例采用常见的 PHP 支付宝 SDK 写法。不同 SDK 版本的类名和响应结构可能略有差异:
<?php
$aop = new AopClient();
$aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do';
$aop->appId = $appId;
$aop->rsaPrivateKey = $merchantPrivateKey;
$aop->alipayrsaPublicKey = $alipayPublicKey;
$aop->signType = 'RSA2';
$aop->format = 'json';
$aop->charset = 'UTF-8';
$request = new AlipayTradePrecreateRequest();
$bizContent = [
'out_trade_no' => $orderNo,
'total_amount' => number_format($amount, 2, '.', ''),
'subject' => $subject,
];
$request->setBizContent(
json_encode($bizContent, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
);
$request->setNotifyUrl('https://example.com/payment/alipay/notify');
try {
$result = $aop->execute($request);
$response = $result->alipay_trade_precreate_response ?? null;
if (!$response) {
throw new RuntimeException('支付宝预下单响应结构异常');
}
if (($response->code ?? '') !== '10000') {
$message = sprintf(
'预下单失败:code=%s, msg=%s, sub_code=%s, sub_msg=%s',
$response->code ?? '',
$response->msg ?? '',
$response->sub_code ?? '',
$response->sub_msg ?? ''
);
error_log($message);
throw new RuntimeException($message);
}
$qrCode = $response->qr_code ?? '';
if ($qrCode === '') {
throw new RuntimeException('支付宝返回成功,但 qr_code 为空');
}
// 使用 $qrCode 生成二维码
} catch (Throwable $e) {
error_log(sprintf(
'[Alipay] %s in %s:%d',
$e->getMessage(),
$e->getFile(),
$e->getLine()
));
http_response_code(500);
exit('支付订单创建失败,请稍后重试');
}
使用沙箱环境时,App ID、支付宝公钥、网关地址和客户端都必须属于沙箱。沙箱配置不能与正式环境配置混用。
查看服务器错误,不要依赖白屏提示
白屏往往表示 PHP 发生了致命错误,只是生产环境关闭了错误显示。常见原因包括:
- 支付 SDK 没有正确加载;
- 缺少所需的 PHP 扩展;
- 类名或命名空间错误;
- 私钥文件读取失败;
- 返回对象为空,程序却继续读取其属性;
- 输出 JSON 前混入了警告信息;
- 框架将异常转换成了空响应;
- 文件权限、缓存或运行目录配置不正确。
排查时,优先查看这些信息:
- PHP-FPM 错误日志;
- Nginx 或 Apache 错误日志;
- 网站框架的应用日志;
- 浏览器开发者工具中的 Network 和 Console;
- 支付接口请求对应的 HTTP 状态码和响应正文。
开发环境可以临时开启 PHP 错误显示:
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
生产环境不要长期开启错误显示,否则可能泄露目录、密钥路径和数据库信息等敏感内容。页面应关闭错误显示,并将异常写入受保护的日志。
正确处理支付宝异步通知
notify_url 必须是支付宝服务器能够从公网访问的 HTTPS 地址,不能使用以下地址:
localhost;127.0.0.1;- 局域网 IP;
- 登录后才能访问的地址;
- 会被防火墙或安全插件拦截的路径;
- 返回 301 或 302 跳转的地址。
通知处理至少需要完成这些工作:
- 使用支付宝公钥验签。
- 校验
app_id。 - 根据
out_trade_no查找本地订单。 - 校验订单金额和收款方信息。
- 只在交易状态有效时更新订单。
- 处理完成后仅返回纯文本
success。 - 确保重复通知不会造成重复入账。
示意代码如下:
<?php
$postData = $_POST;
if (!$aop->rsaCheckV1($postData, null, 'RSA2')) {
http_response_code(400);
exit('failure');
}
if (($postData['app_id'] ?? '') !== $expectedAppId) {
http_response_code(400);
exit('failure');
}
$orderNo = $postData['out_trade_no'] ?? '';
$tradeNo = $postData['trade_no'] ?? '';
$status = $postData['trade_status'] ?? '';
$amount = $postData['total_amount'] ?? '';
$order = findOrderByOrderNo($orderNo);
if (!$order) {
exit('failure');
}
if (bccomp((string) $order['amount'], (string) $amount, 2) !== 0) {
exit('failure');
}
if (in_array($status, ['TRADE_SUCCESS', 'TRADE_FINISHED'], true)) {
// 需要使用事务或唯一约束保证幂等,避免重复通知造成重复入账。
markOrderPaid($orderNo, $tradeNo);
}
exit('success');
rsaCheckV1 的参数形式可能因 SDK 版本而异,应以当前项目安装的官方 SDK 接口为准。验签失败时不能更新订单,也不能只根据前端提交的“支付成功”修改订单状态。
让支付页面主动查询订单状态
异步通知更新数据库后,支付页面可以每隔两三秒查询一次本站的订单状态:
const timer = setInterval(async () => {
try {
const response = await fetch(
`/payment/status?order_no=${encodeURIComponent(orderNo)}`,
{
method: 'GET',
credentials: 'same-origin',
cache: 'no-store'
}
);
if (!response.ok) {
return;
}
const result = await response.json();
if (result.paid === true) {
clearInterval(timer);
window.location.href =
`/payment/success?order_no=${encodeURIComponent(orderNo)}`;
}
} catch (error) {
console.error('查询支付状态失败', error);
}
}, 3000);
状态接口只能读取服务器数据库中的支付结果,不能相信浏览器自行提交的支付状态。接口还要验证订单是否属于当前登录用户,以免泄露其他订单的信息。
如果异步通知暂时没有到达,服务端也可以调用 alipay.trade.query 主动查询支付宝订单状态,但要设置合理的查询频率和超时时间,不能让前端无限高频调用支付宝接口。
按这个顺序排查
- 确认扫码后的白屏出现在支付宝客户端,还是网站浏览器。
- 记录
alipay.trade.precreate返回的完整错误字段,确认code为10000。 - 确认二维码使用的数据确实是响应中的
qr_code。 - 查看 PHP、Web 服务器和框架日志,找到与白屏对应的异常。
- 在浏览器 Network 面板中检查接口是否返回
500、空内容或无效 JSON。 - 确认沙箱环境和正式环境的配置没有混用。
- 检查应用私钥、支付宝公钥和
RSA2签名配置。 - 从公网测试
notify_url是否可以访问,并确认该地址没有登录验证或重定向。 - 查看通知日志和验签结果,确认本地订单是否已更新。
- 为支付页面增加订单状态轮询,在付款成功后跳转到成功页面。
注意事项
- 不要在日志中完整记录应用私钥、签名值、用户隐私数据或支付凭证。
- 金额要使用字符串或高精度十进制处理,不要直接使用浮点数比较。
out_trade_no必须唯一,同一订单重复提交时也要保证幂等。- 支付是否成功,应以验签通过的支付宝通知或服务端主动查询结果为准。
- “没有提示”不能作为有效的错误信息。先记录预下单响应和异常,通常很快就能确定问题出在配置、代码、网络回调,还是前端缺少状态更新逻辑。
备注:内容仅供参考。
