微信小程序從零開始接入支付

最近在作微信小程序的後臺,這兩天作到了支付相關部分,有幾個坑,這裏當記錄一下。php

首先申請開通支付這裏很少說了。開通支付後須要設置一個api祕鑰,這個是簽名的時候要用到的,須要注意的是,api只能在剛開始的時候看到,因此須要保存好。ios

流程

首先咱們整理一下支付下單的流程。下面是微信官方文檔的流程圖,我這裏一一解釋一下每一個流程是作什麼的。axios

clipboard.png

  1. 用戶請求下單,這個無疑問,就是商戶本身的訂單系統生成訂單。小程序

  2. 微信小程序請求下單支付,這裏呢請求支付的確定也是用戶自己,若是用戶未登陸須要調用用戶登陸接口獲取openid(注意:wx.getuserInfo是不能直接獲取openid的,這又是另外一個話題了),若是已經登陸直接獲取openid就行微信小程序

  3. 服務端生成用的訂單,這個也沒有疑問。一樣是商戶的自身的訂單系統。api

  4. 服務端調用統一下單接口生成預支付交易,這個是微信支付自己必須的流程。這部分須要的參數比較多。須要注意一下。圖中未提到的是簽名。若是前面不正確,能夠使用校驗工具驗證簽名檢查哪裏發生了錯誤。這裏的簽名和下面小程序請求籤名是不一樣的,注意不要混淆了。簽名簡單示意:
    clipboard.png微信

  5. 將數據帶上請求統一下單的接口。若是簽名正確會返回預支付信息(prepay_id)正確的結果應該返回:
    clipboard.pngapp

  6. 正確的收結果後,須要對結果再進行簽名返回給小程序,其中result就是返回的結果。只有下面列出的幾個參數才參加簽名,其餘不須要。
    clipboard.png工具

  7. 參照小程序的請求支付接口咱們發現接口參數已經齊全了。若是一切正常的話已經能夠支付了。
    clipboard.pngpost

實現

'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

上面代碼能夠直接使用,細節有空再補充

相關文章
相關標籤/搜索