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游戏前端支付参考处理方案的主要内容,如果未能解决你的问题,请参考以下文章

H5游戏前端支付参考处理方案

iOS开发-处理H5拉起微信支付返回到Safari情况

android 11 上配置微信授权、判断是不是安装微信或支付宝 、h5 拉起支付问题

iOS H5拉起微信支付

支付宝h5页面字体变大

英雄联盟手游拉起苹果支付失败怎么办