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的回调方法,根据微信支付后台返回的支付结果来处理不同的实际逻辑。
这样微信小程序的支付流程就结束了。

六、总结

当时在进行开发的时候遇到了很多问题,由于是第一次开发支付相关的内容,踩了很多坑,而且微信官方给的开发文档实在是一言难尽,无力吐槽。微信支付中还有对微信支付结果异步发送给后端的处理,以及退款操作,这两点还有很多坑,后面会写文章来说一下这两个操作该如何处理。

猜你喜欢

转载自blog.csdn.net/m0_37857819/article/details/106442901