最近在作微信小程序的後臺,這兩天作到了支付相關部分,有幾個坑,這裏當記錄一下。php
首先申請開通支付這裏很少說了。開通支付後須要設置一個api祕鑰,這個是簽名的時候要用到的,須要注意的是,api只能在剛開始的時候看到,因此須要保存好。ios
首先咱們整理一下支付下單的流程。下面是微信官方文檔的流程圖,我這裏一一解釋一下每一個流程是作什麼的。axios
用戶請求下單,這個無疑問,就是商戶本身的訂單系統生成訂單。小程序
微信小程序請求下單支付,這裏呢請求支付的確定也是用戶自己,若是用戶未登陸須要調用用戶登陸接口獲取openid(注意:wx.getuserInfo是不能直接獲取openid的,這又是另外一個話題了),若是已經登陸直接獲取openid就行微信小程序
服務端生成用的訂單,這個也沒有疑問。一樣是商戶的自身的訂單系統。api
服務端調用統一下單接口生成預支付交易,這個是微信支付自己必須的流程。這部分須要的參數比較多。須要注意一下。圖中未提到的是簽名。若是前面不正確,能夠使用校驗工具驗證簽名檢查哪裏發生了錯誤。這裏的簽名和下面小程序請求籤名是不一樣的,注意不要混淆了。簽名簡單示意:
微信
將數據帶上請求統一下單的接口。若是簽名正確會返回預支付信息(prepay_id)正確的結果應該返回:
app
正確的收結果後,須要對結果再進行簽名返回給小程序,其中result就是返回的結果。只有下面列出的幾個參數才參加簽名,其餘不須要。
工具
參照小程序的請求支付接口咱們發現接口參數已經齊全了。若是一切正常的話已經能夠支付了。
post
'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
上面代碼能夠直接使用,細節有空再補充