PAYATHON 2026

网站对接支付宝当面付后无法跳转,如何排查?

支付阿杰

明确结论

付款成功后页面没有跳转,通常不是支付宝扣款失败,而是主题自带的支付接口没有完整处理支付结果确认和页面跳转。

支付宝当面付一般通过 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 能正常工作,可以用来对照排查,但不适合直接复制它的通知处理文件。插件与主题可能采用不同的订单表、状态字段、登录体系和业务钩子。直接混用可能导致订单付款后没有发货,甚至重复发货。

建议按以下步骤处理:

  1. 对比两套接口创建订单时使用的参数和通知地址;
  2. 确认主题自己的回调能够完成验签并更新订单;
  3. 为主题支付页面补充状态轮询;
  4. 轮询确认成功后,跳转到主题规定的订单结果页;
  5. 使用全新的小额测试订单验证整个流程。

注意事项

支付结果必须以服务端验签后的通知或主动查单结果为准,不能相信前端传入的“支付成功”状态。同步返回页面和浏览器跳转只用于改善用户体验,不能作为发货、开通会员或交付下载内容的唯一依据。

修改主题时,建议使用子主题、自定义插件或主题提供的钩子,避免代码在主题升级后被覆盖。同时保留支付创建、异步通知、验签结果、订单更新和前端查单等环节的日志,这些记录通常足以定位故障所在。

备注:内容仅供参考。