最近在做微信小程序的后台,这两天做到了支付相关部分,有几个坑,这里当记录一下。
首先申请开通支付这里不多说了。开通支付后需要设置一个api秘钥,这个是签名的时候要用到的,需要注意的是,api只能在刚开始的时候看到,所以需要保存好。
流程
首先我们整理一下支付下单的流程。下面是微信官方文档的流程图,我这里一一解释一下每个流程是做什么的。
用户请求下单,这个无疑问,就是商户自己的订单系统生成订单。
微信小程序请求下单支付,这里呢请求支付的肯定也是用户本身,如果用户未登录需要调用用户登录接口获取openid(注意:wx.getuserInfo是不能直接获取openid的,这又是另一个话题了),如果已经登录直接获取openid就行
服务端生成用的订单,这个也没有疑问。同样是商户的自身的订单系统。
服务端调用统一下单接口生成预支付交易,这个是微信支付本身必须的流程。这部分需要的参数比较多。需要注意一下。图中未提到的是签名。如果前面不正确,可以使用校验工具验证签名检查哪里发生了错误。这里的签名和下面小程序请求签名是不同的,注意不要混淆了。签名简单示意:
将数据带上请求统一下单的接口。如果签名正确会返回预支付信息(prepay_id)正确的结果应该返回:
正确的收结果后,需要对结果再进行签名返回给小程序,其中result就是返回的结果。只有下面列出的几个参数才参加签名,其他不需要。
参照小程序的请求支付接口我们发现接口参数已经齐全了。如果一切正常的话已经可以支付了。
实现
'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
上面代码可以直接使用,细节有空再补充
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。