技术思考 【微信小程序 支付】

204 阅读3分钟

微信小程序支付

微信小程序支付分为四个流程,获取用户 token ,创建用户订单,创建商品预订单,下单支付。

支付流程

获取用户信息

查看后台给出的接口文档,发现获取用户信息需要下面5个必传参数。其中四个参数可以通过小程序内置方法获取用户信息 uni.getUserProfile({}) 后得到,参数 code 可执行小程序登录 uni.login() 后获取到。

参数名必选类型参数说明
encryptedDatastring执行小程序 获取用户信息后 得到
rawDatastring执行小程序 获取用户信息后 得到
ivstring执行小程序 获取用户信息后 得到
signaturestring执行小程序 获取用户信息后 得到
codestring执行小程序登录后获取
async getToken() {
  // 获取用户信息
  const [errPro,resPro] = await uni.getUserProfile({desc:'刀刀'})
  console.log(resPro)

  // 获取参数code
  const [errLogin, resLogin] = await uni.login()
  console.log(resLogin)

  // 调用后台接口获取用户的token
  const {message} = await this.$u.post('/users/wxlogin', {
    encryptedData: resPro.encryptedData,
    rawData: resPro.rawData,
    iv: resPro.iv,
    signature: resPro.signature,
    code: resLogin.code
  })
  console.log(message.token);
  return message.token
},

创建用户订单

创建用户订单所需参数如下所示。

请求头参数:

参数名必选类型说明
Authorizationstring用户登录成功获取的token值

请求体参数

参数名必选类型说明
order_pricestring订单总价格
consignee_addrstring收货地址
goodsArray订单数组

参数订单总价与收货地址在前面已经通过计算属性获取,可以直接使用,订单数组需要的数据如下。

goods字段说明

参数名必选类型说明
goods_idnumber商品id
goods_numbernumber购买的数量
goods_pricenumber单价

查看接口文档,可以发现这些数据是每一项商品都有的数据,因此,可以通过遍历要购买的商品的数组数据获取。

拿到需要的数据后可以发送请求获取数据,封装请求头,参数为之前获取到的 token

async getOrderNumber(token) {
  const order = {
    order_price: this.cartPrice,
    consignee_addr: `${this.address} ${this.username} ${this.phone}`,
    goods: this.goodsChecked.map(item=>{
      return {
        goods_id: item.goods_id,
        goods_number: item.number,
        goods_price: item.goods_price
      }
    })
  }
  const {message} = await this.$u.post('/my/orders/create',order,{
    Authorization: token
  })
  console.log(message.order_number);
  return message.order_number
},

创建商品预订单

商品支付订单需要的参数如下所示,请求头(和创建订单一致,封装即可)、订单编号,已经通过创建订单的步骤获取到了,因此可直接发请求。

请求头参数:

参数名必选类型说明
Authorizationstring用户登录成功获取的token值

请求体参数:

参数名必选类型说明
order_numberstring订单编号
async getPay(order_number, token) {
  const {message} = await this.$u.post('/my/orders/req_unifiedorder',{order_number},{
    Authorization: token
  })
  console.log(message.pay);
  return message.pay
},

下单支付

查看 uni-app 官方文档,获取到需要的数据,如下表所示。这些数据已经通过创建商品预付订单时返回接受了,因此可以直接使用。

参数名类型必填说明平台差异说明
timeStampString微信小程序必填时间戳从1970年1月1日至今的秒数,即当前的时间。微信小程序
nonceStrString微信小程序必填随机字符串,长度为32个字符以下。微信小程序
packageString微信小程序必填统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=xx。微信小程序
signTypeString微信小程序必填签名算法,应与后台下单时的值一致微信小程序
paySignString微信小程序必填签名,具体签名方案参见 微信小程序支付文档(opens new window)微信小程序
async toPay() {
  const token = await this.getToken()
  
  const order_number = await this.getOrderNumber()

  const pay = await this.getPay(order_number)

  const [err,res] = await uni.requestPayment(pay)
  if (!err) {
    this.cartbuy()
    uni.switchTab({
      url: '/pages/cart/cart'
    })
  }
},

封装登录请求

