支付宝面对面支付接口付款后二维码不跳转是什么原因?
结论
支付宝面对面支付完成后,二维码页面没有自动跳转,通常并不代表支付失败,而是网站页面尚未获取支付状态。
alipay.trade.precreate 只负责生成二维码,不会控制浏览器跳转。用户扫码付款后,商户系统需要接收支付宝的异步通知,或主动调用 alipay.trade.query 查询订单状态。确认支付成功后,再由前端跳转到成功页。
常见原因
1. 只生成二维码,没有检测支付结果
如果页面只是调用 alipay.trade.precreate 获取 qr_code 并展示二维码,付款后页面不会自行变化。二维码只是支付入口,无法通知当前网页执行跳转。
用户用手机扫描电脑上的二维码时,付款操作发生在手机支付宝中,电脑浏览器无法直接得知支付已经完成。
2. notify_url 未配置或无法访问
支付成功后,支付宝可以向商户配置的 notify_url 发送异步通知。以下情况可能导致通知失败:
- 没有设置
notify_url - 地址只能从内网访问
- 域名、HTTPS 证书或网络配置异常
- 接口被登录验证、CSRF 校验或防火墙拦截
- 通知接口返回的内容不符合支付宝要求
- 接口处理时间过长,支付宝没有及时收到响应
异步通知发送给商户服务器,不会直接触发用户浏览器跳转。服务器收到通知后,还要更新本地订单状态。
3. 前端没有轮询或实时通信机制
即使服务器已经收到支付通知,如果二维码页面不再查询本地订单状态,页面仍然不会自动跳转。
常见的处理方式包括:
- 前端定时轮询商户订单查询接口
- WebSocket
- Server-Sent Events(SSE)
普通扫码支付页面通常使用短时间轮询,实现起来相对简单。
4. 把 return_url 当成二维码页面的跳转机制
面对面支付预创建交易使用的主要接口是 alipay.trade.precreate。扫码付款发生在支付宝客户端,不能直接依靠网页支付中的同步返回机制控制原二维码页面。
某个参数是否受支持,应以当前使用的支付宝接口文档和 SDK 定义为准。仅设置 return_url,并不能保证电脑端二维码页面自动跳转。
5. 支付状态判断错误
调用 alipay.trade.query 后,需要根据 trade_status 判断交易结果。常见状态如下:
WAIT_BUYER_PAY:等待买家付款TRADE_SUCCESS:交易支付成功TRADE_FINISHED:交易结束TRADE_CLOSED:交易关闭
通常只有检测到 TRADE_SUCCESS 或 TRADE_FINISHED 后,页面才跳转到成功页。实际业务中还要核对订单金额、商户订单号,并结合退款规则处理。
6. 商户订单号不一致
创建二维码、接收通知和查询订单时使用的 out_trade_no,必须指向同一笔商户订单。
如果前端查询了错误的订单号,或重新生成二维码时更换了 out_trade_no,就可能出现支付宝已经扣款,但页面始终显示等待支付的情况。
推荐实现流程
完整的扫码支付流程可以按以下方式处理:
- 商户服务器生成唯一的
out_trade_no。 - 调用
alipay.trade.precreate创建交易并获取qr_code。 - 前端展示二维码,同时定时查询商户服务器中的订单状态。
- 支付宝向
notify_url发送异步通知。 - 商户服务器验签并核对订单信息。
- 商户服务器把本地订单更新为已支付。
- 前端查询到已支付状态后停止轮询,并跳转到成功页。
- 如果异步通知暂时没有到达,服务器可以调用
alipay.trade.query主动确认。
前端轮询示例
<div id="payment-status">等待支付</div>
<script>
const outTradeNo = "ORDER_202609090001";
let checking = false;
const timer = setInterval(async () => {
if (checking) {
return;
}
checking = true;
try {
const response = await fetch(
`/api/payment/status?out_trade_no=${encodeURIComponent(outTradeNo)}`,
{
method: "GET",
cache: "no-store"
}
);
if (!response.ok) {
return;
}
const result = await response.json();
if (result.status === "paid") {
clearInterval(timer);
document.getElementById("payment-status").textContent = "支付成功";
window.location.replace(
`/payment/success?out_trade_no=${encodeURIComponent(outTradeNo)}`
);
} else if (result.status === "closed") {
clearInterval(timer);
document.getElementById("payment-status").textContent = "订单已关闭";
}
} catch (error) {
console.error("查询支付状态失败", error);
} finally {
checking = false;
}
}, 2000);
</script>
轮询间隔没有适用于所有系统的固定值。可以先设置为 2~5 秒,再根据接口限流、并发量和用户体验调整,同时设置最长查询时间。
服务端状态查询示例
以下代码仅用于演示处理逻辑。支付宝 SDK 的类名和调用方式可能随语言及 SDK 版本变化,请以实际安装的官方 SDK 为准。
app.get("/api/payment/status", async (req, res) => {
const outTradeNo = req.query.out_trade_no;
// 必须验证当前用户是否有权查询这笔订单
const order = await orderRepository.findByOutTradeNo(outTradeNo);
if (!order) {
return res.status(404).json({ status: "not_found" });
}
if (order.paymentStatus === "paid") {
return res.json({ status: "paid" });
}
if (order.paymentStatus === "closed") {
return res.json({ status: "closed" });
}
// 可按业务需要调用 alipay.trade.query 主动确认
const trade = await queryAlipayTrade({
out_trade_no: outTradeNo
});
if (
trade.trade_status === "TRADE_SUCCESS" ||
trade.trade_status === "TRADE_FINISHED"
) {
await orderRepository.markPaid(outTradeNo, trade.trade_no);
return res.json({ status: "paid" });
}
if (trade.trade_status === "TRADE_CLOSED") {
await orderRepository.markClosed(outTradeNo);
return res.json({ status: "closed" });
}
return res.json({ status: "waiting" });
});
异步通知处理要点
通知接口不能只看到“支付成功”字段就直接发货,至少需要完成以下检查:
1. 使用支付宝公钥验证通知签名
2. 检查 app_id 是否属于当前商户应用
3. 检查 out_trade_no 是否存在
4. 检查 total_amount 是否与本地订单金额一致
5. 检查 seller_id 等商户信息是否符合预期
6. 确认 trade_status 为可接受的支付成功状态
7. 以幂等方式更新订单
8. 处理成功后返回纯文本 success
示意代码如下:
app.post("/api/alipay/notify", async (req, res) => {
const params = req.body;
const verified = verifyAlipayNotification(params);
if (!verified) {
return res.send("failure");
}
const order = await orderRepository.findByOutTradeNo(
params.out_trade_no
);
if (!order) {
return res.send("failure");
}
if (String(order.totalAmount) !== String(params.total_amount)) {
return res.send("failure");
}
if (
params.trade_status === "TRADE_SUCCESS" ||
params.trade_status === "TRADE_FINISHED"
) {
// 必须幂等:重复通知不能重复发货、重复入账
await orderRepository.markPaid(
params.out_trade_no,
params.trade_no
);
}
return res.type("text/plain").send("success");
});
verifyAlipayNotification 不能用自行编写的字符串比较代替。必须使用支付宝 SDK 提供的验签功能,并正确配置支付宝公钥。具体方法名取决于使用的语言和 SDK 版本。
排查步骤
可以按照以下顺序排查:
- 在支付宝商户后台或服务端日志中确认交易是否确实成功。
- 使用
out_trade_no调用alipay.trade.query,检查trade_status。 - 检查创建交易时是否配置了正确的
notify_url。 - 查看服务器访问日志,确认支付宝是否请求过通知接口。
- 检查异步通知是否通过验签,金额是否一致。
- 确认通知接口最后返回的是纯文本
success。 - 检查本地订单是否已更新为已支付。
- 在浏览器开发者工具中查看状态查询请求是否持续发送。
- 检查前端判断条件和跳转代码是否执行。
- 确认前端查询的
out_trade_no与创建二维码时使用的值完全一致。
注意事项
- 不要根据前端传来的“支付成功”结果直接发货,最终状态必须由服务器确认。
- 支付宝可能重复发送异步通知,因此订单更新和发货逻辑必须支持幂等。
- 前端跳转只影响用户体验,不能作为交易成功的唯一凭据。
- 不要把支付宝私钥、公钥配置或完整通知参数输出到前端。
- 主动查询支付宝接口时需要控制频率,避免每次浏览器轮询都无条件调用支付宝。
- 更稳妥的方式是先查询本地订单。只有本地状态尚未更新时,才按一定频率调用
alipay.trade.query。 - 用户关闭页面、网络断开或跳转失败,都不应影响服务端更新订单和履行业务。
备注:内容仅供参考。
