Vue2中实现微信分享支付功能

   近期做了一个微信公众号前后分离项目,前端采用Vue2开发,后端SpringBoot,今天迫不及待的来给大家分享一下这次在开发中遇到的一些坑以及解决办法。

  在这里,一些公众号的基本配置我就不多说了,不懂的可以看一下微信开发文档。

  话不多说,咱们开始吧!


安装微信JS-SDK

  首先需要通过npm安装微信的JS-SDK

npm -install weixin-js-sdk --save

  

  由于项目是基于微信公众号开发的,所有授权的时候微信会自己判断用户所用的浏览器是否为微信浏览器,所以我们这里并没有判断用户使用的是哪种浏览器。

封装JS-SDK

  接着,新建一个jssdk.js,用来初始化微信JS-SDK。该方法接收一个对象参数,用于微信分享参数设置。因为微信签名数据需要后端人员生成,所以需要使用axios来获取这些数据。然后前端调用wx.config方法完成配置,这里需要注

意的地方有几点:

  1. SPA单页面应用,url都有携带#,通过url获取config配置签名时,截取#号之前的数据当作入参。
  2. wx.config入参中的jsApiList一定要配置你用到的微信JS方法。
  3. 微信js-sdk-1.4.0废弃之前的“onMenuShareTimeline”,“onMenuShareAppMessage”,“onMenuShareQQ”,取代它们的是“updateAppMessageShareData”,“updateTimelineShareData”。详情见:微信开发文档

初始化微信JS-SDK

  由于每个页面的分享内容不通,每次跳转页面的时候都需要重新调用wx.config配置分享内容,所以我使用router的全局后置钩子touter.afterEach,分享参数通过路由中的meta获取,你也可以通过接口的形式配置。

实现代码

router路由index.js

import Vue from 'vue'
import Router from 'vue-router'
import CourseDetail from '@/components/CourseDetail'
import StudyAbroad from '@/article/StudyAbroad'
import MuseumIndustry from '@/article/MuseumIndustry'
import WeekendCamp from '@/article/WeekendCamp'
import ColdSummerCamp from '@/article/ColdSummerCamp'
import Login from '@/login/Login'
import Member from '@/member/Member'
import RechargeRecord from '@/member/RechargeRecord'
import CustomerService from '@/member/CustomerService'
import FeedBack from '@/member/FeedBack'
import NearFuture from '@/future/NearFuture'
import PersonalData from '@/member/PersonalData'
import RechargeDetails from '@/member/RechargeDetails'
import RechargeSuccess from '@/member/RechargeSuccess'
import RechargeFile from '@/member/RechargeFile'
import Payment from '@/member/Payment'
import Invalid from '@/member/Invalid'
import ResultAlready from '@/member/ResultAlready'
import NotStarted from '@/member/NotStarted'
import ClassList from '@/components/Classlist'
import Fillinformation from '@/components/Fillinformation/Fillinformation'
import SignupDetail from '@/components/SignupDetail/SignupDetail'
import WxFail from '@/components/result/WxFail'
import CourseFail from '@/components/result/CourseFail'
import SignupSuccess from '@/components/result/SignupSuccess'
import SignupFail from '@/components/result/SignupFail'
import Ditu from '@/components/Ditu'
import Author from '@/author/Author'
import DelToken from '@/member/DelToken'
import wx from '@/wx/jssdk'
import global from '@/global/global'

Vue.use(Router)

let router = new Router({
  // mode: 'history', // 把Router的mode修改为history模式,VueRouter默认的模式为HASH模式
  routes: [
    {
      path: '/CourseDetail/:courseId',
      name: 'CourseDetail',
      component: CourseDetail,
      meta: {
        title: '课程详情',
        type: true,
        shareInfo: {
          title: '知育童行',
          desc: '打造丰富多彩的亲子活动,帮助家长们给孩子更全面的素质教育。',
          imgUrl: global.WX_STATIC_URL + '/images/share/share.png'
        }
      },
      props: true
    },
    {
      path: '/StudyAbroad',
      name: 'StudyAbroad',
      component: StudyAbroad,
      meta: {
        title: '微留学',
        type: false
      }
    },
    {
      path: '/MuseumIndustry',
      name: 'MuseumIndustry',
      component: MuseumIndustry,
      meta: {
        title: '博物行',
        type: false
      }
    },
    {
      path: '/WeekendCamp',
      name: 'WeekendCamp',
      component: WeekendCamp,
      meta: {
        title: '周末营地',
        type: false
      }
    },
    {
      path: '/author',
      name: 'Author',
      component: Author,
      meta: {
        title: '授权中',
        type: false
      }
    },
    {
      path: '/DelToken',
      name: 'DelToken',
      component: DelToken,
      meta: {
        title: '删除缓存',
        type: false
      }
    }
  ],
  scrollBehavior (to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    } else {
      return { x: 0, y: 0 }
    }
  }
})

