uniApp H5微信网页授权,微信支付

用于记录 uniapp 编译成H5后,完成微信授权和微信支付,微信浏览器中主要看场景二

业务场景介绍

业务需要在H5中完成支付功能(微信支付)

使用场景介绍

1、H5运行在普通浏览器中;
2、H5运行在微信浏览器中。

分析过程
场景一:H5运行在普通浏览器中

H5普通浏览器支付过程并不复杂,大致流程如下:
a、前端发起支付请求给后端,后端收到后,调用微信支付接口下单;
b、微信支付接口会返回一个支付链接给到后端;
c、后端把微信返回的支付链接通过支付请求(a)接口返回给前端;
d、前端拿到链接之后,跳转到此链接,开启轮询调用后端接口查询是否支付成功;
e、在跳转之后的链接完成支付动作的操作,用户自己点击返回到发起支付的页面。
微信H5支付开发文档

// 页面卸载时清除计时器,停止轮询,用于页面计时器重复启用
	onUnload() {
        clearTimeout(this.payTime);
    }

// 如下为methods:{}中代码的一部分
// 同意支付方法 
        agreePayFn(price) {
            let params = {};
            params.price = price; //价格
            params.id = this.estimateSchemeId; //支付的产品ID
            this.getWxPayCb(); //获取支付是否成功的回调方法 ,用于轮询后端接口
            h5WxPay(params).then(res => { // 发起微信支付请求给后端,res是后端返回的支付链接
                params = null; //释放内存
                location.href = res; //跳转到支付链接
            });
        },
 // 获取微信支付结果
        getWxPayCb() {
            const self = this;
            let params = {};
            params.id = this.estimateSchemeId; //产品ID
            clearTimeout(this.payTime); //清除计时器
            function next() {    //用于轮询的函数
                getWxPayReuslt(params) //获取微信支付结果接口
                    .then(res => {
                        if (res == true) { //后台返回 true 成功
                           	// doSomething
                            return;
                        }
                        if (res == false) {  //后台返回 false 失败 
							// doSomething
                            showToast('支付失败,请重新支付', 'none', 2000); //此方法是自己封装的uni.showToast方法
                            return;
                        }
                        // 无结果时每隔一秒轮询接口
                        self.payTime = setTimeout(() => {
                            clearTimeout(self.payTime);
                            next();
                        }, 1000);
                    })
                    .catch(e => {  //接口出现异常时处理
                        self.payTime = setTimeout(() => {
                            clearTimeout(self.payTime);
                            next();
                        }, 1000);
                    });
            }
            next(); //进入就进行调用
        }
场景二:H5运行在微信浏览器【此文的重点】

H5支付是指商户在微信客户端外的移动端网页展示商品或服务
因此在微信浏览器支付时需要,使用另外一套支付过程----微信jsapi传送门

根据微信【JSAPI】支付开发的要求,则需要完成2步操作
1、有支付功能的页面,需要通过微信网页授权获取code,并把code传给后台获得当前用户的openid,openid将用于后台统一下单时使用;
2、发起支付(有两种方式,a、直接调用;b、通过jssdkapi调用)

问题难点,hash 模式下网页授权获取code

由于uniapp开发的H5采用的hash路由模式,在网页授权时,微信会扰乱带#号的参数,以至于回调时不能回到发起页面。

解决方案

1、需要授权的页面,在授权之前,把带#号的url转换成不带#号的url;
2、微信授权回来之后,在项目入口,把不带#号的url转换成带#号的url。

封装WX_JS_SDK 文件

// 引入qs解析包,用于将URL解析成对象
import qs from 'qs'
// 引入微信jsapi包
import wx from 'weixin-jsapi';
// 引入自己定义的微信配置和请求
import { weChatConfig, callSever } from '../api/requestConfig.js';
// 需要用到的微信api接口
const jsApiList = ['updateAppMessageShareData', 'updateTimelineShareData', 'onMenuShareTimeline',
    'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareQZone', 'onMenuShareWeibo', 'chooseWXPay'
];

