支付宝H5支付流程

使用:location.replace(res.data) 跳支付宝签约页,支付宝 WebView 里是覆盖式打开签约收银台,原中间页保留在 WebView 页面栈底层;用户点【完成】,WebView 回退历史栈,把底层中间页重新显示出来。

关键点区分:

  1. return_url支付宝完成签约 / 支付后,主动 302 跳转的目标页面(由后端在组装交易参数时指定)
  2. 异步 notify:后端接收结果,不能干预支付宝 "完成" 按钮的点击行为,只能决定 return_url 页面渲染什么内容

1、两种方案

方案 A:不配置 return_url /return_url = 当前中间页

  • 前端:location.replace(支付宝网关)
  • 行为:支付宝收银台覆盖打开;用户点完成,WebView 回退历史,回到原来的中间页
  • 问题:中间页还保留旧状态(还在 loading、还在等待唤起支付),用户看到旧页面。
  • 后端无法直接修改这个底层页面内容,页面是前端静态渲染在 WebView 缓存里的。
  • 方案:使用直接关闭当前界面,不影响支付宝签约和支付界面
  • 为什么不直接调用closeCurrentPage() 因为之前的 setTimeout(closeCurrentPage, 800) 不可靠------此时中间页在后台,关闭后台 webview 容易失败,且 800ms 时用户可能还在签约,无法正常关闭
csharp 复制代码
// 去支付宝 直接调方法
this.bindAlipayReturnClose();
location.replace(res.data);

// 监听用户从支付宝签约页返回(成功签约或点击返回),关闭中间页
    // 支付宝签约页是覆盖在当前页之上,当前页不会重新加载,因此靠可见性/焦点事件判断返回
    bindAlipayReturnClose() {
        const _this = this;
        let closed = false;
        const doClose = () => {
            if (closed) return;
            closed = true;
            setTimeout(() => {
                _this.closeCurrentPage();
            }, 500);
        };
        // 页面由后台变为前台可见(签约页关闭后回到当前页)
        document.addEventListener('visibilitychange', function onVisibilityChange() {
            if (!document.hidden) {
                document.removeEventListener('visibilitychange', onVisibilityChange);
                doClose();
            }
        });
        // 窗口重新获得焦点(部分机型/支付宝版本以 focus 为准)
        window.addEventListener('focus', function onFocus() {
            window.removeEventListener('focus', onFocus);
            doClose();
        });
        // 兜底:若事件未触发(极少数 webview 不派发),延迟一段时间后关闭
        setTimeout(doClose, 5000);
    },
    // 关闭当前页面(兼容微信/支付宝内置浏览器)
    closeCurrentPage() {
        if (this.checkEnv('weixin') && typeof window.WeixinJSBridge !== 'undefined') {
            window.WeixinJSBridge.call('closeWindow');
        } else if (this.checkEnv('alipay')) {
            if (typeof window.AlipayJSBridge !== 'undefined') {
                window.AlipayJSBridge.call('closeWebview');
            } else {
                // AlipayJSBridge 未就绪时延迟再试
                setTimeout(() => {
                    if (typeof window.AlipayJSBridge !== 'undefined') {
                        window.AlipayJSBridge.call('closeWebview');
                    } else {
                        window.close();
                    }
                }, 300);
            }
        } else {
            window.close();
        }
    },

方案 B :后端在交易参数传入 return_url,指向独立结果页

在后端组装 alipay.trade.wap.pay(支付同时签约)biz_content,增加 return_url:https://xxx/h5/pay-result?qr_session_id=xxx

  1. 用户完成支付签约,点【完成】
  2. 支付宝 WebView不再回退历史栈,直接 302 跳转到你 return_url 指定的独立结果页面
  3. 结果页面是全新加载,页面初始化时调用后端接口查询本次qr_session_id的真实状态,渲染对应的 UI:
    • 支付成功 + 签约成功
    • 支付成功,但签约失败
    • 支付失败

这个页面的内容完全由后端控制:页面请求后端查询会话状态,后端返回状态,前端渲染对应文案。
⚠️重要:return_url 只是同步跳转页面 ,页面展示文案从后端实时查单,业务最终结果必须以异步 notify 为准,return_url 仅用于展示给用户看

2、理解页面栈,选择合适自己的方案

