299

公司项目需要,需要接入第三方支付,前前后后也搞了很久。虽然微信公众平台文档已经写得很清楚了,但是还是记录一下整个接入的流程。

openid

openid是微信用户在公众号appid下的唯一用户标识(appid不同,则获取到的openid就不同,所以不同的公众号下有不同的openid,),可用于永久标记一个用户,同时也是微信公众号支付的必传参数。

code

微信支付,需要用户授权,获取code,通过code获取网页授权,其实是要取得openid,需要注意的是,code只能使用一次,用户每次授权带上的code都是不一样的,并且五分钟内不使用的话会自动过期。

场景描述

我们在进行支付时,点击支付按钮,首先会跳转了页面然后再跳转回来,然后弹出框让我们输入密码,支付成功或者支付失败。或者, 没有跳转页面,直接就弹出密码输入框

逻辑描述

上面也说了,我们获取code就是为了获取到openid然后利用openid这个必传的参数才能成功唤起支付。

其实呢,让用户每次跳转URL去获取code,然后传code参数给后台也是可以的。因为后台的逻辑也是获取了code之后再利用code去获取openid,然后进行进一步的操作。现在只是保存openid这一操作,从后台放到了前端。 只要我们前端判断openid存在,我们就不必再跳转URL进行授权重新获取code,可以直接进行支付了。

所以可以得出下图:
图片描述

具体代码

逻辑图写的虽然并不具体,但是很简要。

接下来我们就走一遍流程

1.获取code

点击支付,第一次支付肯定是不存在openid的,那么就要跳转URL获取code
官方也已经写得很明白 微信支付网页授权

我们需要拼接此链接:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

其中 APPID是商户的APPID,REDIRECT_URI是页面跳转后跳回来的URL,state可以放一些我们自定义的内容,会带在REDIRECT_URI上。

例如:我要重定向的地址是https://baidu.com/testIndex.html/?#index
那么,我要拼接的地址应该是https://open.weixin.qq.com/connect/oauth2/authorize?appi=XXXXX&redirect_uri=https://baidu.com/testIndex.html/?#index&response_type=code&scope=SCOPE&state={name:guo}#wechat_redirect

URL要进行编码encodeURIComponent,这里演示我就省略了)

那么,重点来了,地址根据我们拼接的地址跳转过去,实际跳转回来的地址会变成:
https://baidu.com/testIndex.html?code=XXXX&state={name:guo}/?#index(同样需要解码decodeURIComponent)

code=XXXX&state={name:guo}这一坨东西跑到中间去了,那我们尝试把它取出来放到后面去
变成这样子https://baidu.com/testIndex.html/?#index?code=XXXX&state={name:guo}
这样一来,我们就能用$route.query取到参数了

//跳转回来的URL修正,在地址跳转回来的时候调用进行修正
var payUrlReplace = function() {
    //修正前: http://XXXX/?code=XXX&state=XXX/?#/XXX
    var codeReg = /(?:[?|&]code=)([\w\d]+)&{0,1}/;
    var statePayInfoReg= /(?:[?|&]*state=)({{1}.+}{1})&{0,1}/;
    var url = decodeURIComponent(window.location.href)

    //这里是为了确保#前有?
    if (url.indexOf('?#') < 0) {
        url = url.replace('#','?#');
    }
    var urlReg = /\?#(.+)\?+/


    //判断URL挂载参数
    if(    codeReg.test(url) &&
        statePayInfoReg.test(url) && 
        url.match(statePayInfoReg).length > 0 &&
        url.match(codeReg).length > 0
    ){
        var code = url.match(codeReg)[1]
        var state = url.match(statePayInfoReg)[1]
        url = url.replace(codeReg, '')
        url = url.replace(statePayInfoReg, '')


        if (urlReg.test(url)) {
            url = `${url}&payCode=${code}`
        }else {
            url = `${url}?payCode=${code}`
        }

        url = `${url}&payInfo=${state}`

        //修正后变成 http://XXX/?#/XXXX?payCode=XXX&payInfo=XXX,这里我换了字段名
        window.location.href = url
    }
}

这样,payCodepayInfo都能取到了。

2.获取openid

有了code,就可以获取openid了,然后存在本地(这里我们的后台专门写了传codeopenid的接口,这是异步操作)
图片描述
上图就说明了我们传code给后台后,后台再去获取openid, 后台在传给我们前端。

而我们要使用openid请求后台接口获取支付参数就要保证openid必须存在,获取openid是异步操作,我们必须保证先后顺序。值得注意的是,openid一旦获取保存在本地,之后支付就不再需要获取code了。

