网站对接支付宝当面付后无法跳转,如何排查?
明确结论
付款成功后页面没有跳转,通常不是支付宝扣款失败,而是主题自带的支付接口没有完整处理支付结果确认和页面跳转。
支付宝当面付一般通过 alipay.trade.precreate 生成二维码。用户扫码付款后,支付宝会向商户服务器发送异步通知,但不会控制原付款页面跳转。支付页面需要主动轮询订单状态,或由后端调用 alipay.trade.query 查询订单。确认付款成功后,前端才能执行跳转。
ErphpDown 接口可以正常跳转,说明支付宝应用配置、签名密钥和基础网络环境大概率正常。应重点排查主题接口的异步通知、订单状态更新、前端轮询和跳转逻辑。
先确认使用的是哪种支付流程
不同支付宝接口的跳转方式并不一样:
alipay.trade.page.pay:电脑网站支付,通常通过return_url同步返回页面。alipay.trade.wap.pay:手机网站支付,也支持同步返回地址。alipay.trade.precreate:当面付扫码支付,主要依赖notify_url异步通知,不会自动让二维码所在页面跳转。
如果主题使用 alipay.trade.precreate,只配置 return_url 通常无法解决问题。主题还需要实现订单状态轮询,并在确认成功后执行前端跳转。
建议按以下顺序排查
1. 对比插件与主题实际调用的接口
分别查看 ErphpDown 和主题创建支付订单时提交的 method:
alipay.trade.precreate
alipay.trade.page.pay
alipay.trade.wap.pay
如果 ErphpDown 使用电脑网站支付,主题使用当面付,两者的跳转行为不同属于正常现象。
如果二者都使用 alipay.trade.precreate,再比较以下内容:
notify_url是否不同;- 商户订单号
out_trade_no的生成方式; - 异步通知处理地址能否正常访问;
- 支付页面是否定时查询订单状态;
- 查询成功后是否调用了跳转代码。
2. 检查支付宝异步通知是否到达
确认主题传入的 notify_url 是完整的公网 HTTPS 地址,例如:
$bizContent = [
'out_trade_no' => $orderNo,
'total_amount' => number_format($amount, 2, '.', ''),
'subject' => $subject,
];
$request->setNotifyUrl('https://example.com/alipay/notify');
通知地址需要满足以下条件:
- 可以从公网直接访问;
- 不要求用户登录;
- 不会被登录验证、验证码或维护页面拦截;
- 不会发生错误的 HTTP/HTTPS 跳转;
- 没有被防火墙、CDN、WAF 或安全插件阻止;
- PHP 执行过程中没有致命错误;
- 支付宝应用、公钥配置和验签逻辑相互匹配。
可以在通知入口记录必要日志,但不要写入私钥、完整签名或其他敏感信息:
error_log(sprintf(
'[Alipay Notify] out_trade_no=%s trade_status=%s trade_no=%s',
$_POST['out_trade_no'] ?? '',
$_POST['trade_status'] ?? '',
$_POST['trade_no'] ?? ''
));
如果日志中完全没有相关记录,先检查 notify_url、服务器访问日志、防火墙和路由规则。
3. 确认异步通知已经正确验签
不能仅凭请求中的 trade_status 判断付款成功。程序必须先使用支付宝 SDK 验签,再核对订单信息。
下面只展示处理结构,具体类名和初始化方式应以项目当前使用的支付宝 SDK 为准:
<?php
$postData = $_POST;
if (!$alipayClient->rsaCheckV1($postData, null, 'RSA2')) {
http_response_code(400);
echo 'failure';
exit;
}
$orderNo = $postData['out_trade_no'] ?? '';
$tradeNo = $postData['trade_no'] ?? '';
$tradeStatus = $postData['trade_status'] ?? '';
$totalAmount = $postData['total_amount'] ?? '';
$appId = $postData['app_id'] ?? '';
$order = findOrderByOrderNo($orderNo);
if (!$order) {
echo 'failure';
exit;
}
if (
$appId !== ALIPAY_APP_ID ||
bccomp((string) $totalAmount, (string) $order['amount'], 2) !== 0
) {
echo 'failure';
exit;
}
if (in_array($tradeStatus, ['TRADE_SUCCESS', 'TRADE_FINISHED'], true)) {
markOrderPaidIdempotently($orderNo, $tradeNo);
echo 'success';
exit;
}
echo 'failure';
除了验签,至少还要核对:
app_id是否属于当前应用;out_trade_no是否存在;total_amount是否与本地订单金额一致;- 订单是否已经支付;
trade_no是否重复绑定到了其他订单。
支付宝可能重复发送异步通知,因此订单更新必须具有幂等性。处理成功后应直接输出:
success
success 前后不要输出 HTML、调试信息、BOM 或 PHP 警告。
4. 检查主题是否更新了正确的订单表
支付宝通知成功,不代表网站中的订单状态已经更新。主题和 ErphpDown 可能使用不同的数据表、订单字段或状态值。
例如,插件查询的是:
wp_erphpdown_orders
主题可能维护着自己的订单表。需要确认主题的通知处理程序是否根据同一个 out_trade_no 更新了正确记录。
重点检查:
- 创建订单时保存的订单号;
- 通知中收到的
out_trade_no; - SQL 更新条件;
- 支付成功对应的状态值;
- 数据表前缀;
- 数据库事务是否提交;
- 程序是否因金额类型或订单状态不匹配而提前返回。
付款后可以直接查看该订单在数据库中的状态。如果数据库仍显示“待支付”,问题出在通知或订单更新流程;如果已经显示“已支付”,则主要检查前端轮询和跳转逻辑。
5. 检查支付页面是否轮询订单状态
扫码支付页面通常需要定期请求后端,确认订单是否已经支付:
const orderNo = window.currentOrderNo;
const successUrl = '/payment/success';
const timer = window.setInterval(async () => {
try {
const response = await fetch(
`/api/payment/status?out_trade_no=${encodeURIComponent(orderNo)}`,
{
method: 'GET',
credentials: 'same-origin',
cache: 'no-store'
}
);
if (!response.ok) {
return;
}
const result = await response.json();
if (result.paid === true) {
window.clearInterval(timer);
window.location.assign(successUrl);
}
} catch (error) {
console.error('查询支付状态失败', error);
}
}, 2000);
后端状态接口只负责返回订单状态,不能根据浏览器提供的金额判断订单是否成功:
<?php
$orderNo = $_GET['out_trade_no'] ?? '';
$order = findOrderForCurrentUser($orderNo);
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
if (!$order) {
http_response_code(404);
echo json_encode([
'paid' => false,
'message' => '订单不存在',
], JSON_UNESCAPED_UNICODE);
exit;
}
echo json_encode([
'paid' => $order['status'] === 'paid',
], JSON_UNESCAPED_UNICODE);
实际项目还要校验当前用户、订单归属或不可伪造的查询凭证,防止他人枚举订单号。
6. 使用浏览器开发者工具定位前端问题
打开支付页面后按 F12,查看 Console 和 Network:
- 是否出现 JavaScript 语法错误;
- 是否发出了订单状态查询请求;
- 请求是否返回
404、403或500; - 返回内容是否确实为 JSON;
- JSON 字段名是否与前端判断条件一致;
- 请求是否被缓存;
- 是否出现跨域或混合内容错误;
- 成功后是否执行了
window.location; - 跳转地址是否为空或被错误拼接。
例如,后端返回:
{
"status": "success"
}
前端判断的却是:
if (result.paid === true) {
// ...
}
字段不一致时,即使付款成功,页面也会一直停留在原处。
还要确认主题依赖的 JavaScript 文件是否正常执行。文件压缩、合并、延迟加载或 CDN 缓存都可能影响脚本运行。可以暂时关闭相关优化功能,清理缓存后重新测试。
7. 必要时增加主动查单作为补偿
异步通知可能因为临时网络问题而延迟。如果轮询接口发现本地订单仍处于待支付状态,可以受控地调用 alipay.trade.query 主动查单,再更新本地状态。
请求参数通常包含商户订单号:
$request = new AlipayTradeQueryRequest();
$request->setBizContent(json_encode([
'out_trade_no' => $orderNo,
], JSON_UNESCAPED_UNICODE));
$response = $alipayClient->execute($request);
$result = $response->alipay_trade_query_response ?? null;
if (
$result &&
($result->code ?? '') === '10000' &&
in_array($result->trade_status ?? '', ['TRADE_SUCCESS', 'TRADE_FINISHED'], true)
) {
// 仍需核对订单号、金额、应用身份,并以幂等方式更新订单。
markOrderPaidIdempotently($orderNo, $result->trade_no ?? '');
}
不要让浏览器直接调用支付宝接口,也不要把应用私钥放到前端。主动查单需要设置频率限制,不能每隔一两秒无限请求支付宝。
根据现象快速判断故障位置
可以根据订单状态快速缩小排查范围:
- 支付宝后台显示成功,但服务器没有收到通知:检查
notify_url、路由、HTTPS、防火墙和服务器日志。 - 已经收到通知,但验签失败:检查支付宝公钥、应用环境、签名类型和字符处理。
- 验签成功,但数据库仍未更新:检查订单号、金额校验、数据表和更新条件。
- 数据库已经更新,但页面没有跳转:检查前端轮询、接口返回格式和 JavaScript 报错。
- 前端已经识别付款成功,但跳转失败:检查目标 URL、浏览器控制台、安全策略,以及后续脚本是否中断了跳转代码。
不建议直接复制 ErphpDown 的回调代码
ErphpDown 能正常工作,可以用来对照排查,但不适合直接复制它的通知处理文件。插件与主题可能采用不同的订单表、状态字段、登录体系和业务钩子。直接混用可能导致订单付款后没有发货,甚至重复发货。
建议按以下步骤处理:
- 对比两套接口创建订单时使用的参数和通知地址;
- 确认主题自己的回调能够完成验签并更新订单;
- 为主题支付页面补充状态轮询;
- 轮询确认成功后,跳转到主题规定的订单结果页;
- 使用全新的小额测试订单验证整个流程。
注意事项
支付结果必须以服务端验签后的通知或主动查单结果为准,不能相信前端传入的“支付成功”状态。同步返回页面和浏览器跳转只用于改善用户体验,不能作为发货、开通会员或交付下载内容的唯一依据。
修改主题时,建议使用子主题、自定义插件或主题提供的钩子,避免代码在主题升级后被覆盖。同时保留支付创建、异步通知、验签结果、订单更新和前端查单等环节的日志,这些记录通常足以定位故障所在。
备注:内容仅供参考。