支付宝 App 内置 WebView 里:

location.replace 只是替换当前页面地址,WebView 的宿主容器没有销毁页面实例,支付宝收银台是在同 webview 上层覆盖。点击完成,webview 的回退逻辑,重新展示底层页面。

  • 就算你前端用location.replace,支付宝 WebView 的历史栈行为和普通浏览器不完全一致;单纯前端 JS 很难彻底干掉底层页面缓存。
  • 最好解法:配置 return_url 跳独立结果页,而不是让它退回中间发起页

3、前后端改动点

后端

  1. 在组装alipay.trade.wap.pay参数时,增加 return_url 参数,带上 1. qr_session_id。

    {
    "out_trade_no":"xxx",
    "total_amount":"34",
    "subject":"套餐",
    "return_url":"https://xxx/h5/pay-result?qr_session_id=tradeMemberczzx1788835682033311",
    "agreement_sign_params":{...}
    }

  2. 新增【H5 结果页查单接口】,入参 qr_session_id,返回当前会话状态(和 PC 轮询用同一套状态枚举)。

  3. 原有两个异步通知(支付 notify、签约 sign_notify)保持不变,异步通知才是更新会话状态的权威入口。

前端

  1. 保留原来的中间发起页(扫码进入、请求后端获取参数、唤起支付宝)。
  2. 新建独立结果页 pay-result.html
    • 页面加载后读取 url 上qr_session_id,调用后端查单接口
    • 根据后端返回的状态渲染不同 UI 文案
    • 页面提示:可回到 PC 查看最终结果
  3. 中间页不用复杂 history 清理逻辑,交给 return_url 跳转,避免 WebView 页面栈坑。

4、备选兜底方案(不想新增独立页面)

return_url 仍然指向中间页,但是在中间页增加URL 状态标记

  • return_url 拼接参数 ?isResult=1&qr_session_id=xxx
  • 中间页前端 onload 判断:如果 url 带isResult=1,不再执行唤起支付逻辑,直接调用后端查单,展示结果。
  • 或是直接调上面 closeCurrentPage() 方法,关闭当前界面

缺点:底层页面实例被缓存,部分机型返回时会短暂闪现旧页面,体验不如独立结果页。

5、JSAPI 模式补充(如果你切回 alipayjsapi 方案)

如果用 AlipayJSBridge tradePay唤起支付 + 签约弹窗,不走 location.replace 跳转网关 ,是原生弹窗浮层。

用户关闭弹窗后直接回到当前 H5 页面,JSAPI 拿到同步回调;页面主动请求后端查单,渲染结果。

JSAPI 场景不需要 return_url,但依然要后端接口查询真实状态。

6、关键提醒

  1. 后端不能直接 "推送页面内容" 到 WebView,只能通过 return_url 控制跳转目标地址;页面内容是页面主动请求后端接口查询状态来渲染。
  2. return_url 跳转只是用户体验层展示,不能作为业务成功凭证;签约、支付成功以 notify 异步通知为准。
  3. 多分支状态一定要全部处理:支付成功签约成功 / 支付成功签约拒绝 / 支付失败。
相关推荐
悟空码字13 天前
支付宝限额、搜索功能变了
支付宝·搜索·限额
tonydf2 个月前
接入服务商模式的支付宝支付
后端·支付宝
暗冰ཏོ2 个月前
Spring Boot + Vue3 订单支付模块技术实现:支付单、回调验签、幂等处理与余额扣减
前端·spring boot·后端·vue·微信支付·支付宝支付
stark张宇4 个月前
支付宝 App 支付踩坑记:x509 证书不匹配 & 应用未上线问题解决
后端·支付宝
SkyWalking中文站4 个月前
用 SkyWalking 监控微信和支付宝小程序
微信·微信小程序·支付宝
追光少年33226 个月前
支付宝第三方登录集成教程:从申请到实现
前端开发·支付宝·oauth2.0·后端开发·第三方登录·api集成
kong79069287 个月前
订单支付-签约支付
支付宝·订单支付·签约支付
青茶3608 个月前
支付宝支付 报错 invalid [default store dir]: /tmp/
支付·支付宝
Tancenter9 个月前
支付宝小程序的用户登录/注册流程
小程序·登录·注册·支付宝