nodejs實(shí)現(xiàn)微信支付,koa框架的接入流程,此文檔通過測試,在小程序/微信公眾號(hào)的后端代碼中使用, 已經(jīng)用于生產(chǎn)環(huán)境, 如有需要,請放心復(fù)制.
根據(jù)官方微信支付的文檔
和流程圖
后端流程
后端分為2步
- 根據(jù)用戶的下單請求調(diào)用微信統(tǒng)一下單api拿到返回的關(guān)鍵數(shù)據(jù)
prepay_id
export const prepay = async ({openid,orderId,desc,totalPrice,spbill_create_ip})=> {
// 通過查閱文檔,調(diào)用統(tǒng)一下單有10個(gè)參數(shù)是必須的
let obj = {
appid,
mch_id,
nonce_str: get_nonce_str(32),
body: desc,
out_trade_no: orderId,
total_fee: parseInt(totalPrice * 100),
spbill_create_ip,
notify_url,
trade_type:'JSAPI',
openid
}
// js的默認(rèn)排序即為ASCII的從小到大進(jìn)行排序(字典排序)
let arr = Object.keys(obj).sort().map(item => {
return `${item}=${obj[item]}`;
});
// 這里拼接簽名字符串的時(shí)候一定要注意: 商戶的key是要單獨(dú)拿出來拼在最后面的
let str = arr.join('&') + '&key=' + key;
// appid=wxf8600b***b5dfb&body=德勝村&mch_id=1490909372&nonce_str=plfbp2bhr0id1z6aktmndfot94hkewcv¬ify_url=https://server.***.cn/wechat/pay_notify&openid=oFm4h0WvnQWB4ocFmdPzsWywlE8c&out_trade_no=20150806125346&spbill_create_ip=127.0.0.1&total_fee=56600&trade_type=JSAPI&key=Lzy1234567890111***5161718192
obj.sign = getSign(str);
let res;
try{
// 調(diào)用微信統(tǒng)一下單接口拿到 prepay_id
res = await wechatPay(obj);
let {prepay_id} = res;
if(prepay_id){
res = getClientPayConfig(prepay_id)
}
// console.log(res);
}catch(e){
res = e;
console.log(e);
}
return res;
}
/**
* 統(tǒng)一下單 prepay_url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
* @param {Object} obj 調(diào)用統(tǒng)一下單的必須參數(shù)
*/
const wechatPay = (obj)=>{
let xml = json2xml(obj);
console.log(xml)
return new Promise((resolve,reject)=>{
// 這里用了reques庫,不熟悉的同學(xué)可以看看相關(guān)文檔 https://github.com/request/request
// 總之就是向微信的統(tǒng)一下單接口提交一個(gè)xml
request({method:'POST',url: prepay_url,body: xml},(err,res, body)=>{
if(err){
reject(err);
}else{
//如果成功即可得到微信返回參數(shù)
console.log(body);
let obj = parseXml(body).xml;
resolve(obj);
}
});
});
}
/**
* 對指定字符串進(jìn)行md5加密
* @param {String} str
*/
const getSign = (str)=>{
console.log(str)
let hash = crypto.createHash('md5').update(str,'utf8');
return hash.digest('hex').toUpperCase();
}
/**
* 轉(zhuǎn)化xml用了xml2js庫
https://github.com/Leonidas-from-XIV/node-xml2js
* @param {Object} obj
*/
const json2xml = (obj)=>{
let builder = new xml2js.Builder({
headless:true,
allowSurrogateChars: true,
rootName:'xml',
cdata:true
});
var xml = builder.buildObject(obj);
return xml;
}
const parseXml = (xml)=>{
let {parseString} = xml2js;
let res;
parseString(xml, {
trim: true,
explicitArray: false
}, function (err, result) {
res = result;
});
return res;
}
/**
* 生成指定長度的隨機(jī)數(shù)
* @param {*int} len
*/
const get_nonce_str = (len)=>{
let str = '';
while(str.length < len){
str += Math.random().toString(36).slice(2);
}
return str.slice(-len);
}
- 通過
prepay_id
生成前端調(diào)啟微信支付界面的必要參數(shù)
官方文檔
/**
* 生成前端調(diào)啟支付界面的必要參數(shù)
* @param {String} prepay_id
*/
const getClientPayConfig = (prepay_id)=>{
let obj = {
appId: appid,
timeStamp: String(Math.floor(Date.now()/1000)),,
nonceStr: get_nonce_str(32),
package: 'prepay_id=' + prepay_id,
signType: 'MD5'
}
let arr = Object.keys(obj).sort().map(item => {
return `${item}=${obj[item]}`;
});
let str = arr.join('&') + '&key=' + key;
obj.paySign = getSign(str);
return obj;
}
前端流程
前端分為2步 官方文檔
- 向后臺(tái)提交支付訂單的請求
- 拿到后臺(tái)返回參數(shù), 調(diào)啟支付頁面
$api({
method:'POST',
url:'/order',
data: obj,
success:(data)=>{
console.log(data);
/**
* {
appId:"wxf860****03b5dfb"
nonceStr:"o5any3uj14tyfjr8wa249n5s0nnp9rkl"
package:"prepay_id=wx20171104151803201b17a3100900948881"
paySign:"D2632F71E4CB7E9E18D329460FDF5EB0"
signType:"MD5"
timeStamp:"1509779883"
}
*/
let obj = Object.assign({
'success':function(res){
wx.showModal({
title: '提示',
content: '支付成功',
showCancel: false
});
},
'fail':function(res){
wx.showModal({
title: '提示',
content: '取消支付',
showCancel: false
});
}
},data)
//調(diào)用小程序支付api,若為網(wǎng)頁支付,查看相應(yīng)文檔即可. (若是spa網(wǎng)頁支付,有一個(gè)支付目錄的坑.最粗暴的方式:刷新進(jìn)入支付頁面)
wx.requestPayment(obj)
}
})
簽名錯(cuò)誤
根據(jù)經(jīng)驗(yàn) 簽名錯(cuò)誤是xml的加密出錯(cuò)了
這里貼出一個(gè)提交統(tǒng)一下單的原始xml
這里說明一下: 經(jīng)過親測 spbill_create_ip
, notify_url
這兩個(gè)參數(shù)即使是寫死的也不是導(dǎo)致簽名錯(cuò)誤
的原因
<xml>
<appid>wxf8600b48303b5dfb</appid>
<body>德勝村</body>
<mch_id>1490909372</mch_id>
<nonce_str>t7z9yb0wa8e5zhcwaw4ovjlrzj39t2xh</nonce_str>
<notify_url>https://server.**.cn/wechat/pay_notify</notify_url>
<openid>oFm4h0WvnQWB4ocFmdPzsWywlE8c</openid>
<out_trade_no>20150806125346</out_trade_no>
<spbill_create_ip>127.0.0.1</spbill_create_ip>
<total_fee>56600</total_fee>
<trade_type>JSAPI</trade_type>
<sign>39FD69074F0B184D10CC5E826914785A</sign>
</xml>
<xml>
<return_code><![CDATA[FAIL]]></return_code>
<return_msg><![CDATA[簽名錯(cuò)誤]]></return_msg>
</xml>
遇到簽名錯(cuò)誤,不要著急,進(jìn)行以下2步排查,定能解決問題
- 到官方調(diào)試界面,輸入自己的參數(shù),看看最終的簽名是否和自己生成的一致
- 如果簽名沒錯(cuò),那肯定是商戶信息的問題了(本人此處被坑了很久,老板給了我mch_id和key都是正確的,結(jié)果未安裝操作證書,導(dǎo)致我調(diào)試了很久找不到原因,心中一萬匹草泥馬)
檢查商戶信息,也就是商戶號(hào)mch_id和商戶的key(這里需要注意key
,是申請微信支付成功后,騰訊發(fā)給申請者郵件里面的秘鑰,要想此秘鑰生效還需要安裝操作證書)