微信小程序+ node koa2框架實現(xiàn)微信小程序支付

導語

小程序支持微信支付,前提條件是小程序賬號的主體不能為個人,并且已經(jīng)開通了商戶號,商戶在完成簽約后,需要確認當前商戶號同appid的綁定關(guān)系,方可使用。

在前置準備做好了之后,我們先看下小程序是如何調(diào)起微信支付的,官方文檔

wx.requestPayment(Object object)

發(fā)起微信支付。了解更多信息,請查看微信支付接口文檔

參數(shù)

Object object

屬性 類型 默認值 必填 說明
timeStamp string 時間戳,從 1970 年 1 月 1 日 00:00:00 至今的秒數(shù),即當前的時間
nonceStr string 隨機字符串,長度為32個字符以下
package string 統(tǒng)一下單接口返回的 prepay_id 參數(shù)值,提交格式如:prepay_id=***
signType string MD5 簽名算法
paySign string 簽名,具體簽名方案參見 小程序支付接口文檔
success function 接口調(diào)用成功的回調(diào)函數(shù)
fail function 接口調(diào)用失敗的回調(diào)函數(shù)
complete function 接口調(diào)用結(jié)束的回調(diào)函數(shù)(調(diào)用成功、失敗都會執(zhí)行)

我們可以看到調(diào)起小程序支付只需要傳入五個參數(shù),便可調(diào)起支付,而這些參數(shù)需要從服務端請求獲取,所以支付主要的工作都在服務端。


小程序支付交互圖

上圖是官方給出的交互圖,簡單的可以概況為幾個步驟

1.小程序觸發(fā)生成訂單事件,攜帶用戶標識(在項目中我使用的是token)到服務端
2.服務端調(diào)用微信平臺支付統(tǒng)一下單api,微信后臺會返回預付單信息(prepay_id)
3.將返回的預付單信息進行組合以及再次簽名,將5個參數(shù)和sign返回給微信小程序
4.小程序通過服務端返回的參數(shù),即可直接調(diào)起微信支付,支付的結(jié)果會直接返回給小程序
5.支付成功后,微信后臺會像服務器推送支付結(jié)果,服務器修改訂單狀態(tài)
以上五個步驟就是微信小程序的完整支付流程,下面開始通過node來實現(xiàn)小程序支付的服務端代碼編寫


一、調(diào)用微信支付統(tǒng)一下單API

我們直接上代碼,具體可以去看微信的文檔

//生成隨機字符串的npm包
const stringRandom = require('string-random')
//node加密相關(guān)
const crypto = require('crypto');
//解析和生成xml文件的npm包
const Xml2js = require('xml2js');
/*方法接收2個參數(shù),out_trade_no(服務器生成的訂單id,需小于32位)
openid(當支付類型是JSAPI時需要傳入,用戶的openid)
total_fee(訂單金額,單位為分,我們先設置默認值為1,記得改回來!)
*/
 unifiedorder(out_trade_no,openid,total_fee=1) {
        // 先將需要發(fā)送的參數(shù)創(chuàng)建好,然后根據(jù)參數(shù)名ASCII碼從小到大排序(字典序)
        let order = {
            appid: config.getItem('wx.appId'),
            body: "一乎小程序-發(fā)布任務支付賞金",
            mch_id:config.getItem('wx.merchantId'),
            //    生成隨機字符串,長度32位以內(nèi),我們使用stringRandom庫生成16位隨機數(shù)
            nonce_str: stringRandom(16),
            notify_url: config.getItem('siteDomain')+'/v1/task/pay/result',
            openid,
            out_trade_no:out_trade_no,
            spbill_create_ip: config.getItem('spbill_create_ip'),
            total_fee,
            trade_type: "JSAPI",
        }
        //    將參數(shù)對象轉(zhuǎn)為key=value模式的字符串,用&分隔
        let stringA = obj2String(order)
        //    將生成的字符串末尾拼接上API密鑰(key設置路徑:微信商戶平臺(pay.weixin.qq.com)-->賬戶設置-->API安全-->密鑰設置)
        let stringSignTemp = stringA + `&key=${config.getItem('wx.merchantKey')}`
        //    通過HMAC-SHA256或者MD5生成sign簽名,這里我們使用md5,然后將簽名加入?yún)?shù)對象內(nèi)
        let md5 = crypto.createHash('md5')
        md5.update(stringSignTemp);
        order.sign = md5.digest('hex').toUpperCase()
        //    將參數(shù)對象專為xml格式
        const builder = new Xml2js .Builder();
        const xml = builder.buildObject(order);
        //    發(fā)送請求
        /axios.post("https://api.mch.weixin.qq.com/pay/unifiedorder",xml)
        //    由于微信服務器返回的data格式是xml,所以這里我們需要轉(zhuǎn)成object
        const parser = new Xml2js.Parser();
        const xmlObj =await parser.parseStringPromise(result.data)
        if(xmlObj.xml.return_code[0]==='FAIL'){
            throw new Failed({
                msg:`支付失敗,${xmlObj.xml.return_msg[0]}`,
            })
        }
        if(xmlObj.xml.result_code&&xmlObj.xml.result_code[0]==='SUCCESS'){
            let payData= {
                appId:xmlObj.xml.appid[0],
                nonceStr:xmlObj.xml.nonce_str[0],
                package:`prepay_id=${xmlObj.xml.prepay_id[0]}`,
                signType:"MD5",
                timeStamp:new Date().getTime().toString(),
                key:config.getItem('wx.merchantKey')
            }
            const StringPay = obj2String(payData)
            let payMd5 = crypto.createHash('md5')
            payMd5.update(StringPay);
            let paySign= payMd5.digest('hex').toUpperCase();
            payData.paySign = paySign;
            delete payData.key;
            //前面已經(jīng)將需要的字段拼接好,將對象從方法返回,服務端可以將對象直接傳回給小程序客戶端
            return payData
        }else{
            throw new Failed({
                msg:`支付失敗,${xmlObj.xml.err_code[0]}:${xmlObj.xml.err_code_des[0]}`,
            })
        }
        
    }