//检查是否使用了微信支付
var payIsUse = function(query,callback){
    //检查这里是否携带需要支付的参数
    //?payCode=XXX&payInfo=XXX
    
    if(query && query.payCode){
        var code = query.payCode
        if(query.payInfo){
            var payInfo = JSON.parse(query.payInfo)
        }
        //判断是否本地存在openID 如果存在则取消回调,由pay方法手动唤起支付
        var openID = tokenServer.getOpenId();
        if(openID){
            return false;
        }
        //不存在openID用code去接口请求openID,在触发回调去支付
        payModel.saveOpenid({
            code:code
        }).then(function(data){
                if( tokenServer.setOpenId(data.data) ){
                    if(callback){
                        //将支付参数传给回调函数 具体用的时候是传给pay
                        callback(payInfo)
                    }
                }
            }
        })
    }
}

3.获取支付参数

后台接口,传参获得的。

4.唤起微信支付

https://pay.weixin.qq.com/wik...

//微信JSDK唤起支付
var WeChatJSDKpay = function(config,onSuceess,onError){
    var pay = function(){
        WeixinJSBridge.invoke('getBrandWCPayRequest',{
                "appId":config.appId,
                "timeStamp": config.timeStamp,
                "nonceStr": config.nonceStr,
                "package": config.package,
                "signType": config.signType,
                "paySign": config.paySign
            },function(res){
                if(res.err_msg == "get_brand_wcpay_request:ok"){   
                    //支付成功
                    onSuceess()
                }else{
                      //支付失败
                      onError('支付失败')
                }
        });
    }
    if(typeof WeixinJSBridge == "object" && typeof WeixinJSBridge.invoke == "function"){
        pay()
    }else{
        if (document.addEventListener) {
            document.addEventListener("WeixinJSBridgeReady", pay, false);
        } else if (document.attachEvent) {
            document.attachEvent("WeixinJSBridgeReady", pay);
            document.attachEvent("onWeixinJSBridgeReady", pay);
           }
    }
}

支付封装的函数


/*
*onSuccess 支付成功的回调
*onError 支付失败的回调
*payInfo 支付参数
*/
var pay = function(onSuceess,onError,payInfo) {
    var codeReg = /(?:[?|&]code=)([\w\d]+)&{0,1}/;
    var statePayInfoReg= /(?:[?|&]*state=)({{1}.+}{1})&{0,1}/;
    var payCodeReg = /(?:[?|&]payCode=)([\w\d]+)&{0,1}/;
    var payInfoReg = /(?:[?|&]*payInfo=)({{1}.+}{1})&{0,1}/;

    var url = decodeURIComponent(window.location.href)

    var openId = tokenServer.getOpenId()
    //openid存在
    if (openId) {
        if (!payInfo){
               return false;
        }
        //整理获取微信支付参数的传参
        var defaultParams = {
            type: 1, //微信浏览器的标志
            openId: openId
        }
        
        var _payInfo = tool.extend(defaultParams, payInfo)
        (第三步骤-请求后台接口获取支付参数)
        payRequest(_payInfo).then(function(data) {
            modalLoadingServer.unload();
            if(data.appId && data.status === 200){
                //第四步骤-调起微信支付JSDK
                WeChatJSDKpay(data,function(){
                    //微信JSDK支付成功 返回验证参数-流水号进行验证
                    onSuceess(data.outTradeNo)
                },onError)
            }else{
                console.error('获取微信支付参数错误', _payInfo)
                onError('获取微信支付参数错误,请稍候再试')
            }
        })
        return false;
    }
    //如果已经存在
    if(codeReg.test(url) && statePayInfoReg.test(url)){
        window.location.reload();
        return false;
    }
    //重复请求的话
    if(payCodeReg.test(url) && payInfoReg.test(url)){
        //处理下url重新请求
        url = url.replace(payCodeReg,'');
        url = url.replace(payInfoReg,'');
        url = encodeURIComponent(url);
    }else{
        //跳转获取code 回调url
        url  = encodeURIComponent(window.location.href);
    }
    var statePay = {};
    for(var key in payInfo){
        //微信内支付不存在回调URL
        if(payInfo.hasOwnProperty(key)  && payInfo[key] && key !== 'returnUrl'){
            statePay[key] = payInfo[key]
        }
    }
    var statePay = encodeURIComponent(JSON.stringify(statePay));
    var getCodeUrl = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxd9242c6d1e249d25&redirect_uri='+url+'&response_type=code&scope=snsapi_base&state='+statePay+'&connect_redirect=1#wechat_redirect';
    //获取`code`的跳转链接
    window.location.href = getCodeUrl
}


以上是主要的代码逻辑,也不是特别完整能够直接使用,但是能够得到启发。主要还是要看具体的业务和接口怎么给。有问题欢迎一起交流~


高压郭
961 声望494 粉丝

从简单到难 一步一步