// 注入微信权限验证配置
let injectionWxConfig = function(params) {
    const { timestamp, nonceStr, signature } = params
    wx.config({
        debug: false,
        appId: weChatConfig.appId, // 必填,公众号的唯一标识
        timestamp: parseInt(timestamp), // 必填,生成签名的时间戳 
        nonceStr: nonceStr, // 必填,生成签名的随机串
        signature: signature, // 必填,签名
        jsApiList: jsApiList // 必填,需要使用的JS接口列表
    })
}

/**
 * 调起微信支付
 * params 支付参数
              timestamp: '', 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
              nonceStr: '', 支付签名随机串,不长于 32 位
              package: '', 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
              signType: '', 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
              paySign: '', 支付签名
              signature: ''  //签名 用于权限注入使用
 * successCallBack 成功回调
 * failCallBack 失败回调
 * cancelBack 取消回调
 */
const wxPay = (params, successCallBack, failCallBack, cancelBack) => {
    injectionWxConfig(params)
    wx.ready(() => {
        wx.chooseWXPay({
            timestamp: params.timestamp,
            nonceStr: params.nonceStr,
            package: params.package,
            signType: params.signType,
            paySign: params.paySign,
            success: successCallBack,
            fail: failCallBack,
            cancel: cancelBack
        })
    })
}




//-------------------------------------------------------------------------------------//
/**
 * 如下配置是为了在微信浏览器环境中,网页拿去授权
 * 由于采用的hash模式路由,会导致授权后的链接被扰乱(微信会处理掉#号) redirect_uri
 * 解决方案:
 * 1、授权前重置 redirect_uri 改为没有#号的url,把当前页面的原有参数信息,额外信息,codePath参数(用来指定跳转页面的,此处为授权发起页)
 * 2、项目入口处,将没有#号的url改回到 带#号的url,跳转到指定页面
 */

/**
 * 微信授权-需要授权的页面: [页面用到的参数key] 
 * 参数key适用于获取微信回调跳传回来时的参数(授权时传了此参数,如未传则不填,也不用写页面)
 */
const plagForm = {
    '/pages/interestModule/interestLoan/interestLoan': ['']
}

/**
 * 重置redirect_uri 需要用到的页面使用
 * 把有#号的url改为没有#号的url,
 * scope 授权作用域  snsapi_base(静默授权)   snsapi_userinfo(弹框授权)
 * path 授权所在的页面路径
 */
const getWxCode = function(scope = 'snsapi_base', path) {
    let homeUrl = '',
        searchObj = {},
        searchStr = '';
    let oriUrls = location.href.split('?');
    let baseShareUrl = location.origin + location.pathname
    homeUrl = baseShareUrl
    // 如果有携带参数,把参数进行拼接,参数不能带code
    if (oriUrls.length > 1) {
        let searchUrls = oriUrls[1].split('#')
        let searchUrlReal = searchUrls[0]
        searchObj = qs.parse(searchUrlReal)
        let { code } = searchObj
        if (code) {
            delete searchObj.code
        }
    }
    // 路径存入到codePath中,用于微信回调时入口处处理
    searchObj.codePath = path
    searchStr = `?${qs.stringify(searchObj)}`
    homeUrl += searchStr
    let redirect_uri = encodeURIComponent(homeUrl)
    let response_type = 'code'
    let state = ''
    let url =
        `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${weChatConfig.appId}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope}&state=${state}#wechat_redirect`
    location.href = url
}

/**
 * 重置redirect_uri 项目入口处使用
 * 将没有#号的url改回到 带#号的url
 * 如果地址有页面需要传递的参数,需要在 plagForm 进行设置
 * 返回之后的例子:
 * https://xxxx.com/?codePath=%2Fpages%2FinterestModule%2FinterestLoan%2FinterestLoan&code=021cZo0002D87L1BSH200bPoNK3cZo0Z&state=#/pages/indexTabBar/index
 */
