nodejs 獲取微信小程序支付的簽名(paySign)

支付的大致流程如下:

  1. 小程序端用 wx.request 向服務(wù)器請(qǐng)求支付參數(shù)(相當(dāng)于ajax)
  2. 服務(wù)器根據(jù)客戶(hù)端的 sessionId , 找到 它的 openId, 將它和商家自己的 應(yīng)用id, 商戶(hù)號(hào), 支付密鑰等 和商品的一系列(包括價(jià)格/商品描述)這些信息用規(guī)定的方式拼接起來(lái), 然后加密, 去請(qǐng)求微信的服務(wù)器
  3. 微信的服務(wù)器向商家服務(wù)器返回這次交易需要的數(shù)據(jù)(prepay_id等)
  4. 商家服務(wù)器處理上述數(shù)據(jù), 并且和其他需要用到的信息一起返回給用戶(hù)端(小程序前端)使用
  5. 用戶(hù)端(小程序前端)接收到數(shù)據(jù), 使用這些信息調(diào)用 wx.requestPayment 接口
  6. 微信服務(wù)器主動(dòng)向商家的服務(wù)器發(fā)送本次支付的信息(是否成功等), 商戶(hù)服務(wù)器根據(jù)微信發(fā)來(lái)的數(shù)據(jù)判斷用戶(hù)是否完成支付

官方的文檔在這里
https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1

話(huà)不多說(shuō), 代碼如下

1. 準(zhǔn)備好一些需要用的常量

const axios = require('axios')
const md5 = require('blueimp-md5')
const xml2js = require('xml2js')
const xmlParser = new xml2js.Parser()

const appId = 'your wx appId'
const appSecret = 'your wx app secret'

// 商戶(hù)號(hào)
const mchId = 'mch id'
// 支付api 的 key
const PAY_API_KEY = 'pay api key'
// 一個(gè)方便的 log 方法
const log = console.log.bind(console)

2. 準(zhǔn)備一些工具方法(函數(shù))

由于獲取支付簽名的代碼比較多, 這里先一股腦把他們封裝到函數(shù)里面
后面只需要調(diào)用這些函數(shù)就可以的

// 預(yù)定義的一些工具函數(shù)
function getNonceStr() {
    var text = ""
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
    for (var i = 0; i < 16; i++) {
        text += possible.charAt(Math.floor(Math.random() * possible.length))
    }
    return text
}
function getPaySign(appId, timeStamp, nonceStr, package) {
    var stringA = 'appId=' + appId +
        '&nonceStr=' + nonceStr +
        '&package=' + package +
        '&signType=MD5' +
        '&timeStamp=' + timeStamp

    var stringSignTemp = stringA + '&key=' + PAY_API_KEY
    var sign = md5(stringSignTemp).toUpperCase()
    return sign
}
function getTradeId(attach) {
    var date = new Date().getTime().toString()
    var text = ""
    var possible = "0123456789"
    for (var i = 0; i < 5; i++) {
        text += possible.charAt(Math.floor(Math.random() * possible.length))
    }
    var tradeId = 'ty_' + attach + '_' + date + text
    return tradeId
}

function getPrePaySign(appId, attach, productIntro, mchId, nonceStr, notifyUrl, openId, tradeId, ip, price) {
    var stringA = 'appid=' + appId +
        '&attach=' + attach +
        '&body=' + productIntro +
        '&mch_id=' + mchId +
        '&nonce_str=' + nonceStr +
        '&notify_url=' + notifyUrl +
        '&openid=' + openId +
        '&out_trade_no=' + tradeId +
        '&spbill_create_ip=' + ip +
        '&total_fee=' + price +
        '&trade_type=JSAPI'
    var stringSignTemp = stringA + '&key=' + PAY_API_KEY
    var sign = md5(stringSignTemp).toUpperCase()
    return sign
}

function wxSendData(appId, attach, productIntro, mchId, nonceStr, notifyUrl, openId, tradeId, ip, price, sign) {
    const sendData = '<xml>' +
        '<appid>' + appId + '</appid>' +
        '<attach>' + attach + '</attach>' +
        '<body>' + productIntro + '</body>' +
        '<mch_id>' + mchId + '</mch_id>' +
        '<nonce_str>' + nonceStr + '</nonce_str>' +
        '<notify_url>' + notifyUrl + '</notify_url>' +
        '<openid>' + openId + '</openid>' +
        '<out_trade_no>' + tradeId + '</out_trade_no>' +
        '<spbill_create_ip>' + ip + '</spbill_create_ip>' +
        '<total_fee>' + price + '</total_fee>' +
        '<trade_type>JSAPI</trade_type>' +
        '<sign>' + sign + '</sign>' +
        '</xml>'
    return sendData
}

function getPayParams(prepayId, tradeId) {
    const nonceStr = getNonceStr()
    const timeStamp = new Date().getTime().toString()
    const package = 'prepay_id=' + prepayId
    const paySign = getPaySign(appId, timeStamp, nonceStr, package)
    // 前端需要的所有數(shù)據(jù), 都從這里返回過(guò)去
    const payParamsObj = {
        nonceStr: nonceStr,
        timeStamp: timeStamp,
        package: package,
        paySign: paySign,
        signType: 'MD5',
        tradeId: tradeId,
    }
    return payParamsObj
}

3. 這時(shí), 前端發(fā)請(qǐng)求過(guò)來(lái)了, 需要給他返回支付簽名

如果是在 express 下, 下面的代碼應(yīng)該是在一個(gè)長(zhǎng)成這樣的方法里面:

