天天看點

Nodejs後端實作微信小程式支付

Nodejs後端實作微信小程式支付

    • 一、前言
    • 二、微信小程式支付流程
    • 三、工具類
    • 四、微信支付統一下單接口解析
    • 五、小程式調用微信支付
    • 六、總結

一、前言

前端時間在做微信小程式後端的時候,小程式中用到了微信支付的功能,後端需要接入微信支付的接口,實作小程式的支付功能。第一次接觸支付相關的内容,在此寫篇文章記錄一下。

二、微信小程式支付流程

首先引用微信官方的小程式支付流程圖:【微信官方支付API文檔】

Nodejs後端實作微信小程式支付

微信官方給出的支付流程包含了微信支付需要的各部分内容,比較全面,但是根據不同的項目需求,所需要的開發流程是不同的,有些步驟根據項目是可以做适當的調整的。但是微信支付的整體套路是一樣的,下面是我自己畫的一個支付流程圖,僅供參考:

Nodejs後端實作微信小程式支付

首先小程式端将商品等資訊發給後端,後端在本地生成一個訂單,并将訂單資訊傳回給小程式,小程式确認訂單資訊後,發起支付請求,後端收到請求後,将參數進行簽名處理,然後調用微信支付的統一下單接口,将處理後的資料發送給微信支付背景,微信背景将預支付單資訊傳回給後端,後端進行二次簽名,将處理後的資料傳回給小程式,小程式根據傳回的資料調起微信支付工具,使用者完成支付之後,直接将資料發送給微信支付背景,微信支付背景将支付結果傳回給小程式,同時微信支付背景還會通過異步的方式将支付結果發送給後端,後端将支付結果通知進行處理,便于小程式進行支付結果查詢的操作。這樣微信小程式的支付就結束了。

注意:要使用微信支付的功能,首先要有小程式的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的回調方法,根據微信支付背景傳回的支付結果來處理不同的實際邏輯。

這樣微信小程式的支付流程就結束了。

六、總結

當時在進行開發的時候遇到了很多問題,由于是第一次開發支付相關的内容,踩了很多坑,而且微信官方給的開發文檔實在是一言難盡,無力吐槽。微信支付中還有對微信支付結果異步發送給後端的處理,以及退款操作,這兩點還有很多坑,後面會寫文章來說一下這兩個操作該如何處理。

繼續閱讀