H5游戏前端支付参考处理方案
Posted 明立
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了H5游戏前端支付参考处理方案相关的知识,希望对你有一定的参考价值。
支付文档
该文档用作前端处理H5游戏支付参考文档,游戏引擎为egret
H5游戏支付不同于web支付,H5只有单界面,不能通过路由寻址的方式跳转到对应的游戏界面
因此,在支付中需要注意要在不打断游戏进程的情况下完成支付拉起,这个时候需要对支付流程
做特殊处理,不展示各平台的网关页面,而是从中抽取能够拉起支付的deeplink,
通过iframe来进行重定向,从而实现拉起,注意的是不同的平台,对不同的设备,在处理上有差异
需要单独做差异化的处理
支付流程(大概)
客户端 服务端 商户平台 支付平台
发起订单----------------------------->-------------->统一下单
订单记录<-----------订单信息<--------
获取订单数据<-------------------------
订单数据差异化处理
拉起支付-----------------------------------------------
支付结果通知<-----处理支付结果<-----支付结果重定向---------
支付结果处理
手机运行平台分类(暂不包括native端)
PC
WEB_android
WEB_ios
WEB_ANDROID_WECHAT
WEB_IOS_WECHAT
微信支付
差异化处理(关于部分特殊浏览器的支付处理方案,在特殊问题中描述):
PC
展示支付二维码
WEB_ANDROID
抽取deepLink,iframe展示,具体见**deeplink抽取**
WEB_IOS
抽取deepLink,window.location.href替换,具体见**deeplink抽取**
WEB_ANDROID_WECHAT
WEB_IOS_WECHAT
接通jsspi,通过微信进行支付
支付宝支付
差异化处理(关于部分特殊浏览器的支付处理方案,在特殊问题中描述)
PC
展示支付二维码
WEB_ANDROID
抽取deepLink,iframe展示,具体见**deeplink抽取**
WEB_IOS
抽取deepLink,window.location.href替换,具体见**deeplink抽取**
WEB_ANDROID_WECHAT
WEB_IOS_WECHAT
待定
deepLink抽取
微信
微信需要抽取deeplink的部分,平台返回的数据为微信的网关页文本,核心内容为
<script type="text/javascript">
var is_postmsg="";
if( 0!==0 && is_postmsg=="1" )
parent.postMessage(JSON.stringify(
action : "send_deeplink_fail",
data :
deeplink : ""
,
error :
error_code : "0",
error_msg : "ok"
), "");
if( 0===0)
window.onload=function()
// var fp=new Fingerprint2();
// fp.get(function(result)
// var fingerprint="";
/* if(fingerprint!=result && fingerprint)
document.getElementById("errpage").innerhtml='<div class="icon_area"><i class="icon_msg warn">!</i></div> \\
<div class="text_area"> \\
<h2 id="111" class="title"> '+result+'缃戠粶鐜鏈兘閫氳繃瀹夊叏楠岃瘉锛岃绋嶅悗鍐嶈瘯</h2> \\
</div>';
return;
*/
var is_postmsg="";
if(is_postmsg=="1")
parent.postMessage(JSON.stringify(
action : "send_deeplink",
data :
deeplink : "weixin://wap/pay?prepayid%3Dwx18124723988137f5141f245333fe250000&package=1371992231&noncestr=1600404444&sign=a733c49509892bfc49cfd6299d9a87c3"
), "");
else
var url="weixin://wap/pay?prepayid%3Dwx18124723988137f5141f245333fe250000&package=1371992231&noncestr=1600404444&sign=a733c49509892bfc49cfd6299d9a87c3";
var redirect_url="";
;
if(redirect_url)
setTimeout(
function()
;
,
5000
);
else
setTimeout(
function()
window.history.back();
,
5000);
// );
</script>
</body>
</html>
处理方案为:
```
private handleH5Response(response: PayParam)
let data = response.Parameters;
let lines = data.split('\\n');
for (let line of lines)
if (line != "" && line.indexOf('deeplink') != -1 && line.indexOf('weixin://wap/pay') != -1)
let index = line.indexOf(":");
line = line.slice(index + 1, line.length)
line = line.split(`"`)[1]
line = line.split(`"`)[0]
return line;
return undefined;
```
支付宝
支付宝支付会返回一个表单,需要把表单提交转化成post请求来获取到网关页,具体为:
```
let url = `https://openapi.alipay.com/gateway.do?charset=UTF-8`;
let xhr = new XMLHttpRequest()
xhr.open('post', url);
xhr.onreadystatechange = (e) =>
if (xhr.readyState == 4)
if (xhr.status == 200)
//用iframe打开
this.openIframe(xhr.response);
let fd = new FormData();
//这一步是解析表单里面的数据,把参数解析出来
let datas = data.split("<input");
for (let i = 1; i < datas.length - 1; i++)
let params = datas[i].split("/>")[0];
params = params.split(" type='hidden'")[1]
let values = params.split(" value=")
let names = values[0]
let value = values[1]
let name = names.split(" name=")[1]
name = name.trim().slice(1, name.length - 1)
value = value.trim().slice(1, value.length - 1)
fd.append(name, value);
xhr.send(fd);
```
其中,openIframe的部分实际包括两个部分
抽取deepLink以及差异化展示
本节重点是抽取deeplink
通过post请求返回的页面文本核心内容为
```
(function()
var _AP =
var ua = navigator.userAgent.toLowerCase(),
locked = false,
domLoaded = document.readyState==='complete',
delayToRun;
function customClickEvent()
var clickEvt;
if (window.CustomEvent)
clickEvt = new window.CustomEvent('click',
canBubble: true,
cancelable: true
);
else
clickEvt = document.createEvent('Event');
clickEvt.initEvent('click', true, true);
return clickEvt;
function getAndroidVersion()
var match = ua.match(/android\\s([0-9\\.]*)/);
return match ? match[1] : false;
var noIntentTest = /aliapp|360 aphone|weibo|windvane|ucbrowser/.test(ua);
var hasIntentTest = /chrome|samsung/.test(ua);
var isAndroid = /android|adr/.test(ua) && !(/windows phone/.test(ua));
var canIntent = !noIntentTest && hasIntentTest && isAndroid;
var clientBtn
// 确定浏览器类型
var isChrome = false;
var isWebview = false;
if (ua.match(/(?:chrome|crios)\\/([\\d\\.]+)/))
isChrome = true;
if (ua.match(/version\\/[\\d+\\.]+\\s*chrome/))
isWebview = true;
var isOriginalChrome = isAndroid && isChrome && !isWebview;
if (ua.indexOf('m353')>-1 && !noIntentTest)
canIntent = false;
// 安卓走iframe方式唤起
if (ua.indexOf('android')>-1 && !noIntentTest)
canIntent = false;
/**
* open client
*/
_AP.open = function (params)
if (!domLoaded && (ua.indexOf('360 aphone')>-1 || canIntent))
var arg = arguments;
delayToRun = function ()
_AP.open.apply(null, arg);
delayToRun = null;
;
return;
if (locked)
return;
locked = true;
var o;
if (typeof params === 'object')
o =
'ios': encodeURIComponent(JSON.stringify(params)),
'android': encodeURIComponent(params.dataString)
;
else
console.error('params error, pls use JSON format!')
// params fault tolerance
if (typeof o.ios !== 'string')
o.ios = '';
else if(typeof o.android !== 'string')
o.android = '';
// nonsupport Android intent
if (!canIntent)
if(isAndroid)
var alipaysUrl = 'alipays://platformapi/startApp?appId=20000125&orderSuffix=' + o.android +'#Intent;scheme=alipays;package=com.eg.android.AlipayGphone;end';
//fix for iOS QQ browser
else if (ua.indexOf('mqqbrowser') > -1)
var alipaysUrl = 'alipay://alipayclient/?' + o.android;
else
var alipaysUrl = 'alipay://alipayclient/?' + o.ios;
//FIXME: 直接判断ios,不判断os版本号
if ( ua.indexOf('qq/') > -1 || ( ua.indexOf('safari') > -1 && ua.indexOf('os 9_') > -1 ) || ( ua.indexOf('safari') > -1 && ua.indexOf('os 10_') > -1 ) || ( ua.indexOf('safari') > -1 && ua.indexOf('os 11_') > -1 ) || ( ua.indexOf('safari') > -1 && ua.indexOf('os 12_') > -1 ) || ( ua.indexOf('safari') > -1 && ua.indexOf('os 13_') > -1 ) || ( ua.indexOf('safari') > -1 && ua.indexOf('os 14_') > -1 ) )
var openSchemeLink = document.getElementById('openSchemeLink');
if (!openSchemeLink)
openSchemeLink = document.createElement('a');
openSchemeLink.id = 'openSchemeLink';
openSchemeLink.style.display = 'none';
document.body.appendChild(openSchemeLink);
//openSchemeLink.href = alipaysUrl;
// oppo浏览器兼容写法
openSchemeLink.onclick = function()
window.location.href = alipaysUrl;
;
// trigger click
openSchemeLink.dispatchEvent(customClickEvent());
else
var ifr = document.createElement('iframe');
ifr.src = alipaysUrl;
ifr.style.display = 'none';
document.body.appendChild(ifr);
$('.J-startapp').attr('href', alipaysUrl);
//support Android intent
else
var packageKey = 'AlipayGphone';
var intentUrl = 'alipays://platformapi/startApp?appId=20000125&orderSuffix='+o.android+'#Intent;scheme=alipays;package=com.eg.android.'+ packageKey +';end';
var openIntentLink = document.getElementById('openIntentLink');
if (!openIntentLink)
openIntentLink = document.createElement('a');
openIntentLink.id = 'openIntentLink';
openIntentLink.style.display = 'none';
document.body.appendChild(openIntentLink);
//openIntentLink.href = intentUrl;
// oppo浏览器兼容写法
openIntentLink.onclick = function()
window.location.href = intentUrl;
;
// trigger click
openIntentLink.dispatchEvent(customClickEvent());
_AP.pay = function(param)
_AP.open(param);
window._AP = _AP;
)();
try
//唤起客户端快捷参数
var data = "requestType":"SafePay","fromAppUrlScheme":"alipays","dataString":"h5_route_token=\\"RZ42lyaFqrVMr2SudPI7WhDX5RA1iSmobilecashierRZ42\\"&is_h5_route=\\"true\\"&need_invoke_app=\\"true\\"";
window.setTimeout(function()
_AP.pay(data);
, 50);
catch(e)
window.console && window.console.log('e.name:' + e.name + ';e.message:' + e.message)
```
通过代码审计,抽取核心的拼接逻辑
```
private handleZfbResponse(data: string)
//获取inData对象,存储的是核心的支付信息
let lindex = data.indexOf("inData");
let rindex = data.lastIndexOf("inData");
let response = data.slice(lindex, rindex)
let obj = response.split("")[1].split("")[0];
let objs = obj.split(",");
let inData =
requestType: null,
fromAppUrlScheme: null,
dataString: null,
for (let param of objs)
//这里做了一层封装,实际上是对字符串"key":"value"类型进行kv抽取
let kv = HttpResponseUtil.getParamKV(param, ":");
let v = kv.value.trim();
let k = kv.key.trim();
k = k.slice(1, k.length - 1)
v = v.slice(1, v.length - 1)
inData[k] = v
//注意,datsString中有个\\的转义符,实际是不需要的,需要清除
let str = inData.dataString;
let datas = str.split("\\\\");
let newStr = ""
for (let dt of datas)
newStr += dt;
inData.dataString = newStr
//获取到inData,拼接后缀
let o =
'ios': encodeURIComponent(JSON.stringify(inData)),
'android': encodeURIComponent(inData.dataString)
;
if (typeof o.ios !== 'string')
o.ios = '';
else if (typeof o.android !== 'string')
o.android = '';
//根据不同的运行平台解析出deeplink
if (DeviceUtil.isAndroid())
var alipaysUrl = 'alipays://platformapi/startApp?appId=20000125&orderSuffix=' + o.android + '#Intent;scheme=alipays;package=com.eg.android.AlipayGphone;end';
//ios的qq浏览器,这个兼容有问题,暂时忽略
else if (DeviceUtil.isBrowserQQ())
var alipaysUrl = 'alipay://alipayclient/?' + o.android;
//ios
else
var alipaysUrl = 'alipay://alipayclient/?' + o.ios;
return alipaysUrl
```
特殊问题
QQ浏览器问题
1.android中,QQ浏览器从点击支付到支付拉起,过程不能超过1000ms,否则不能拉起
解决方案:针对QQ浏览器版本,获取到deepLink之后,再做一个支付信息确定提示,借此缩短拉起支付的时间
2.ipad中,QQ浏览器无法拉起支付,原因待定
iframe参考
```
const iframe = document.createElement('iframe')
iframe.style.position = "absolute";
iframe.style.display = 'none';
iframe.style.zIndex = "99999";
iframe.style.width = "100%"
iframe.style.height = "100%"
iframe.setAttribute('name', `zfbpay`);
iframe.setAttribute('sandbox', 'allow-top-navigation allow-scripts')
iframe.setAttribute('src', data)//data为deeplink
document.body.appendChild(iframe)
```
说明
1.在抽取deeplink中,仅很朴素的针对文本做了一定的处理操作,并没有用到其他的算法之类的
如果有好的处理思路,欢迎交流
2.仅作为参考,不代表各个项目实际运行情况,特殊操作应按实际项目需求进行
3.出文档的初衷是网上关于H5游戏支付处理这块的文档较少,实际操作中踩了不少的坑,故记录下处理过程
如有不妥,欢迎交流
以上是关于H5游戏前端支付参考处理方案的主要内容,如果未能解决你的问题,请参考以下文章