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的回調方法,根據微信支付背景傳回的支付結果來處理不同的實際邏輯。
這樣微信小程式的支付流程就結束了。
六、總結
當時在進行開發的時候遇到了很多問題,由于是第一次開發支付相關的内容,踩了很多坑,而且微信官方給的開發文檔實在是一言難盡,無力吐槽。微信支付中還有對微信支付結果異步發送給後端的處理,以及退款操作,這兩點還有很多坑,後面會寫文章來說一下這兩個操作該如何處理。