三方支付H5支付完成后关闭网页且未跳转完成页
结论
支付完成后点击”完成”,H5 页面直接关闭,通常是因为创建支付订单时没有设置同步跳转地址 return_url。支付平台完成收银流程后找不到业务页面的返回地址,可能会关闭当前支付窗口,也可能回到发起支付的客户端。
创建支付订单时,需要传入可通过公网访问的 return_url,同时配置异步通知地址 notify_url。两者用途不同:
return_url负责支付完成后的页面跳转。notify_url负责让服务端确认支付结果。- 成功页或
return_url中的参数不能作为订单已支付的依据。
原因说明
H5 支付通常包含两条相互独立的流程:
- 用户在支付宝完成付款后,通过
return_url返回商户页面。 - 支付平台通过
notify_url,将支付结果异步通知商户服务器。
当前请求没有设置 return_url,因此支付平台完成付款后,没有明确的商户页面可供返回。具体表现取决于支付渠道、支付宝客户端和页面的打开方式。支付页面可能直接关闭,也可能返回上一个页面,不一定会进入业务系统的支付完成页。
如果三方支付服务商返回的是已经组装好的支付链接,还要确认是否应在创建订单时将 return_url 提交给服务商,而不是自行拼接到服务商返回的支付链接中。支付链接通常带有签名参数,直接修改可能导致验签失败,新增的参数也可能无法被支付渠道识别。
解决步骤
1. 创建支付订单时传入 return_url
将支付完成页的完整地址作为 return_url 提交,例如:
https://pay.example.com/payment/return
该地址通常需要满足以下要求:
- 可以通过公网访问。
- 使用 HTTPS。
- 不依赖仅保存在原页面中的临时状态。
- 符合三方支付平台对回跳域名、白名单和 URL 长度的要求。
- 作为表单或查询参数提交时,正确进行 URL 编码。
如果支付平台要求预先配置回跳域名,还需要在商户后台完成相应设置。
2. 同时配置 notify_url
异步通知地址示例:
https://api.example.com/payment/notify
notify_url 应由服务端接收,并完成以下处理:
- 按照支付平台的规则验证签名。
- 校验商户号、订单号、支付金额和币种。
- 根据交易状态更新本地订单。
- 做好幂等处理,避免重复通知造成重复入账。
- 按照接口协议返回成功响应。
不要将 notify_url 指向普通前端页面。这个地址接收的是支付平台服务器发起的通知请求,不是用户浏览器的跳转请求。
3. 让成功页主动查询订单状态
用户进入 return_url 后,不要立即显示”支付成功”。可以在地址中携带商户订单号,再由成功页调用业务服务器查询订单状态:
https://pay.example.com/payment/return?order_no=ORDER_202609130001
前端示例:
<script>
const params = new URLSearchParams(window.location.search);
const orderNo = params.get('order_no');
async function checkPayment() {
const response = await fetch(
`/api/payment/status?order_no=${encodeURIComponent(orderNo)}`
);
const result = await response.json();
if (result.status === 'PAID') {
document.body.textContent = '支付成功';
return;
}
if (result.status === 'CLOSED' || result.status === 'FAILED') {
document.body.textContent = '支付未完成';
return;
}
document.body.textContent = '支付结果确认中,请稍候';
setTimeout(checkPayment, 2000);
}
checkPayment();
</script>
状态查询接口必须从服务端读取本地订单状态,必要时再调用支付平台的订单查询接口。不要信任浏览器传入的支付状态。
创建支付请求示例
不同三方支付服务商使用的字段名称和签名规则可能不同。下面只展示通用结构,实际请求应以所接支付平台的接口文档为准:
{
"merchant_order_no": "ORDER_202609130001",
"amount": "99.00",
"subject": "商品订单",
"return_url": "https://pay.example.com/payment/return?order_no=ORDER_202609130001",
"notify_url": "https://api.example.com/payment/notify"
}
使用表单提交时,需要对 URL 参数进行编码:
const paymentParams = new URLSearchParams({
merchant_order_no: 'ORDER_202609130001',
amount: '99.00',
return_url:
'https://pay.example.com/payment/return?order_no=ORDER_202609130001',
notify_url: 'https://api.example.com/payment/notify'
});
fetch('https://third-party.example.com/create-payment', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: paymentParams.toString()
});
如果接口要求对参数签名,必须先按平台规定完成参数排序和编码,再生成签名。签名完成后,不要修改 return_url,也不要追加其他参与签名的参数。
如果配置后仍然没有跳转
可以按以下顺序排查:
- 检查实际创建订单的请求中是否包含
return_url。 - 检查三方支付服务商是否支持该参数,并确认服务商是否将其透传给支付宝。
- 确认参数名、编码方式和签名内容是否符合接口要求。
- 检查回跳地址是否为公网 HTTPS 地址,以及是否存在证书、重定向或域名白名单问题。
- 分别测试支付宝客户端、系统浏览器和 App 内
WebView的行为。 - 如果使用 App 内
WebView,确认宿主应用是否拦截了新窗口、深链或页面关闭事件。 - 查看异步通知和订单查询结果,确认支付是否成功。
部分支付产品或打开场景不保证付款后一定返回商户页面。比如,页面被支付宝客户端接管、原 H5 窗口已经失效,或者三方服务商没有透传 return_url,都可能导致页面直接关闭。因此,业务流程需要允许用户重新进入订单页,并根据服务端保存的订单状态恢复支付结果。
注意事项
return_url只用于改善用户体验,不能作为可靠的到账凭证。- 最终支付状态应以验签成功的异步通知或支付平台的订单查询结果为准。
- 不要根据
return_url中的status=success等前端参数直接发货。 - 异步通知可能重复、延迟,也可能先于页面跳转到达,因此订单更新必须支持幂等。
- 回跳地址中尽量只携带商户订单号或短期有效的业务标识,不要暴露敏感数据。
- 如果三方支付平台不支持
return_url,应在发起支付前保留订单入口,并提示用户付款后返回订单页查看结果。
备注:内容仅供参考。
