十 Node.js实现微信小程序支付功能
一 开发前提
1.一个注册好,并且通过微信认证的,主体信息不能是个人的小程序。
主体信息为个人的小程序是没有微信支付功能的,并且也无法获取用户手机号码
2.一个注册好并且通过微信认证的商户号
3.商户号与小程序绑定
4.这篇文章是以旧版微信支付为例开发微信支付。文档地址:旧版微信支付文档
二 支付流程介绍
1.由小程序端发起后台请求,后台根据传回的参数请求微信同一下单接口,将拿到的参数返回给小程序端,再由小程序拉起微信支付,支付完成以后,在回调函数中执行对应的更新操作。
三 上代码
1.小程序端发起后台请求,完成统一下单
1.1 小程序端发起后台请求
// request是我自己封装的wx.request方法,方便调用。
// wechatapp/wechatpay是我的接口地址,以post方式访问
// data 是参数对象
request('wechatapp/wechatpay', data).then(res => {
let result = res.data
//result 是后台返回的数据,我的封装格式如下:
// {
// flag:Boolean, //用于判断请求结果是否正常
// state:Int, //状态码,用于确定请求状态是否正常
// data:Array|Object, //返回的数据,根据情况返回数组或者对象,增删改操作不返回data
// message:String, //增删改的操作完成提示
// error:String //增删改的错误提示
// }
if (result.flag) {
let obj = result.data
//这里得到微信统一下单的数据
//拉起小程序微信支付,此处粘贴1.3节点的代码
//**************************************
}
})
1.2 Node.js后台接收请求,完成统一下单(重点)
//此处是Node.js后台的router,与1.1步骤中的访问API一致
wechatapp.post('/wechatpay', (req, res) => {
let b = req.body
//PayOrder方法中封装了完成微信统一下单的全部重要内容
// req.ip 是当前访问该接口的ip地址,返回一个IPv6格式的地址
// b.openid 是当前请求访问该接口的唯一码,该接口需要在小程序中完成登录才能获得,然后传回后台
common2.PayOrder(b, b.openid, req.ip, result => {
res.send(result)
})
})
下面是PayOrder方法的完整内容
需要引入的第三方module:
1.request 用于访问第三方接口 npm i request
2. md5 用于完成MD5加密 npm i md5
3. xml-js 用于接口回调后格式化xml数据 npm i xml-js
PayOrder(b, openid, currip, cb) {
let nonceStr = common.CreateCode('', 20) //随机字符串
let out_trade_no = common.CreateCode('WPAY', 20) //支付订单号
//第一次签名
let body = this.WePaySign(b, openid, nonceStr, out_trade_no, currip)
//此处的this.BaseURL=‘https://api.mch.weixin.qq.com/pay/unifiedorder’
//这是微信旧版统一下单接口
request({ url: this.BaseURL, method: "POST", body }, (err, response, result) => {
if (!err && response.statusCode == 200) {
//将返回的数据格式化为JSON,再由JSON格式化为JS对象
//此处的convert是引入xml-js的别名
var result1 = convert.xml2json(result, { compact: true, spaces: 4 });
let obj = JSON.parse(result1)
let xmlobj = obj.xml
//根据文档,通过下面两个值得结果判断是否完成微信统一下单
if (xmlobj.return_code._cdata == "SUCCESS" && xmlobj.result_code._cdata == "SUCCESS") {
//prepay_id 该参数为统一下单接口返回的关键参数,我们就是要用它完成微信支付
let prepay_id = xmlobj.prepay_id._cdata
/*
以下代码用于在微信小程序中拉起微信支付时使用
根据接口要求,需要完成二次签名
*/
let appid = xmlobj.appid._cdata
//获取时间戳
let timeStamp = parseInt((new Date().getTime() / 1000)) + ''
let stringA = `appId=${appid}`
stringA += `&nonceStr=${nonceStr.toUpperCase()}`
stringA += `&package=${'prepay_id=' + prepay_id}`
stringA += `&signType=MD5`
stringA += `&timeStamp=${timeStamp}`
stringA += '&key=' + common.mch_secret
//第二次签名
let key = md5(stringA).toUpperCase()
let data = {
key,
prepay_id,
nonceStr,
appid,
timeStamp,
out_trade_no,
package: 'prepay_id=' + prepay_id
}
cb({ state: 200, flag: true, data })
}
} else {
cb({ state: 200, flag: false, message: err })
}
});
},
/**
* 微信支付下单签名
*/
WePaySign(b, openid, nonce_str, out_trade_no, currip) {
//需要将参数封装成XML格式
let total_fee = b.total * 100 //支付金额,以分为单位
//拼接签名字符串
// 此处的body是商品说明,在微信支付完成界面显示
// notify_url 返回通知URL
// mch_id 商户号
let stringA = `appid=${common.appid}&body=微信统一下单测试 &mch_id=${common.mch_id}&nonce_str=${nonce_str}¬ify_url=${common.notify_url}&openid=${openid}&out_trade_no=${out_trade_no}&spbill_create_ip=${currip}&total_fee=${total_fee}&trade_type=JSAPI`
//mch_secret 商户秘钥
stringA += `&key=${common.mch_secret}`
//生成秘钥
let key = MD5(stringA).toUpperCase()
let formData = "<xml>"
formData += "<appid>" + common.appid + "</appid>"
// formData += "<body>" + JSON.stringify(b) + "</body>"
formData += "<body>微信统一下单测试</body>"
formData += "<mch_id>" + common.mch_id + "</mch_id>"
formData += "<nonce_str>" + nonce_str + "</nonce_str>"
formData += "<notify_url>" + common.notify_url + "</notify_url>"
formData += "<openid>" + openid + "</openid>"
formData += "<out_trade_no>" + out_trade_no + "</out_trade_no>"
formData += "<sign>" + key + "</sign>"
formData += "<spbill_create_ip>" + currip + "</spbill_create_ip>"
formData += "<total_fee>" + total_fee + "</total_fee>"
formData += "<trade_type>JSAPI</trade_type>"
formData += "</xml>"
return formData
}
1.3 获取统一下单的返回数据,拉起微信支付
下面是小程序拉起微信支付的代码,具体的格式请大家参考文档
将下面的的代码粘贴到1.1节点的标识处,也可以单独封装一个方法
wx.requestPayment({
appId: obj.appId,
timeStamp: obj.timeStamp,
nonceStr: obj.nonceStr,
package: obj.package,
signType: 'MD5',
paySign: obj.key,
success(e) {
//用户支付成功时更新数据库订单信息
data = { total, openid: app.globalData.userInfo.openid, orderStr, out_trade_no: obj.out_trade_no }
self.UpDateOrder(data)
},
fail(err) {
console.log('取消支付');
self.onLoad()
}, complete(res) {
}
四 感悟
第一次做微信支付,遇到的坑比较多,不过最多的还是签名那一块,两次签名折腾了好久,下面附上微信官方提供的签名校验工具地址
签名校验工具
1.第一大坑:一定要注意去空格去回车,否则与官方签名不一致,会导致签名失败。
2.第二大坑:统一下单接口和拉起微信支付时所用的随机码必须是同一个,同一个。否则统一下单接口之后拉起微信支付会提示签名失败,这一点文档中没说明,本人也是折腾了好一会儿才发现这个坑。
3.另外线上的小程序IP地址需要https域名,下一节为大家讲述怎么白嫖SSL证书及怎么为Node.js安装SSL证书