app.get('/paysign', (req, res) => {
  // 代碼應(yīng)該在這里
})
// attach 是一個(gè)任意的字符串, 會(huì)原樣返回, 可以用作一個(gè)標(biāo)記
const attach = 'GJS-ORG'
// 一個(gè)隨機(jī)字符串
const nonceStr = getNonceStr()
// 用戶(hù)的 openId
const openId = 'user openId'
// 生成商家內(nèi)部自定義的訂單號(hào), 商家內(nèi)部的系統(tǒng)用的, 理論上只要不和其他訂單重復(fù), 使用任意的字符串都是可以的
const tradeId = getTradeId(attach)
// 生成簽名
const sign = getPrePaySign(appId, attach, productIntro, mchId, nonceStr, notifyUrl, openId, tradeId, ip, price)
// 這里是在 express 獲取用戶(hù)的 ip, 因?yàn)槭褂昧?nginx 的反向代理, 所以這樣獲取
let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
ip = ip.match(/\d+\.\d+\.\d+\.\d+/)[0]
將微信需要的數(shù)據(jù)拼成 xml 發(fā)送出去
const sendData = wxSendData(appId, attach, productIntro, mchId, nonceStr, notifyUrl, openId, tradeId, ip, price, sign)

// 使用 axios 發(fā)送數(shù)據(jù)帶微信支付服務(wù)器, 沒(méi)錯(cuò), 后端也可以使用 axios
axios.post('https://api.mch.weixin.qq.com/pay/unifiedorder', sendData).then(wxResponse => {
       // 微信返回的數(shù)據(jù)也是 xml, 使用 xmlParser 將它轉(zhuǎn)換成 js 的對(duì)象
        xmlParser.parseString(wxResponse.data, (err, success) => {
            if (err) {
                log('parser xml error ', err)
            } else {
                if (success.xml.return_code[0] === 'SUCCESS') {
                    const prepayId = success.xml.prepay_id[0]
                    const payParamsObj = getPayParams(prepayId, tradeId)
                    // 返回給前端, 這里是 express 的寫(xiě)法
                    res.json(payParamsObj)
                } else {
                    // 錯(cuò)誤處理
                    if (err) {
                        log('axios post error', err)
                        res.sendStatus(502)
                    } else if (success.xml.return_code[0] !== 'SUCCESS') {
                        res.sendStatus(403)
                    }
                }
            }
        })
    }).catch(err => {
        log('post wx err', err)
    })

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末本刽,一起剝皮案震驚了整個(gè)濱河市鲸湃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌子寓,老刑警劉巖暗挑,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異斜友,居然都是意外死亡炸裆,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)鲜屏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)烹看,“玉大人国拇,你說(shuō)我怎么就攤上這事」呤猓” “怎么了酱吝?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)土思。 經(jīng)常有香客問(wèn)我务热,道長(zhǎng),這世上最難降的妖魔是什么己儒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任崎岂,我火速辦了婚禮,結(jié)果婚禮上闪湾,老公的妹妹穿的比我還像新娘该镣。我一直安慰自己,他們只是感情好响谓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布损合。 她就那樣靜靜地躺著,像睡著了一般娘纷。 火紅的嫁衣襯著肌膚如雪嫁审。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天赖晶,我揣著相機(jī)與錄音律适,去河邊找鬼。 笑死遏插,一個(gè)胖子當(dāng)著我的面吹牛捂贿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胳嘲,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼厂僧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了了牛?” 一聲冷哼從身側(cè)響起颜屠,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鹰祸,沒(méi)想到半個(gè)月后甫窟,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蛙婴,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年粗井,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浇衬,死狀恐怖呆瞻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情径玖,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布颤介,位于F島的核電站梳星,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏滚朵。R本人自食惡果不足惜冤灾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辕近。 院中可真熱鬧韵吨,春花似錦、人聲如沸移宅。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)漏峰。三九已至糠悼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浅乔,已是汗流浹背倔喂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留靖苇,地道東北人席噩。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像贤壁,于是被迫代替她去往敵國(guó)和親悼枢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

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

  • 今天看到朋友圈小伙伴說(shuō)双揪,加班來(lái)的太快就像龍卷風(fēng),估計(jì)她看到加班時(shí)候的心情是這樣的包帚。 我也想談下關(guān)于加班的那點(diǎn)事兒渔期。...
    王二公子閱讀 435評(píng)論 1 2
  • 痛苦有兩種,一種是肉體上的,一種是精神上的疯趟。 肉體上的痛苦又分兩種拘哨,一種是疾病原因痛苦,一種是非疾病原因的信峻,如工作...
    乾坤屯蒙閱讀 426評(píng)論 0 1
  • 你是否遺忘了窗外飄來(lái)的濃厚花香你是否擦去了我遙寄信件中的情話(huà)你是否拋棄了一起走過(guò)的那個(gè)炎夏 我希望是否倦青,你毫不猶...
    不二檸檬閱讀 113評(píng)論 3 2
  • 看了新一期的奇葩說(shuō)感慨萬(wàn)千,終于討論這個(gè)問(wèn)題盹舞,終于提到中國(guó)人的手機(jī)病了产镐,對(duì)我來(lái)說(shuō),我不敢說(shuō)時(shí)保聯(lián)是不是一種暴政踢步,因...
    程水瓶閱讀 324評(píng)論 0 0
  • 陸居非屋水居非舟 在山泉清初山泉濁 月駕軒
    塵末末閱讀 199評(píng)論 0 0