Nodejs后端实现微信小程序支付
-
- 一、前言
- 二、微信小程序支付流程
- 三、工具类
- 四、微信支付统一下单接口解析
- 五、小程序调用微信支付
- 六、总结
一、前言
前端时间在做微信小程序后端的时候,小程序中用到了微信支付的功能,后端需要接入微信支付的接口,实现小程序的支付功能。第一次接触支付相关的内容,在此写篇文章记录一下。
二、微信小程序支付流程
首先引用微信官方的小程序支付流程图:【微信官方支付API文档】

微信官方给出的支付流程包含了微信支付需要的各部分内容,比较全面,但是根据不同的项目需求,所需要的开发流程是不同的,有些步骤根据项目是可以做适当的调整的。但是微信支付的整体套路是一样的,下面是我自己画的一个支付流程图,仅供参考:
首先小程序端将商品等信息发给后端,后端在本地生成一个订单,并将订单信息返回给小程序,小程序确认订单信息后,发起支付请求,后端收到请求后,将参数进行签名处理,然后调用微信支付的统一下单接口,将处理后的数据发送给微信支付后台,微信后台将预支付单信息返回给后端,后端进行二次签名,将处理后的数据返回给小程序,小程序根据返回的数据调起微信支付工具,用户完成支付之后,直接将数据发送给微信支付后台,微信支付后台将支付结果返回给小程序,同时微信支付后台还会通过异步的方式将支付结果发送给后端,后端将支付结果通知进行处理,便于小程序进行支付结果查询的操作。这样微信小程序的支付就结束了。
注意:要使用微信支付的功能,首先要有小程序的ID和密钥,其次需要用户的微信商户ID和密钥,这样才能成功开通微信支付功能。
三、工具类
1.数据签名算法
微信支付过程发送的数据用到了签名的加密形式,【微信官方签名算法】。
1)将需要发送的参数按照参数名ASCII码从小到大排序(字典序),在使用URL键值对的形式(key1=value1&key2=value2…)将参数拼接成字符串stringA。
参考代码:
raw = (args: any) => {
let keys: any = Object.keys(args);
keys = keys.sort();
const results: Array<string> = [];
keys.forEach((key: string) => {
results.push(`${key}=${args[key]}`)
});
return results.join("&");
}
2)在stringA的末尾拼接上Key值得到stringSignTemp字符串。
stringSignTemp = stringA + "&key=" + key;
3)将得到的字符串进行MD5运算,再将结果全部转换为大写,得到最终的sign值signValue。(这里后端使用”crypto-js”包来进行MD5运算)
cryptoJS.MD5(str).toString().toUpperCase();
完整签名方法:
paySignApp_1 = (appid, body, mch_id, nonce_str, notify_url, out_trade_no, spbill_create_ip, total_fee, trade_type, mchkey) => {
const ret: any = {
appid,
mch_id,
nonce_str,
body,
notify_url,
out_trade_no,
spbill_create_ip,
total_fee,
trade_type
};
let str: any = this.raw(ret);
const key = mchkey;
str = str + "&key=" + key;
return cryptoJS.MD5(str).toString().toUpperCase();
}
2.获取随机字符串
传递的参数中需要一个16位的随机字符串
createNonceStr = () => {
return Math.random().toString(36).substr(2, 15);
}
3.获取时间戳
传递的参数中需要一个时间戳字符串
createTimeStamp = () => {
return parseInt((new Date().getTime() / 1000).toString()) + "";
}
4.金额转换
微信支付中的参数支付金额的单位是分,参数值不能带小数,所以需要对参数进行金额处理
getMoney = (money) => {
return parseFloat(money) * 100;
}
四、微信支付统一下单接口解析
微信支付的关键点就在统一下单接口的使用。参数的详情介绍请参考【微信支付统一下单接口】
1.参数定义
小程序和商户数据配置:
private appId = "小程序ID";
private appSecret = "小程序密钥";
private mchId = "商户ID";
private mchKey = "商户密钥";
private notifyUrl = "自己后端定义的支付结果通知获取地址";
前端传递的参数配置:(注意:openid是前端通过微信登录获取的用户标识ID,是用户唯一的,这里由前端获取并发送给后端。wxpay是自己写的工具类的脚本,里面包含了签名等方法)
const orderCode = req.body.orderCode; //订单号
const money = req.body.money;
const openid = req.body.openid;
const mchId = this.mchId;
const nonceStr = this.wxpay.createNonceStr();
const timestamp = this.wxpay.createTimeStamp();
const body = "微信支付测试";
const outTradeNo = orderCode;
const totalFee = this.wxpay.getMoney(money);
const spbillCreateIp = req.connection.remoteAddress;
const notifyUrl = this.notifyUrl ;
const tradeType = "JSAPI";
const sign = this.wxpay.paySign_1(this.appId, body, mchId, nonceStr, notifyUrl, openid, outTradeNo, spbillCreateIp, totalFee, tradeType, this.mchKey);
2.将参数封装成XML数据格式 (注意:直接插入数据会出现错误,这里需要对XML数据进行转义,或者使用下面的方式传值<![CDATA[data]]>)
const formData = `
<xml>
<appid><![CDATA[${this.appId}]]></appid>
<body><![CDATA[${body}]]></body>
<mch_id><![CDATA[${mchId}]]></mch_id>
<nonce_str><![CDATA[${nonceStr}]]></nonce_str>
<notify_url><![CDATA[${notifyUrl}]]></notify_url>
<openid><![CDATA[${openid}]]></openid>
<out_trade_no><![CDATA[${outTradeNo}]]></out_trade_no>
<spbill_create_ip><![CDATA[${spbillCreateIp}]]></spbill_create_ip>
<total_fee><![CDATA[${totalFee}]]></total_fee>
<trade_type><![CDATA[${tradeType}]]></trade_type>
<sign><![CDATA[${sign}]]></sign>
</xml>
`;
3.调用统一下单接口 (注意:这里使用“request”包来发送请求。微信支付后台返回的数据是XML格式的,这里使用“xmlreader”包来读取数据内容。)
const url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
request({ url: url, method: "POST", body: formData }, (err, response, body) => {
if (!err && response.statusCode == 200) {
xmlreader.read(body.toString("utf-8"), (error, response) => {
if (null !== error) {
return;
}
if (response.xml.return_code.text() === "SUCCESS") {
const prepayId = response.xml.prepay_id.text();
const packages = "prepay_id=" + prepayId;
const signType = "MD5";
const minisign = this.wxpay.paySign_2(this.appId, nonceStr, packages, signType, timestamp, this.mchKey);
const data = {
"appId": this.appId,
"partnerId": mchId,
"prepayId": prepayId,
"nonceStr": nonceStr,
"timeStamp": timestamp,
"package": "Sign=WXPay",
"paySign": minisign
};
res.send(data);
}
else {
res.send(response.xml.return_msg.text());
}
}
}
}
微信后台统一下单接口返回数据后,对数据进行二次签名处理,将处理后的数据再返回给小程序使用。
这样微信支付后端的流程就结束了。
五、小程序调用微信支付
小程序端我没有参与开发,这里简单说一下小程序端的调用流程。小程序端调用微信支付比较简单,主要是调用wx.requestPayment()来调起微信支付工具。
1.封装数据调用后端的统一下单接口
2.根据后端返回的数据调用wx.requestPayment(),直接把数据发送给微信支付后台。
3.执行requestPayment的回调方法,根据微信支付后台返回的支付结果来处理不同的实际逻辑。
这样微信小程序的支付流程就结束了。
六、总结
当时在进行开发的时候遇到了很多问题,由于是第一次开发支付相关的内容,踩了很多坑,而且微信官方给的开发文档实在是一言难尽,无力吐槽。微信支付中还有对微信支付结果异步发送给后端的处理,以及退款操作,这两点还有很多坑,后面会写文章来说一下这两个操作该如何处理。