笔者最近需求包含了微信支付的流程,遇到了不少坑,故做一个记录,为以后避坑可用。
一、知己知彼,微信支付种类
当产品说,别人组也支持了微信支付,咱们组也支持一下吧。需求单上也列了微信H5支付。
这时候要留意啦,需求单上的支付场景是不是没有考虑周全。
微信支付常见种类:
- H5支付,主要用于触屏版的手机浏览器(注意不是微信的浏览器)请求微信支付的场景。可以从外部浏览器唤起微信支付。
- JSAPI支付,主要在微信内打开网页时,可以调用微信支付完成下单购买的流程。
- APP支付,移动端应用APP中集成开放SDK调起微信支付模块来完成支付。(移动端内也可以嵌入H5支付,但体验没有原生支付好,官网不推荐这种做法)
- Native支付,是指商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。适用于PC网站、实体店单品或订单、媒体广告支付等场景。
- 付款码支付,用户出示微信钱包中的条码、二维码,商家通过扫描用户条码即可完成收款。
- 小程序支付,商户已有微信小程序,用户通过好友分享或扫描二维码在微信内打开小程序时,可以调用微信支付完成下单购买的流程。
此次笔者需求单上是H5支付,安卓客户端也嵌入H5支付,IOS客户端是苹果原生IAP支付。
开发前没有考虑到的
- 微信浏览器的JSAPI支付(能怎么办,加班咯),
- PC和桌面端打开时候的Native支付(toast提示在移动端支付,不做)。
二、开发前的准备
可参考微信支付开发文档,以下列重点。
-
开发前要有一个商户号,可在 微信公众平台 注册,并认证通过。还要有一个微信公众号,为了做微信内JSAPI支付用。
-
认证通过以后,可在微信公众平台申请开通微信支付。
以上两步公司一般已经申请完,直接找相应的人对接就行
-
开通H5支付权限
前往微信支付商户平台—>产品中心—>开发配置—>H5支付,设置后一般10分钟内生效。 开通完之后,在“支付配置”添加H5支付域名,注意:域名必须通过ICP备案,域名填写格式不包含http://或https:// 。
-
开通JSAPI支付权限
JSAPI支付依托于微信公众号,且要企业认证,才有网页授权。微信公众号要单独在微信公众平台登录设置。
前往微信支付商户平台—>产品中心—>开发配置—>JSAPI支付,设置后一般5分钟内生效。
4.1 设置支付目录
-
商户最后请求拉起微信支付收银台的页面地址我们称之为“支付目录”,例如:www.weixin.com/pay.php。
-
商户实际的支付目录必须和在微信支付商户平台设置的一致,否则会报错“当前页面的URL未注册:”
-
如果支付授权目录设置为顶级域名(例如:www.weixin.com/ ),那么只校验顶级域名,不校验后缀;
-
如果支付授权目录设置为多级目录,就会进行全匹配,例如设置支付授权目录为www.weixin.com/abc/123/,则实…
-
配置域名时,一定要注意协议头,http 和 https完全不一样,如果开发过程中,一直遇到“当前页面的URL未注册:”这个问题的时候,一定要来检查这个授权域名。
4.2 设置网页授权域名
如果看不到网页授权,看看公众号是否开通企业认证。服务号必须通过微信认证。
-
开发JSAPI支付时,在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。
-
授权域名设置说明:登录微信公众平台-->公众号设置-->功能设置
图片中的“下载文件”,一定要放在线上服务器web根目录下。不然域名无法添加。
以上的基础配置一定要配置好,不然很容易出现一些问题。尤其网页授权这一步,经常会看不到这个配置,首先公众号要企业认证的才可以。然后域名一定要完全写对,最好自己都要过目以下,让别人添加,容易出现写错的坑。授权的域名数量一般有限,此次开发过程碰到换域名的需求,刚好数量达到上限,只能把原来的删除掉,添加新的域名。然后就碰到各种奇奇怪怪的事情。以上写的请细品。
-
三、基础工具准备
- 记录下公众号的ppid(wx开头的),JSAPI支付获取code用;还有app_secret,一般丢给后端就行。
- 公众号的web开发者工具,一定要添加开发者的微信号,这样才方便调试。
- 微信开发者工具很多坑,开发的时候各种问题,还是要借助测试机(真机)调试。
开发调试遇到的问题
通报:微信更新到7.0以后抓包公众号会有证书问题,抓包小程序直接不能打开
各位不用到处找了,也不用怀疑人生了,你没有问题、win10也没有问题、fiddler和Charles也没有问题,是因为微信更新了,不再从手机本地获取证书。
安卓系统 7.0 以下版本,不管微信任意版本,都会信任系统提供的证书
安卓系统 7.0 以上版本,微信 7.0 以下版本,微信会信任系统提供的证书
安卓系统 7.0 以上版本,微信 7.0 以上版本,微信只信任它自己配置的证书列表
以上规则是拷贝别人的,但确实亲测如此,搞得我都怀疑人生了,主要是前一天在win7上调得好好的,第二天换了个win10的系统,刚好微信又自动更新,突然就不能抓了,各种怀疑win10(这哥们儿躺枪了),各种怀疑人生都找不到原因,就问你怕不怕,就问你坑不坑。。。
意思是你要抓包调试的话,现阶段要么安卓7以下的手机,要么微信7.0以下的版本。。。
开发环境下,安卓手机微信内确实无法打开网站链接(测试小姐姐说她的测试机可以,反正我的就不行),微信开发者工具能够打开,但是链接无法跳转。按照以上的教程,将微信降级到7.0以下版本,测试无效;安卓7.0以下版本没去试。
开发环境下微信内无法打开网站链接,那JSAPI支付就没得调试,问了后端大佬,他也不知道,说只能在线上环境调试了(leader对线上调试表示很大的担忧)。怎么办?怎么办?忧愁的我掉了很多根头发。安卓测试机无法链接,下意识的以为IOS也是不行。
解决
一次意外的尝试在ios打开,发现居然可以在测试环境下打开链接。不过它有个条件,就是手机只能连dns,不能连代理(可用于抓包工具)。
所以开发调试时,只能借用IOS测试机。
四、H5支付
官网提醒H5支付不建议在APP端使用,但本次需求中,安卓APP端内却使用了,开发上遇到不少坑,体验也大打折扣。
需求
本次的需求是,点击付费商品的购买按钮,跳转到支付订单页,在支付订单页发起微信支付。若成功,则返回到支付订单页,toast提示成功购买,1s后跳转到商品详情页,并改变页面样式;若失败,则返回停留在支付订单页,toast提示支付失败;若取消支付,则返回停留在支付订单页,toast提示取消支付。(JSAPI支付也是一样,但需求单没写。此次的购买还包含了积分购买,考虑的情况会有点多,还有本次来不及做的账内支付。)
开发文档重点
参考开发文档,以下划重点:
先来了解下H5支付的回调页面。正常流程用户支付完成后会返回至发起支付的页面,如需返回至指定页面,则可以在MWEB_URL后拼接上redirect_url参数,来指定回调页面。
注意:
- 需对redirect_url进行urlencode处理
- 由于设置redirect_url后,回跳指定页面的操作可能发生在:
- 微信支付中间页调起微信收银台后超过5秒 (自动返回);
- 用户点击“取消支付“或支付完成后点“完成”按钮。
因此无法保证页面回跳时,支付流程已结束,所以商户设置的redirect_url地址不能自动执行查单操作,应让用户去点击按钮触发查单操作。回跳页面展示效果可参考下图。
以上的回跳操作,区分5s内和5s后的操作,表现效果完全不同。如果没有支付是否成功的提示弹框,则很难稳定的执行查单操作。
需求PK(一把辛酸泪)
官方文档推荐,redirect回来,展示回跳页面,让用户去点击按钮触发查单操作。
而笔者的需求单是返回来之后,直接toast提示得到结果。这样子的话,是无法准确的知道页面回跳回来的支付流程结果。
开发过程中,被5s内和5s后不同的表现折磨的想哭。开发了很长一段时间,还是没法完成想要的结果,导致开发流程上各种问题。
笔者元旦加了两天班。动之以情,晓之以理的说服产品,支付回来一定要加上支付确认提示页,否则是没法做支付。产品看在我连续加班的份上,终于改了这个需求。因此也终于在元旦加班的最后一天(到晚上10点多),完成了这个支付流程开发。
再次感谢产品小姐姐。
开发
本次需求的难点是,如何监听到微信成功支付或者取消支付后返回支付订单页,以此来做一些提示效果。
(一)、visibilitychange
第一个想到的是,visibilitychange
这个原生事件,参考 MDN,当其选项卡的内容变得可见或被隐藏时,会在文档上触发 visibilitychange (能见度更改)事件。有个库vue-visibility-change
可以使用。
但它有一些问题:
visibilitychange
原生事件兼容性不太好,Safari无法使用,即便是用上了vue-visibility-change
,结果也好不到哪去。还有在安卓客户端,5s内结束微信操作,visibilitychange
的触发效果能够看到,5s后则没法看到,需要借助JsBridge,让安卓客户端告诉web页面显现。即便有了JsBridge的辅助,第一次进入支付订单页,也能看到JsBridge触发弹起的toast。主要看webview页面中web页的加载情况,加载的慢,那么toast则看不到(加载出来前已经toast结束),加载的快,则能看到不想要看到的toast提示。- 除了从微信支付页面返回来能被监听到,页面从后台切换回来也仍然能被监听到,那这样子考虑的情况就相对比较多。
- 因为没有支付确认弹框,5s内和5s后成功支付和取消支付后返回页面的表现不一样,再考虑到安卓和IOS的环境,以及多端不同的浏览器的兼容表现,各种奇奇怪怪的现象数不胜数。
最终放弃这个方案。
(二)、history.length
代码的支付,集合在支付订单页,而支付订单页是单独另开的一个页面。进入到微信页面,再返回支付页面,history会不断累加。因此可以在history.length(以下简称hLlength)上做文章。
从商品进入到支付订单页,因为支付订单页是新开页面,所以hLlength是1,进入到微信页面,hLlength再加1,最后返回到订单页。
所以理论上只要判断hLlength变为2之后,就可以弹出确认支付弹框。
理想很丰满,现实很骨感。奇怪的事情还是发生了。以下为移动端浏览器的测试表现。
-
进入新页面时,Safari的hLlength为2,而Chrome以及Chrome内核的hLlength表现正常为1。
-
跳到微信支付页,Safari和Chromeh的Llength能加1,OPPO手机自带浏览器的hLlength却不能加1。
-
安卓客户端内webview的hLlength表现正常。
因为不同的浏览器hLlength的表现不一样,无法预料没有测试到的浏览器会有什么样的表现,因此这个方案也被PASS。
(三)、redirect参数标志
H5支付的回调页面。正常流程用户支付完成后会返回至发起支付的页面,如需返回至指定页面,则可以在MWEB_URL后拼接上redirect_url参数,来指定回调页面。
如果没有支付确认弹框,则从支付回来到页面表现不一样,所以需要有一个支付确认弹框,来发送一次查单操作,以此来做支付是否完成到判断。触发弹框的操作放在了created
生命周期中,如果不指定redirect_url
,返回到最开始发起支付到页面,因为是老页面,所以没法继续触发created
钩子。所以可以通过指定redirect_url
,并加上is_redirect=1
这个参数来表示从微信页面回来,跳到新页面,以此来触发created
钩子,通过判断is_redirect=1
来打开支付确认弹框。
computed:{
// 从微信支付成功,取消返回
isRedirectFromWechat() {
const is_redirect = location.search.match(/is_redirect=(\d+)/);
return !!(is_redirect && is_redirect[1] === "1");
}
},
// 从微信支付页面返回,因为通过redirect_url返回,所以会是一个新页面,能够触发created钩子
created() {
if(this.isRedirectFromWechat) {
this.$modal.show();
// TOOD
}
}
这个方案能够稳定打开支付确认弹框。
但有一些副作用:
- 就支付回来的url会带上
is_redirect=1
,如果刷新这个页面,又会继续触发确认弹框。 - history的栈是支付订单页——微信中间页——支付订单页,如果取消弹框后不做任何处理,点击返回按钮,会回到微信支付页,在安卓客户端返回支付订单页,右上角还多了一个关闭按钮(与最开始的页面不一致)。
因此可以在支付确认弹框做一些文章。
- 点击“未支付”取消确认弹框,仍停留在订单支付页面。那直接
history.go(-2);
,完美回到一开始的支付订单页,毫无副作用。 - 点击“已支付”,发送一次查单请求,如果成功,则toast提示“成功支付”,1s后返回商品详情页,如果失败,则toast提示“支付失败”,仍停留在支付订单页。“支付失败”的操作与步骤1一样,成功则延时一定时间后(给toast时间),然后关闭当前页面。
// 延迟跳转到商品详情页
async delayToPage(options) {
await sleep(TIMEOUT_DELAY);
closeCurrentPage(options);
}
而关闭当前页面的代码可以封装在一起。
// 关闭浏览器页面
const closeWindow = () => {
if (window.close) {
window.close();
return;
}
window.opener = null;
const t = window.open('', '_self', '');
t.close();
}
// 关闭当前页面
const closeCurrentPage = ({ isSuccess } = {}) => {
// 客户端
if (isPlatform() ) {
// 现金支付成功,直接关闭支付订单页,返回商品详情页
if (isSuccess) {
window.location.href = 'schema://close_webview';
}
/**
* 安卓微信取消支付后的效果,微信支付成功返回后则不处理
* 安卓的支付订单页是一个webview,最下面是订单页,中间是微信支付页,上面是跳转回来的订单页
* 现金操作跳转到微信支付 `window.location.href = mweb_url;` history 加1;
* 跳转到微信页面,history再加1;
* 取消微信支付,重定向到支付页面,history再加1,则总的为3;
* 通过history.go(-2)能跳转到最初的支付页面。
*/
else {
history.go(-2);
}
return;
}
// h5 浏览器 支付成功,直接关闭支付订单页,返回商品详情页
if (isSuccess) {
closeWindow();
return;
}
// 其他乱七八糟的浏览器,直接回退两步
history.go(-2);
};
至此,微信h5支付关键代码已经完成。
(四)、localStorage
codereview的时候,有同事推荐也可以通过localStorage做状态变化的记录,没去测试过,有机会再去尝试。
五、JSAPI支付
微信内的JSAPI支付,依托于微信公众号,配置回看第二部分。
开发步骤为:
-
引导用户进入授权页面同意授权,获取code
在created钩子函数中,触发getCode。
const getCode = (url) => { // 获取code const redirect_uri = url || window.location.href; let state = parseInt(Math.random() * 1000); // APP_ID 公众号中以wx开头的那一串代号 const path = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${APP_ID}&redirect_uri=${encodeURIComponent(redirect_uri)}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`; window.location.replace(path); };
-
通过code换取网页授权access_token
将上一步获取到的code值(在url的参数中获取),传给后端,让后端做处理,前端会有跨域。
获取code后,请求以下链接获取access_token: api.weixin.qq.com/sns/oauth2/…
-
后端经过一系列处理,获取到参与签名的参数为:appId、timeStamp、nonceStr、package、signType、paySign。
接着前端调用以下方法,即可唤起微信浏览器内的支付。
function onBridgeReady(){ // WeixinJSBridge 为微信的内置对象,可在微信浏览器中直接使用 WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId":"wx2421b1c4370ec43b", //公众号名称,由商户传入 "timeStamp":"1395712654", //时间戳,自1970年以来的秒数 "nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串 "package":"prepay_id=u802345jgfjsdfgsdg888", "signType":"MD5", //微信签名方式: "paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 }, function(res){ // get_brand_wcpay_request:cancel 支付过程中用户取消 // get_brand_wcpay_request:fail 支付失败 if(res.err_msg == "get_brand_wcpay_request:ok" ){ // 使用以上方式判断前端返回,微信团队郑重提示: //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。 } }); } if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', onBridgeReady); document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); } }else{ onBridgeReady(); }
JSAPI支付因其拥有回调函数,所以在支付的效果上,很可控。微信成功支付后,直接history.go(-1);
,即可直接回到商品详情页。取消或失败则继续停留在当前页。
六、开发中遇到的问题
问题的类型和原因可以查看官方文档,遇到的问题无非就是配置出了问题,请回看第二点,然后就是redirect_url配置也出错导致一些问题。其它遇到的问题,直接找后端开发的小伙伴吧。
只要第二步配置好了,开发就能很顺畅。不然遇到的,大部分都是配置不对的问题。