8

最近在做微信小程序的后台,这两天做到了支付相关部分,有几个坑,这里当记录一下。

首先申请开通支付这里不多说了。开通支付后需要设置一个api秘钥,这个是签名的时候要用到的,需要注意的是,api只能在刚开始的时候看到,所以需要保存好。

流程

首先我们整理一下支付下单的流程。下面是微信官方文档的流程图,我这里一一解释一下每个流程是做什么的。

clipboard.png

  1. 用户请求下单,这个无疑问,就是商户自己的订单系统生成订单。

  2. 微信小程序请求下单支付,这里呢请求支付的肯定也是用户本身,如果用户未登录需要调用用户登录接口获取openid(注意:wx.getuserInfo是不能直接获取openid的,这又是另一个话题了),如果已经登录直接获取openid就行

  3. 服务端生成用的订单,这个也没有疑问。同样是商户的自身的订单系统。

  4. 服务端调用统一下单接口生成预支付交易,这个是微信支付本身必须的流程。这部分需要的参数比较多。需要注意一下。图中未提到的是签名。如果前面不正确,可以使用校验工具验证签名检查哪里发生了错误。这里的签名和下面小程序请求签名是不同的,注意不要混淆了。签名简单示意:
    clipboard.png

  5. 将数据带上请求统一下单的接口。如果签名正确会返回预支付信息(prepay_id)正确的结果应该返回:
    clipboard.png

  6. 正确的收结果后,需要对结果再进行签名返回给小程序,其中result就是返回的结果。只有下面列出的几个参数才参加签名,其他不需要。
    clipboard.png

  7. 参照小程序的请求支付接口我们发现接口参数已经齐全了。如果一切正常的话已经可以支付了。
    clipboard.png

实现

'use strict'
const crypto = require('crypto')
const request = require('axios')
const xml2js = require('xml2js')
const parser = new xml2js.Parser()
const builder = new xml2js.Builder()
const qs = require('querystring')

//生成签名
function getSign(data, apiKey) {
  const tmpObj = Object.create(null)
  for (const k of Object.keys(data).sort()) tmpObj[k] = data[k]
  const key = decodeURIComponent(qs.stringify(tmpObj) + '&key=' + apiKey)
  return crypto.createHash('md5').update(key).digest('hex').toUpperCase()
}

class WXPay {
  constructor(options, apikey) {
    this.options = options
    this.apiKey = apikey
    this.unifyOrderUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
    this.queryOrderUrl = 'https://api.mch.weixin.qq.com/pay/orderquery'
    this.sign = ''
  }
  //请求统一下单
  unifyOrder (obj) {
    this.sign = getSign(Object.assign(obj, this.options), this.apiKey)
    const _wxData = builder.buildObject(Object.assign(obj, { sign: this.sign }))
    return request.post(this.unifyOrderUrl, _wxData, {headers: { 'content-type': 'text/xml' }})
      .then(result => result.data)
      .then(function (result) {
        if (!result) return Promise.reject(new Error('数据不存在'))
        return new Promise(function(resolve, reject) {
          parser.parseString(result, (err, _data) => err ? reject(err) : resolve(_data.xml))})
      })
      .catch(err => Promise.reject(err))
  }
  //获取微信支付配置
  WXPayConfig (data) {
    return this.unifyOrder(data).then(result => {
      if (!result) return Promise.reject(new Error('something go wrong'))
      for (let key in result) result[key] = result[key][0]
      const _tmpData = {
        appId: result.appid,
        timeStamp: (Date.now()/1000).toString(),
        nonceStr: result.nonce_str,
        package: 'prepay_id=' + result.prepay_id,
        signType: 'MD5'
      }
      _tmpData.paySign = getSign(_tmpData, this.apiKey)
      return _tmpData
    }).catch(err => Promise.reject(err))
  }
  //查询订单状态
  queryWXOrders (obj) {
    this.sign = getSign(Object.assign(obj, this.options), this.apiKey)
    const _WXData = builder.buildObject(Object.assign(obj, { sign: this.sign }))
    return request.post(this.queryOrderUrl, _WXData, {headers: { 'content-type': 'text/xml' }})
      .then(result => result.data)
      .then(function (result) {
        if (!result) return Promise.reject(new Error('数据不存在'))
        return new Promise(function(resolve, reject) {
          parser.parseString(result, (err, _data) => err ? reject(err) : resolve(_data.xml))})
      })
      .catch(err => Promise.reject(err))
  }
}
module.exports = WXPay

上面代码可以直接使用,细节有空再补充


xiadd
2.6k 声望88 粉丝