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游戏支付处理这块的文档较少,实际操作中踩了不少的坑,故记录下处理过程
如有不妥,欢迎交流
已标记关键词 清除标记
课程简介: 历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring Boot、Spring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页