如何优雅地实现在请求的回调中跳转到第三方平台

541 阅读4分钟

这是我的第一篇掘金博客,开启掘金写作之路。

1. 背景

这是一个和支付相关的需求。在填写完基本信息之后,提交信息给后端,后端会返回一些跳转到第三方支付平台所必需的参数给前端,前端拿着这些参数跳转到第三方支付平台进行后续的操作。

需求的基本意思就是上面简短的一句话,本篇文章举一个简单的例子来说明其实现。 其UI简化图如下所示:

image.png

点击『去付款』按钮,要跳转到第三方平台的订单确认页面,如下图所示:

image.png

然后在第三方平台进行一系列的支付操作。

2. 方案

当时看到这个需求,想到了大概4种方案吧。

2.1 方案1

在前端页面的表单中,点击按钮『去付款』,发请求给后端,后台返回支付的url,前端在请求的回调中使用window.open(url)打开支付页面来实现第三方页面的跳转。

但是,该方案不可行,不可行的原因:

  1. 由于第三方平台的接口是POST方式的,后端无法将参数拼凑成url返回给前端直接使用;

  2. 在请求的回调中使用window.open(url)跳转会遭到浏览器拦截,体验不是太好,另外,window.open(url)本身也存在一些坑,具体有哪些坑我也不清楚,可以自行搜索。

2.2 方案2

在前端页面的表单中,点击按钮『去付款』,发请求给后端,获取必需参数,再带着这些参数跳转到第三方平台的订单确认页面。

前端页面的表单中设计两个按钮,分别是『生成付款单』和『去付款』。

点击『生成付款单』按钮,发请求给后端获取参数,此时隐藏『生成付款单』按钮,在该位置显示『去付款』按钮; 用户需要再次点击『去付款』按钮来触发跳转到第三方支付平台的订单确认页面。

但是,此方案也不可行,需要用户操作两次按钮,体验不好。

2.3 方案3

和方案2几乎一样,只是把两个按钮变成一个按钮,让用户操作一次就好,点击『去付款』按钮时,先使用window.open()打开一个空白窗口,(一般浏览器都是可以正常打开的,如果遇到某些浏览器,窗口被拦截了,就弹框提示其允许弹窗设置),然后发送异步请求获取必需参数,等请求结果返回后,构建一个form表单(该form表单设置样式为不可见),写入到空白窗口页中,执行form的submit()来自动触发跳转到第三方的支付确认页面。

最终采取的是这种方案。

2.4 方案4

前端将需要的字段以form形式提交给后端,后端再进行处理,返回给前端一个html,前端直接打开该html页面。

此方案不可行,后端当时说改动代码的代价较大,故放弃使用。

3. 实现

实现部分,贴一些代码段吧。

『去付款』按钮的代码如下:

 <Button
    type="primary"
    loading={xxxxx}
    disabled={xxxx}
    onClick={this.goToPay}
>
    去付款
</Button>

构建一个隐藏的form表单,代码如下:

<div ref={formDiv => this._formDiv = formDiv}>
    <form
        action={vo.url}
        method="POST"
        style={{ display: 'inline-block' }}
    >
        <div style={{ visibility: 'hidden' }}>
            sth1: <input type="text" name="sth1" readOnly value={vo.str1} />
            sth2: <input type="text" name="sth2" readOnly value={vo.str2} />
            sth3: <input type="text" name="sth3" readOnly value={vo.str3} />
            sth4: <input type="text" name="sth4" readOnly value={vo.str4} />
            sth5: <input type="text" name="sth5" readOnly value={vo.str5} />
        </div>
    </form>
</div>

goToPay方法如下:

 goToPay() {
        const payWindow = window.open();
        if (!payWindow) {
            this.modal.warning({
                title: '提示',
                content:
                    <div>
                        <p>支付弹窗已被浏览器拦截,请设置为『始终允许弹窗』!</p>
                        <p>例如:谷歌浏览器,请点击右上角,允许弹窗</p>
                        <div>
                            <img
                                src={tipImg}
                                alt="操作提示"
                            />
                        </div>
                    </div>,
            });
            return;
        }

       const fetchParams = {
            params1:'1111',
            params2:'22222',
            params3:'33333',
        };

        this.fetch({
            url: 'xxxx/yyyy/zzzz',
            method: 'POST',
            contentType: 'application/json',
            data: fetchParams,
        }).then((res) => {
            const {
                str1, str2, str3, str4, str5,
            } = res.vo || {};

           // 缓存后端返回的必需参数
            actions.updateSaveVo({
                str1,
                str2, 
                str3, 
                str4, 
                str5,
            });

            return true;
        }, (res) => {
            payWindow.close(); // 关闭由window.open()打开的空白窗口
            this.modal.error({
                title: '错误',
                content: res.message,
            });
        }).then(this.goToOtherPage.bind(this, payWindow)); 
    },

跳转到第三方平台页面的goToOtherPage方法如下所示:

goToOtherPage(payWindow) {
    const $formDiv = ReactDOM.findDOMNode(this._formDiv);
    const html = $formDiv.innerHTML;
    payWindow.document.write(html);

    const forms = payWindow.document.getElementsByTagName('form');
    forms && forms[0] && forms[0].submit();

    // 重置表单
    this.onReset();
},

4. 总结

浏览器为什么会拦截新开窗口?如何解决的?

在异步中打开新窗口应该都会被浏览器当做广告拦截的,异步可以调用恶意函数使得浏览器每秒弹一个窗口,使得浏览器无法关闭,因此浏览器厂商为了防止恶意广告之类的,就设置了异步打开窗口就会被当做广告拦截。

避开浏览器拦截的解决方法:

在同步执行时,先使用window.open()打开一个空白窗口,(一般浏览器都是可以正常打开的,如果遇到某些浏览器窗口被拦截了,就弹框提示其允许弹窗设置),然后发送异步请求获取必需参数,等请求结果返回后,构建一个form表单,写入到空白窗口页中,再提交表单。