根据 vue 的代码风格,我们可以把登录获取用户 token 的函数代码封装到 vuex 内,把获取到的 token 保存到 vuex 中,方便数据的调用与使用。

  1. 创建 user.js 的模块仓库,在 index.js 中导入

    const state = {
    
    }
    
    const mutations = {
    
    }
    
    const actions = {
    
    }
    
    export default {
      namespaced: true,
      state,
      mutations,
      actions
    }
    
    import user from "./modules/user";
    
    export default new Vuex.Store({
      modules: {
        user
      },
      getters
    })
    
  2. actions 函数中复制获取 token 的函数

    const state = {
      token: uni.getStorageSync('usertoken') || ''
    }
    
    const mutations = {
      getToken(state, token) {
        state.token = token
        uni.setStorageSync('usertoken', token)
      }
    }
    
    const actions = {
      async getToken({commit}) {
        const [errPro,resPro] = await uni.getUserProfile({desc:'刀刀'})
        console.log(resPro)
    
        const [errLogin, resLogin] = await uni.login()
        console.log(resLogin)
    
        const {message} = await this.$u.post('/users/wxlogin', {
          encryptedData: resPro.encryptedData,
          rawData: resPro.rawData,
          iv: resPro.iv,
          signature: resPro.signature,
          code: resLogin.code
        })
        console.log(message.token);
        commit('getToken', message.token)
      }
    }
    

    pay.vue 组件中用辅助函数获取到 actions 函数和保存的 token 值,直接调用 token 值即可。

    <script>
    import { mapGetters, mapMutations, mapActions, mapState } from "vuex";
    export default {
      data () {
        return {
          bool: true,
          address: '',
          phone: '',
          username: ''
        }
      },
      computed: {
        ...mapGetters(['goodsChecked', 'cartNumber', 'cartPrice']),
        ...mapState('user', ['token'])
      },
      methods: {
        ...mapMutations('cart',['cartbuy']),
        ...mapActions('user', ['getToken']),
        async toPay() {
          if (!this.token) {
            await this.getToken()
          }
    
          const order_number = await this.getOrderNumber()
    
          const pay = await this.getPay(order_number)
    
          const [err,res] = await uni.requestPayment(pay)
          if (!err) {
            this.cartbuy()
            uni.switchTab({
              url: '/pages/cart/cart'
            })
          }
        },
    
        async getPay(order_number) {
          const {message} = await this.$u.post('/my/orders/req_unifiedorder',{order_number},{
              Authorization: this.token
          })
          console.log(message.pay);
          return message.pay
        },
    
        async getOrderNumber() {
          const order = {
            order_price: this.cartPrice,
            consignee_addr: `${this.address} ${this.username} ${this.phone}`,
            goods: this.goodsChecked.map(item=>{
              return {
                goods_id: item.goods_id,
                goods_number: item.number,
                goods_price: item.goods_price
              }
            })
          }
          const {message} = await this.$u.post('/my/orders/create',order,{
              Authorization: this.token
          })
          console.log(message.order_number);
          return message.order_number
        }
      }
    }
    </script>
    
  3. 运行发现报错,错误信息为没有 post ,因为我们把获取 token 的函数复制去 vuex 中,this 指向不是 uni ,没有 $u 方法,因此把 this 改为 uni 即可。

封装请求拦截器

查看文档,封装一个请求拦截器。我们发现需要请求头的路由路径都是带 /my/ ,因此可以使用正则表达式来判断。

const install = (Vue, vm) => {
	// 此为自定义配置参数,具体参数见上方说明
	Vue.prototype.$u.http.setConfig({
		baseUrl: 'https://api-hmugo-web.itheima.net/api/public/v1',
		loadingText: '努力加载中~',
		loadingTime: 800,
		// 设置自定义头部content-type
		// header: {
		// 	'content-type': 'xxx'
		// }
		// ......
	});

	// 请求拦截部分,如配置,每次请求前都会执行
	Vue.prototype.$u.http.interceptor.request = (config) => {
		// 引用token
		
		// 方式二,如果没有使用uView封装的vuex方法,那么需要使用$store.state获取
		// config.header.token = vm.$store.state.token;
		if (/\/my\//.test(config.url)) {
			config.header.Authorization = vm.$store.state.user.token;
		}
		
		// 最后需要将config进行return
		return config;
		// 如果return一个false值,则会取消本次请求
		// if(config.url == '/user/rest') return false; // 取消某次请求
	}
}