const urlResetForWxCode = function() {
    let needRedirect = false,
        homeUrl = '',
        searchObj = {},
        searchStr = '';
    let oriUrls = location.href.split('?');
    let baseShareUrl = location.origin + location.pathname
    homeUrl = baseShareUrl
    if (oriUrls.length > 1) {
        let searchUrls = oriUrls[1].split("#")
        let searchUrlReal = searchUrls[0]
        let searchObjReal = {}
        searchObj = qs.parse(searchUrlReal)
        let { codePath } = searchObj
        needRedirect = codePath
        if (codePath) {
            plagForm[codePath].forEach(item => searchObjReal[item] = searchObj[item])
            if (plagForm[codePath].length) {
                searchStr = `?${qs.stringify(searchObjReal)}`
            }
            homeUrl += `#${codePath}` + (searchstr.length > 1 ? searchstr : '')
        }
    }
    return {
        needRedirect, //需要重定向
        homeUrl, //跳转链接
        search: searchObj //存着code信息和codePath信息等
    }
}
const WX_JS_SDK = {
    urlResetForWxCode,
    getWxCode,
    wxPay
}
export default WX_JS_SDK

在main.js中引用刚刚封装好的WX_JS_SDK文件

import WX_JS_SDK from './utils/WX_JS_SDK.js';
//添加到原型中
Vue.prototype.$wxSdk = WX_JS_SDK

第一步:需要授权的页面,把带#号的地址更换成不带#号的地址,用于获取微信code

	onLoad(options) {
        // 如果是微信环境,且没有wxCode,则需要进行网页授权
        const wxCode = uni.getStorageSync('wxCode');
        console.log('是否浏览器环境','isWeChatH5--自定义的判断是否是浏览器环境')
        if (isWeChatH5) {
            if (isEmpty(wxCode)) {
                this.$wxSdk.getWxCode('snsapi_base', '/pages/interestModule/interestLoan/interestLoan');
                return;
            } else {
                // 通过code调取接口获取openID,用于 统一下单接口 预支付
            }
        }
    },

第二步:项目入口处APP.VUE,处理微信返回的地址,把不带#号的地址更换成带#号的地址,跳转到指定页面

import { getSystemInfo, isWeChatH5 } from '@/utils/index.js';
export default {
    onLaunch: function(options) {
        getSystemInfo();
        // 如果是在微信环境,有通过授权页面回来时
        if (isWeChatH5) {
            let { needRedirect, homeUrl, search } = this.$wxSdk.urlResetForWxCode();
            if (needRedirect) {
                uni.setStorageSync('wxCode', search.code); //本地存入code
                location.href = homeUrl;  //跳转到发起授权的页面
                return
            }
        }
        //其它业务逻辑 。。。。。
    }
};

第三步,需要支付的页面调用 WX_JS_SDK 封装的方法

 // 同意支付
        agreePayFn(price) {
            let params = {};
            params.price = price;
            params.id = this.estimateSchemeId;
            if (isWeChatH5) {
            	//微信浏览器环境,调用微信api支付方法,如需直接支付可以看场景二的直接调用支付
                this.$wxSdk.wxPay(
                    params,
                    sucess => {
                        // 成功回调
                        console.log('成功回调了')
                    },
                    fail => {
                        // 失败回调
                        console.log('失败回调了')
                    },
                    cancel => {
                        // 取消回调
                        console.log('取消回调了')
                    }
                );
            } else {
            // 非微信浏览器环境 支付,可看场景一
                this.getWxPayCb();
                h5WxPay(params).then(res => {
                    params = null;
                    uni.removeStorageSync('from');
                    location.href = res;
                });
            }
        },

你可能感兴趣的:(JS学习)