nodejs 微信支付 (koa 框架)

nodejs實(shí)現(xiàn)微信支付,koa框架的接入流程,此文檔通過測試,在小程序/微信公眾號(hào)的后端代碼中使用, 已經(jīng)用于生產(chǎn)環(huán)境, 如有需要,請放心復(fù)制.

根據(jù)官方微信支付的文檔
和流程圖

image

支付過程可以分為后端流程前端流程

后端流程

后端分為2步

  1. 根據(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&notify_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);
}

  1. 通過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步 官方文檔

  1. 向后臺(tái)提交支付訂單的請求
  2. 拿到后臺(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步排查,定能解決問題

  1. 官方調(diào)試界面,輸入自己的參數(shù),看看最終的簽名是否和自己生成的一致
  1. 如果簽名沒錯(cuò),那肯定是商戶信息的問題了(本人此處被坑了很久,老板給了我mch_idkey都是正確的,結(jié)果未安裝操作證書,導(dǎo)致我調(diào)試了很久找不到原因,心中一萬匹草泥馬)

檢查商戶信息,也就是商戶號(hào)mch_id和商戶的key(這里需要注意key,是申請微信支付成功后,騰訊發(fā)給申請者郵件里面的秘鑰,要想此秘鑰生效還需要安裝操作證書)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肛鹏,一起剝皮案震驚了整個(gè)濱河市现使,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡挚歧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門吁峻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滑负,“玉大人,你說我怎么就攤上這事用含“剑” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵啄骇,是天一觀的道長痴鳄。 經(jīng)常有香客問我,道長缸夹,這世上最難降的妖魔是什么痪寻? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮虽惭,結(jié)果婚禮上橡类,老公的妹妹穿的比我還像新娘。我一直安慰自己芽唇,他們只是感情好顾画,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著匆笤,像睡著了一般研侣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上炮捧,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天庶诡,我揣著相機(jī)與錄音,去河邊找鬼咆课。 笑死末誓,一個(gè)胖子當(dāng)著我的面吹牛璧函,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播基显,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼善炫!你這毒婦竟也來了撩幽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤箩艺,失蹤者是張志新(化名)和其女友劉穎窜醉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體艺谆,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡榨惰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了静汤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琅催。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖虫给,靈堂內(nèi)的尸體忽然破棺而出藤抡,到底是詐尸還是另有隱情,我是刑警寧澤抹估,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布缠黍,位于F島的核電站,受9級(jí)特大地震影響药蜻,放射性物質(zhì)發(fā)生泄漏瓷式。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一语泽、第九天 我趴在偏房一處隱蔽的房頂上張望贸典。 院中可真熱鬧,春花似錦踱卵、人聲如沸瓤漏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蔬充。三九已至,卻和暖如春班利,著一層夾襖步出監(jiān)牢的瞬間饥漫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工罗标, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留庸队,地道東北人积蜻。 一個(gè)月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像彻消,于是被迫代替她去往敵國和親竿拆。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容