導(dǎo)語
小程序支持微信支付,前提條件是小程序賬號的主體不能為個人驾窟,并且已經(jīng)開通了商戶號,商戶在完成簽約后认轨,需要確認(rèn)當(dāng)前商戶號同appid的綁定關(guān)系纫普,方可使用。
在前置準(zhǔn)備做好了之后好渠,我們先看下小程序是如何調(diào)起微信支付的昨稼,官方文檔
wx.requestPayment(Object object)
發(fā)起微信支付。了解更多信息拳锚,請查看微信支付接口文檔
參數(shù)
Object object
屬性 | 類型 | 默認(rèn)值 | 必填 | 說明 |
---|---|---|---|---|
timeStamp | string | 是 | 時間戳假栓,從 1970 年 1 月 1 日 00:00:00 至今的秒數(shù),即當(dāng)前的時間 | |
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ù)需要從服務(wù)端請求獲取兔魂,所以支付主要的工作都在服務(wù)端烤芦。
上圖是官方給出的交互圖,簡單的可以概況為幾個步驟
1.小程序觸發(fā)生成訂單事件析校,攜帶用戶標(biāo)識(在項目中我使用的是token)到服務(wù)端
2.服務(wù)端調(diào)用微信平臺支付統(tǒng)一下單api,微信后臺會返回預(yù)付單信息(prepay_id)
3.將返回的預(yù)付單信息進行組合以及再次簽名构罗,將5個參數(shù)和sign返回給微信小程序
4.小程序通過服務(wù)端返回的參數(shù),即可直接調(diào)起微信支付智玻,支付的結(jié)果會直接返回給小程序
5.支付成功后遂唧,微信后臺會像服務(wù)器推送支付結(jié)果,服務(wù)器修改訂單狀態(tài)
以上五個步驟就是微信小程序的完整支付流程,下面開始通過node來實現(xiàn)小程序支付的服務(wù)端代碼編寫
一吊奢、調(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(服務(wù)器生成的訂單id,需小于32位)
openid(當(dāng)支付類型是JSAPI時需要傳入召边,用戶的openid)
total_fee(訂單金額铺呵,單位為分,我們先設(shè)置默認(rèn)值為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ā)布任務(wù)支付賞金",
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設(shè)置路徑:微信商戶平臺(pay.weixin.qq.com)-->賬戶設(shè)置-->API安全-->密鑰設(shè)置)
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)
// 由于微信服務(wù)器返回的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)將需要的字段拼接好邻悬,將對象從方法返回,服務(wù)端可以將對象直接傳回給小程序客戶端
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ù)文檔可知成翩,微信服務(wù)器返回的數(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è)務(wù)需求支付成功后的操作
//......
//返回xml告訴微信已經(jīng)收到,并且不會再重新調(diào)用此接口
ctx.body = successXml
}
在收到微信推送支付的成功通知后棒呛,便可以根據(jù)業(yè)務(wù)需求去修改自己訂單的狀態(tài)聂示,一個微信支付完整流程也完成了
有什么不對的地方或者疑問,歡迎在評論指出