// 全局守卫,微信授权
router.beforeEach((to, from, next) => {
  // 路由发生变化修改页面title
  if (to.meta.title) {
    document.title = to.meta.title
  }
  if (process.env.NODE_ENV !== 'development') {
    const token = window.localStorage.getItem('token')
    if (token) {
      if (to.path === '/author') {
        next({
          path: '/'
        })
      } else {
        next()
      }
    } else {
      if (to.path !== '/author') {
        // 保存用户进入的url
        window.localStorage.setItem('authUrl', to.fullPath)
        // 跳转到微信授权页面
        window.location.href = process.env.BASE_URL + '/wx/OAuth2/index'
      } else {
        next()
      }
    }
  } else {
    window.localStorage.setItem('token', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJvUUFFYndSSU5VVlhPLVZoOWhEcDUzX3RNeEgwIn0.eShRG4fVFFv4w2gHnkyh7QDdVpG1meOHSZXOrbq-psE')
  }
  next()
})

router.afterEach((to, from) => {
  // 微信JSSDK统一处理
  wx.init({
    title: to.meta.type ? to.meta.shareInfo.title : '',
    desc: to.meta.type ? to.meta.shareInfo.desc : '',
    link: global.SHARE_URL + '/#' + to.path,
    imgUrl: to.meta.type ? to.meta.shareInfo.imgUrl : '',
    type: to.meta.type
  })
})

export default router

jssdk.js

import wx from 'weixin-js-sdk'
import axios from '@/utils/request'
import app from '@/main'
const jsApiList = ['updateAppMessageShareData', 'updateTimelineShareData', 'chooseImage', 'hideAllNonBaseMenuItem', 'showAllNonBaseMenuItem', 'chooseWXPay', 'openLocation']

/**
 * 微信分享配置
 * @param url 分享Url
 * @param isShowShareMenu true:显示分享菜单 false:禁用
 */
function init (shareBean) {
  axios.post('/wx/config/getJsApiSignature', {
    url: window.location.href.split('#')[0]
  }).then(function (response) {
    let data = response.data
    wx.config({
      debug: false,
      appId: data.appId, // 和获取Ticke的必须一样------必填,公众号的唯一标识
      timestamp: data.timestamp, // 必填,生成签名的时间戳
      nonceStr: data.nonceStr, // 必填,生成签名的随机串
      signature: data.signature, // 必填,签名,见附录1
      // 需要分享的列表项:发送给朋友,分享到朋友圈,分享到QQ,分享到QQ空间
      jsApiList: jsApiList
    })
    // 处理验证失败的信息
    wx.error(function (res) {
      console.error('验证失败返回的信息')
    })
    // 处理验证成功的信息
    wx.ready(function () {
      // 自定义“分享给朋友”及“分享到QQ”按钮的分享内容(1.4.0)
      if (shareBean.type) {
        wx.updateAppMessageShareData({
          title: shareBean.title, // 分享标题
          desc: shareBean.desc, // 分享描述
          link: shareBean.link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
          imgUrl: shareBean.imgUrl, // 'http://cicstatic.thjinrong.com/static/images/ClassicsList/share/chinese.png', // 分享图标
          success: function () {
            // 用户确认分享后执行的回调函数
            console.info('分享成功')
          }
        })
        // 自定义“分享到朋友圈”及“分享到QQ空间”按钮的分享内容(1.4.0)
        wx.updateTimelineShareData({
          title: shareBean.title, // 分享标题
          link: shareBean.link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
          imgUrl: shareBean.imgUrl, // 分享图标
          success: function () {
            // 设置成功
            console.info('分享成功')
          }
        })
      }
      // 隐藏所有非基础按钮接口
      if (!shareBean.type) {
        wx.hideAllNonBaseMenuItem()
      } else {
        wx.showAllNonBaseMenuItem()
      }
    })
  })
}

/**
 *  上传图片
 */
function chooseImage () {
  wx.chooseImage({
    count: 1, // 默认9
    sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
    sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
    success: function (res) {
      // let localIds = res.localIds // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
      wx.getLocalImgData({
        localId: res.localIds[0], // 图片的localID
        success: function (res) {
          // localData是图片的base64数据,可以用img标签显示
          // store.dispatch('setLocalId', res.localData)
        }
      })
    }
  })
}

/**
 *  支付
 */
function pay (data) {
  wx.chooseWXPay({
    timestamp: data.data.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
    nonceStr: data.data.nonceStr, // 支付签名随机串,不长于 32 位
    package: data.data.packageValue, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
    signType: data.data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
    paySign: data.data.paySign, // 支付签名
    outTradeNo: data.data.outTradeNo, // 商户订单号
    success: function (res) {
      // 支付成功后的回调函数
      console.info(res)
      if (res.errMsg === 'chooseWXPay:ok') {
        // 使用以上方式判断前端返回,微信团队郑重提示    :
        // res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
        // app.$store.dispatch('updateStatus', true)
        console.info('支付成功')
      } else {
        app.$store.dispatch('updateStatus', false)
        console.info('支付失败')
      }
    },
    // 支付取消回调函数
    cancel: function (res) {
      app.$store.dispatch('updateStatus', false)
      console.info('取消支付')
    },
    // 支付失败回调函数
    fail: function (res) {
      app.$store.dispatch('updateStatus', false)
      console.info('支付失败')
    }
  })
}

/**
 *  地图
 *  @param 纬度
 *  @param 经度
 */
function map (params) {
  wx.openLocation({
    latitude: params.latitude, // 纬度,浮点数,范围为90 ~ -90
    longitude: params.longitude, // 经度,浮点数,范围为180 ~ -180
    name: params.name, // 位置名
    address: params.address, // 地址详情说明
    scale: params.scale, // 地图缩放级别,整形值,范围从1~28。默认为最大
    infoUrl: params.infoUrl // 在查看位置界面底部显示的超链接,可点击跳转
  })
}

export default {
  init: init,
  chooseImage: chooseImage,
  pay: pay,
  map: map
}

  

  我们项目中只使用到了分享,支付,地图功能,到这里微信分享就开发好了,需要注意几点:

  1. 微信JS判断支付成功,使用 res.errMsg === 'chooseWXPay:ok' 判断,微信JS取消支付/支付失败不是在success回调函数中判断,而是通过cancel回调函数和fail回调函数判断。
  2. 地图经纬度一定不要写反。嘿嘿嘿!

微信支付

   我们将封装好的jssdk.js放入全局vue中,这样使用起来方便。在main.js中引入jssdk.js,再将jssdk抛出的对象放入全局变量中。

  支付之前,需要调用后端接口完成统一下单,下单成功之后才可以调用微信支付,话不多说,直接上代码了。

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import './assets/css/reset.css'
import './assets/js/font'
import axios from '@/utils/request.js'
import wx from '@/wx/jssdk'
import VConsole from 'vconsole'
import global from '@/global/global'
import store from '@/store/index'
// 全局处理错误提示、数据加载
import {ToastPlugin, LoadingPlugin} from 'vux'
Vue.use(ToastPlugin)
Vue.use(LoadingPlugin)

// VConsole加载
process.env.NODE_ENV === 'test' && Vue.use(new VConsole())

// import 'element-ui/lib/theme-chalk/index.css'
Vue.config.productionTip = false
Vue.prototype.axios = axios
Vue.prototype.globalParams = global

// 加载mock模块
process.env.NODE_ENV === 'mock' && require('@/mock/login')
process.env.NODE_ENV === 'mock' && require('@/mock/classList')
process.env.NODE_ENV === 'mock' && require('@/mock/courseDetail')
process.env.NODE_ENV === 'mock' && require('@/mock/fillinformation')
process.env.NODE_ENV === 'mock' && require('@/mock/signupDetail')

// 引入工具集
Vue.prototype.toolkit = require('@/utils/toolkit')
// 微信JSSDK
Vue.prototype.wx = wx

/* eslint-disable no-new */
const app = new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

export default app

调用微信支付

// 微信支付
weChatPayment (orderId) {
  let _this = this
  this.axios.post('/wx/pay/act', {
    orderId: orderId,
    body: 'Demo-充值',
  }).then(function (res) {
    let data = res.data
    if (data.code === 1) {
      // 统一下单返回参数
      _this.wx.pay(data)
    } else if (data.code === 0) {
      console.info('统一支付下单失败')
      _this.$store.dispatch('updateStatus', false)
    }
  })
},

  作为java开发的我,只能给大家分享这么多了,不喜勿喷,谢谢各位大神 !!!