總結(jié)下遇到的幾點問題

  • 生成簽名的時候一定要將object的屬性按照ASCII碼從小到大排序
  • 返回給小程序的時間戳timeStamp,需為毫秒數(shù),并且要是字符串格式
  • 可能會遇到簽名錯誤的報錯,這時候要耐心的去看代碼哪里出了問題,商戶平臺的key是手動填寫的32位隨機生成字符串

現(xiàn)在統(tǒng)一下單已經(jīng)調(diào)用成功,我們也拿到了小程序調(diào)起支付的幾個參數(shù),我們先去小程序測試下能不能支付

二、小程序調(diào)起支付

//觸發(fā)生成訂單事件
function (){
 //。。。省略部分代碼

/*
res.data的格式如下
  res.data={
    appId: "wx14dcf5e8a4179d42"
  nonceStr: "TuXBgZrQ1JNWuLiC"
  package: "prepay_id=wx***********"
  paySign: "***********"
  signType: "MD5"
  timeStamp: "1582709049872"
}
*/  
orderModel.createOrder()
  .then(res=>{
      if(res.error_code==0){
        wx.requestPayment({
          ...res.data,
          success:re=>{
            //成功支付后執(zhí)行
            console.log(re)
          },
          fail:(err)=>{
            //取消支付或者支付失敗后執(zhí)行
            console.log(err)
          }
        })
      }
    })
}
 

沒意外的話我們已經(jīng)成功調(diào)起微信支付了,如果有錯誤微信的報錯還是挺清晰的,我們可以根據(jù)穩(wěn)定一步一步去修改
接下來別忘了去接收支付成功后微信返回的支付結(jié)果

三、接收微信推送支付通知

這里接收微信推送的url是第一步調(diào)用微信支付統(tǒng)一下單API的notify_url字段,需要能訪問
根據(jù)文檔可知,微信服務器返回的數(shù)據(jù)是xml格式的,而koa-bodyparser無法解析xml,我們需要引入koa-xml-body中間件來解析xml

這里需要重點說明下,koa-xml-body官方文檔上說明和koa-bodyparser是可以兼容使用的,但是引入中間件的時候,得先引用koa-xml-body再引入koa-bodyparser

const KoaBodyParser = require('koa-bodyparser');
const KoaXmlBody = require('koa-xml-body');
//注意先引入koa-xml-body
app.use(KoaXmlBody());
app.use(KoaBodyParser());

引入koa-xml-body就可以在ctx.request.body.xml里獲得解析好的xml

  router.post('/pay/result',async (ctx)=>{
      const xml = ctx.request.body.xml;
      const successXml= ‘<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>’;

      // 這里進行簽名和校驗返回xml數(shù)據(jù)的真實性,以防惡意調(diào)用接口
      //校驗過程省略...

      if (returnCode === 'SUCCESS' && xml.result_code[0] === 'SUCCESS') {
        // 根據(jù)自己的業(yè)務需求支付成功后的操作
        //......
        //返回xml告訴微信已經(jīng)收到,并且不會再重新調(diào)用此接口
           ctx.body = successXml
  }

在收到微信推送支付的成功通知后,便可以根據(jù)業(yè)務需求去修改自己訂單的狀態(tài),一個微信支付完整流程也完成了

有什么不對的地方或者疑問,歡迎在評論指出

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容