最近做的小程序需要用到微信支付,但是由于后端只会Node.js,所以只能用Node.js实现微信支付功能了,微信支付官方没有提供Node.js的SDK,只好手动实现了,确实有点点麻烦
下单流程
具体实现
后端获取openid
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| var request = require("../utils/request").request function code2Session(code) { return new Promise((resolve, reject) => { if (!code) { reject({ errcode: "error_noCode" }) } else { request({ url: "https://api.weixin.qq.com/sns/jscode2session", data: { appid: "wx0da2fc08*******", secret: "19083b917c9d08f***************", js_code: code, grant_type: "authorization_code" }, method: "GET", }).then(res => { let resObj = JSON.parse(res) if (resObj.openid) { resolve({ errcode: "success", data: resObj }) } else if (resObj.errcode == 40163) { reject({ errcode: "error_codeBeenUsed" }) } else if (resObj.errcode == 40029) { reject({ errcode: "error_invalidCode" }) } else { reject({ errcode: "error_unknownError" }) } }) } }) }
|
request是封装axios的方法
后端prepay接口
POST https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi
参数:
1 2 3 4 5 6 7 8 9 10 11 12 13
| { mchid: config.mchid, out_trade_no: getUUID(), appid: config.appid, description: "test", notify_url: "", amount: { total: money }, payer: { openid: openid } }
|
数字签名格式
1
| WECHATPAY2-SHA256-RSA2048 mchid="${mchid}",nonce_str="${nonce_str}",signature="${signature}",timestamp="${timestamp}",serial_no="${serial_no}"
|
mchid为商户号,serial_no为证书序列号
数字签名写法
微信支付的数字签名算法是SHA256withRSA后再base64,一开始打算用crypto实现,但是一直报错,所以用jsrsasign来写了,content是签名内容,privateKey是私钥,hash这里是SHA256withRSA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
const { KJUR, hextob64 } = require('jsrsasign')
var SignUtil = {
rsaSign: function (content, privateKey, hash) { const signature = new KJUR.crypto.Signature({ alg: hash, prvkeypem: privateKey }) signature.updateString(content) const signData = signature.sign() return hextob64(signData) }, }
module.exports = SignUtil
|
需信息拼接
需加密信息格式如下
1 2 3 4 5
| HTTP请求方法\n URL\n 请求时间戳\n 请求随机串\n 请求报文主体\n
|
使用joinMessage函数拼接
1 2 3
| function joinMessage(...args) { return args.join("\n") + "\n" }
|
发请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| const fs = require("fs") const axios = require("axios") const getUUID = require("./getUUID") const config = require("../config.js") const utils = require("./utils") const SignUtil = require("./rsa") const pem = fs.readFileSync("./certs/apiclient_key.pem").toString()
function calcSign(msg) { return SignUtil.rsaSign(msg, pem, "SHA256withRSA") }
function prepay(money, openid) { return new Promise((resolve, reject) => { let nonce_str = getUUID() let ts = utils.getTimeStamp() let reqBody = { mchid: config.mchid, out_trade_no: getUUID(), appid: config.appid, description: "test", notify_url: "https://waimai.diaoan.xyz/callback", amount: { total: money }, payer: { openid: openid } } let msg = utils.joinMessage("POST", "/v3/pay/transactions/jsapi", ts, nonce_str, JSON.stringify(reqBody)) let signature = calcSign(msg) let auth = `WECHATPAY2-SHA256-RSA2048 mchid="${config.mchid}",nonce_str="${nonce_str}",signature="${signature}",timestamp="${ts}",serial_no="${config.serial_no}"` console.log(msg); console.log(signature); console.log(auth); axios.post("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi", reqBody, { headers: { Authorization: auth } }).then(res => { if (res.status == 200) { let package = `prepay_id=${res.data.prepay_id}` let paySignMsg = utils.joinMessage(config.appid, ts, nonce_str, package) let paySign = calcSign(paySignMsg) resolve({ nonce_str: nonce_str, ts: ts, prepay_id: res.data.prepay_id, paySign: paySign }) } }).catch(err => { console.log(err) reject(err) }) }) }
|
生成paySign
顺序是appid,ts,nonce_str,package,不能弄错,拼接后,再进行一次数字签名,传到前端
前端唤起微信支付
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| api.request({ url: "/prepay", method: "POST", data: { money: 1, openid: wx.getStorageSync('user').openid } }).then(res => { console.log(res) let data = { nonceStr: res.data.nonce_str, package: `prepay_id=${res.data.prepay_id}`, paySign: res.data.paySign, timeStamp: res.data.ts.toString(), signType: "RSA" } console.log(data) wx.requestPayment(data) }).catch(err => { console.log(err) })
|
api.request是对wx.request的封装
这样就完成了微信支付小程序的下单
用到的几个函数
calcSign
1 2 3 4
| function calcSign(msg) { return SignUtil.rsaSign(msg, pem, "SHA256withRSA") }
|
getTimeStamp
1 2 3 4
| function getTimeStamp() { return Math.round(+new Date / 1000) }
|
joinMessage
1 2 3 4
| function joinMessage(...args) { return args.join("\n") + "\n" }
|
getUUID
1 2 3 4 5
| var guid = require("guid") function getUUID() { return guid.create().value.replace(/-/g, "").slice(0, 30) }
|