这是我的第一篇掘金博客,开启掘金写作之路。
1. 背景
这是一个和支付相关的需求。在填写完基本信息之后,提交信息给后端,后端会返回一些跳转到第三方支付平台所必需的参数给前端,前端拿着这些参数跳转到第三方支付平台进行后续的操作。
需求的基本意思就是上面简短的一句话,本篇文章举一个简单的例子来说明其实现。 其UI简化图如下所示:
点击『去付款』按钮,要跳转到第三方平台的订单确认页面,如下图所示:
然后在第三方平台进行一系列的支付操作。
2. 方案
当时看到这个需求,想到了大概4种方案吧。
2.1 方案1
在前端页面的表单中,点击按钮『去付款』,发请求给后端,后台返回支付的url
,前端在请求的回调中使用window.open(url)
打开支付页面来实现第三方页面的跳转。
但是,该方案不可行,不可行的原因:
-
由于第三方平台的接口是
POST
方式的,后端无法将参数拼凑成url
返回给前端直接使用; -
在请求的回调中使用
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表单,写入到空白窗口页中,再提交表单。