支付宝扫码打开h5收银台后如何控制支付完成页跳转
结论
支付完成页中“完成”按钮的跳转地址,一般需要在创建支付订单时通过同步返回地址参数指定。支付宝手机网站支付通常使用 return_url;如果接入的是间联收单机构的统一下单接口,则需要查阅该机构的接口文档,确认其使用的是 return_url、front_url、redirect_url,还是其他表示“支付完成跳转地址”的参数。
这个跳转能否由商户控制,取决于间联机构是否会将参数透传给支付宝。如果接口没有提供相应参数,或不支持透传,商户前端就无法直接修改支付宝收银台“完成”按钮的行为,需要联系收单机构开通或补充这一能力。
为什么不能只在前端设置
用户进入支付宝收银台后,支付结果页由支付宝客户端或支付宝页面控制,已经脱离商户自己的 H5 页面。因此,在原商户页面中执行下面的代码,无法修改支付宝结果页的“完成”按钮:
window.location.href = 'https://merchant.example.com/pay/result';
这段代码只能控制商户页面本身的跳转。
还要注意区分两个容易混淆的地址:
return_url:供用户同步跳转的地址。支付后点击“完成”时,用户可能会返回这个地址。notify_url:支付宝或收单机构向商户服务端发送支付结果通知的地址,与用户是否点击“完成”无关。
支付状态必须以服务端收到的异步通知或主动查询的结果为准,不能仅根据浏览器是否发生跳转来判断支付成功。
解决步骤
1. 检查间联统一下单接口
先在统一下单接口文档中查找以下类型的字段:
return_url
front_url
redirect_url
success_url
callback_url
不同机构对 callback_url 的定义并不一致,有些机构用它表示异步通知地址,因此具体含义必须以收单机构的接口文档为准。
如果接口同时提供前台返回地址和后台通知地址,需要分别设置:
{
"return_url": "https://merchant.example.com/pay/result",
"notify_url": "https://api.merchant.example.com/payment/notify"
}
字段名称、所在层级和签名规则都要遵循间联机构的定义,不能直接套用支付宝开放平台的格式。
2. 将跳转地址放入下单请求
假设间联接口使用 return_url 表示支付完成后的前台返回地址,可以按下面的结构组织请求:
{
"merchant_order_no": "ORDER_202609090001",
"amount": "10.00",
"subject": "门店消费",
"return_url": "https://merchant.example.com/pay/result?order_no=ORDER_202609090001",
"notify_url": "https://api.merchant.example.com/payment/notify"
}
这只是结构示例,不代表某个收单机构实际使用的参数名称。金额单位、签名字段和 URL 编码要求,都应以对方的接口文档为准。
如果间联机构最终调用支付宝手机网站支付接口 alipay.trade.wap.pay,通常需要由该机构把商户传入的地址映射到支付宝请求的顶层参数 return_url。
3. 在返回页面重新查询订单状态
用户返回指定页面后,不要马上展示“支付成功”。返回参数可能被伪造,用户也可能在支付结果尚未同步时进入该页面。
前端应携带商户订单号,向自己的服务端查询订单状态:
<script>
const params = new URLSearchParams(window.location.search);
const orderNo = params.get('order_no');
async function loadPaymentResult() {
if (!orderNo) {
document.body.textContent = '缺少订单号';
return;
}
const response = await fetch(
`/api/orders/${encodeURIComponent(orderNo)}/payment-status`,
{
method: 'GET',
credentials: 'include'
}
);
if (!response.ok) {
document.body.textContent = '订单状态查询失败,请稍后重试';
return;
}
const result = await response.json();
if (result.status === 'PAID') {
window.location.replace('/pay/success');
} else if (result.status === 'PENDING') {
document.body.textContent = '支付结果确认中,请稍后刷新';
} else {
document.body.textContent = '订单尚未支付';
}
}
loadPaymentResult();
</script>
服务端返回的状态应来自已经验证过的异步通知处理结果,或者由服务端调用收单机构的订单查询接口获取。
4. 正确处理异步通知
服务端收到发往 notify_url 的请求后,需要完成以下处理:
- 验证签名和通知来源。
- 核对商户订单号、收单机构交易号、金额及商户身份。
- 确认支付状态是否成功。
- 幂等更新订单,防止重复通知造成重复发货或重复记账。
- 按接口协议返回确认内容。
示意代码如下:
@PostMapping("/payment/notify")
public String paymentNotify(HttpServletRequest request) {
Map<String, String> parameters = extractParameters(request);
if (!paymentService.verifySignature(parameters)) {
return "fail";
}
String orderNo = parameters.get("merchant_order_no");
String tradeStatus = parameters.get("trade_status");
if ("SUCCESS".equals(tradeStatus)) {
paymentService.confirmPaymentIdempotently(orderNo, parameters);
}
return "success";
}
代码中的字段名、支付成功状态值和通知响应格式仅作示意,实际实现必须遵循间联机构的协议。
如果接口没有跳转参数
如果统一下单接口不支持设置前台返回地址,可以依次确认:
- 询问间联收单机构是否存在未公开或需要单独开通的前台跳转参数。
- 确认该机构使用的支付宝产品是否支持
return_url。 - 要求机构在向支付宝下单时透传商户提供的返回地址。
- 如果对方明确不支持,商户就无法控制支付宝结果页的“完成”按钮。
这种情况下,可以调整商户 H5 自己的支付流程,例如在发起支付前保留订单页,并在用户回到商户页面后轮询支付状态。但这种处理无法保证用户点击支付宝“完成”后一定返回指定页面。
注意事项
return_url只用于用户跳转,不能作为可靠的支付成功凭证。- 用户可能不点击“完成”,而是直接关闭页面;网络问题也可能导致跳转失败。
- 跳转地址建议使用公网可访问的 HTTPS 地址。
- 不要允许客户端随意传入跳转域名,否则可能产生开放重定向漏洞。服务端应使用固定域名或域名白名单。
- 不要在 URL 中传递手机号、用户身份凭证、支付密钥等敏感信息。
- 返回地址包含查询参数时,应按照收单机构的要求进行 URL 编码,防止
&、?等字符影响请求解析或签名。 - 支付宝客户端、系统浏览器和内置 WebView 的返回行为可能不同,需要分别测试。
- 如果码牌可能由多人扫码使用,订单号必须由服务端生成,并正确关联当前交易、门店和金额。
备注:内容仅供参考。
