一喻犁、開發(fā)微信支付功能一定要架設(shè)服務(wù)器嗎?
2019年的最后一天何缓,舍得叔叔沉浸在探索的興奮中肢础,驗(yàn)證了微信小程序云開發(fā)也能優(yōu)雅實(shí)現(xiàn)微信支付!小程序的目標(biāo)是建立一個(gè)“serverless”環(huán)境碌廓,不用自行架設(shè)服務(wù)器传轰,而完全通過小程序、云函數(shù)谷婆、云存儲慨蛙、云數(shù)據(jù)庫來實(shí)現(xiàn)整個(gè)應(yīng)用功能辽聊。理念很有吸引力,就是那句話:一個(gè)人也要像一支隊(duì)伍期贫!
但是跟匆,云開發(fā)剛剛推出一年多,很多地方不夠完美通砍,方向是“去服務(wù)器”玛臂,但很多地方離開服務(wù)器就寸步難行。比如封孙,要想在小程序里面實(shí)現(xiàn)微信支付功能迹冤,連官方文檔都說開發(fā)者必須建立自己的服務(wù)器。
二敛瓷、云函數(shù)代替商戶系統(tǒng)實(shí)現(xiàn)微信支付的可行性
舍得叔叔正在開發(fā)微信租賃店叁巨,想既方便客戶使用,又方便對設(shè)備進(jìn)行管理呐籽,需要微信支付功能锋勺。但又不想為了一個(gè)支付功能跑去租服務(wù)器什么的,后期運(yùn)維也費(fèi)時(shí)費(fèi)力狡蝶,于是研究探索了一番庶橱,終于取得成功!不用架設(shè)服務(wù)器贪惹,而僅僅利用一個(gè)node.js云函數(shù)苏章,就可以實(shí)現(xiàn)微信支付的支付、退款奏瞬、查詢等全部功能枫绅。舍得叔叔把關(guān)鍵的策略和步驟記錄下來,供自己未來查看硼端,也分享給小伙伴們并淋,少走彎路珍昨。
三、微信小程序?qū)崿F(xiàn)微信支付的原理
先說微信小程序里面要實(shí)現(xiàn)微信支付功能镣典,開發(fā)者就必須同時(shí)具備2個(gè)前提條件:1、必須先開通微信支付平臺賬戶兄春,目前只有企業(yè)法人經(jīng)過認(rèn)證才能開通澎剥;2赶舆、小程序要跟微信支付平臺賬戶綁定才行趾唱。具體步驟如何,這里不累述蜻懦,官方文檔寫得很清楚。
微信支付業(yè)務(wù)是財(cái)付通公司的夕晓,這是一家人民銀行批準(zhǔn)的三方支付公司;而微信是騰訊公司的蒸辆。兩個(gè)業(yè)務(wù)主體不一樣躬贡。微信支付雖說發(fā)跡于微信,但微信支付是要讓用戶無論通過web拂玻、app、微信公眾號魄懂、還是小程序闯第,都能獲得相同的支付體驗(yàn),還要有極高的安全性填帽,符合監(jiān)管要求咙好,因此業(yè)務(wù)流程比較復(fù)雜。
這里是小程序微信支付的開發(fā)文檔:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1哀蘑。微信支付官方提供的小程序?qū)崿F(xiàn)微信支付的流程示意圖如下葵第,這只是支付功能的,發(fā)生在客戶點(diǎn)擊支付到提示支付成功期間缀台,各角色應(yīng)該做的全部工作√鸥可以看到,支付過程中辩涝,微信小程序與微信支付后臺之間勘天,有一個(gè)紅色的商戶系統(tǒng),像一道屏障把兩者隔開商膊!那么宠进,為什么微信小程序不是直接與微信后臺進(jìn)行通信,而非要弄個(gè)“商戶系統(tǒng)”在中間進(jìn)行“阻隔”实幕,況且還要讓開發(fā)者投入財(cái)力和精力為這個(gè)“阻隔”買單呢赚导?
這就叫“跑得了和尚,跑不了廟”凰锡!需要微信支付功能的圈暗,就要申請微信支付平臺賬戶,就要建立服務(wù)器勇哗,就要有固定IP地址(甚至必須是經(jīng)過備案的網(wǎng)址)寸齐,這是廟;而瀏覽器扰法、app這些是和尚毅厚!三方支付的角色和權(quán)責(zé)劃分得很清楚,這是人民銀行監(jiān)管部門對三方支付平臺的安全性要求祠锣。
回到微信小程序,實(shí)際上非常特別伴网,它不是別人家的應(yīng)用,而是處在微信原生環(huán)境中拳氢,是微信的一部分蛋铆。不但微信后的廟是自己的跑不了刺啦,連微信和微信小程序這些和尚也是自家造的機(jī)器人纠脾,不會亂跑。但對于微信支付業(yè)務(wù)的主體財(cái)付通而言仍然要考慮廟不是自己的糊渊,該有的流程和步驟不能省慧脱。
四、用云函數(shù)代替商戶系統(tǒng)完成微信支付
那么宗兼,能不能變通一下氮采,用云函數(shù)代替商戶系統(tǒng)呢?答案是可以主到!流程圖上躯概,商戶系統(tǒng)”支付“過程中做了3個(gè)工作:
1、配合小程序端獲得用戶的openid怔鳖;
2、生成用戶訂單度陆,并調(diào)用微信支付統(tǒng)一下單API献幔,讓微信支付為即將發(fā)生的交易預(yù)先形成一個(gè)單子,然后將返回?cái)?shù)據(jù)(5個(gè)參數(shù))進(jìn)行組合再次簽名返回給小程序蹬蚁,小程序用這5個(gè)參數(shù)就可以調(diào)用微信小程序API彈出支付對話框郑兴,完成支付;3叽粹、將支付結(jié)果推送給商戶系統(tǒng)却舀,商戶系統(tǒng)可以據(jù)此更新訂單狀態(tài)為已付款。
用一個(gè)云函數(shù)寫實(shí)現(xiàn)這幾個(gè)功能是完全可行的辆脸!尤其第1項(xiàng)工作螃诅,云函數(shù)調(diào)用的時(shí)候微信系統(tǒng)在云函數(shù)參數(shù)中就帶著openid州刽;而2和3,需要在云函數(shù)中寫一些代碼穗椅。有位小伙伴把這件事情講的很清楚匹表,值得參考:https://blog.csdn.net/gf771115/article/details/100917779。但看上去感覺還是有點(diǎn)麻煩袍镀,尤其是這僅僅是一個(gè)支付功能苇羡,其它諸如退款和付款狀態(tài)查詢等功能還要寫不少代碼。難道沒有一套類庫實(shí)現(xiàn)這些標(biāo)準(zhǔn)化的支付相關(guān)功能嗎锦茁?嘿
嘿,還真有码俩!云函數(shù)的運(yùn)行環(huán)境是node.js稿存,很多功能模塊都能在這里找到。有一個(gè)叫node-tenpay的項(xiàng)目率翅,把騰訊微信支付功能打包成類庫供其他人免費(fèi)使用袖迎,項(xiàng)目網(wǎng)址:https://github.com/befinal/node-tenpay。非常感謝這個(gè)項(xiàng)目的創(chuàng)建者,這套類庫寫得非常棒丘喻,不但可以應(yīng)用在微信小程序云開發(fā)中泉粉,也可以用于在H5、app跺撼、微信服務(wù)號中實(shí)現(xiàn)微信支付功能讨彼,讓開發(fā)者不用每個(gè)人都自己造一遍車輪子了。
五哩至、使用node-tenpay實(shí)現(xiàn)微信支付功能
下面就說一下node-tenpay的使用:
1.node.js的安裝和使用
使用任何一個(gè)node.js模塊蜜自,都要先安裝node.js系統(tǒng)。在云函數(shù)中使用tenpay箭阶,就要在開發(fā)微信小程序的電腦上要安裝node.js系統(tǒng)。tenpay與云函數(shù)最終運(yùn)行在微信后臺的node.js環(huán)境下嘹叫,開發(fā)者要上傳云函數(shù)和tenpay相關(guān)依賴冈敛,而且相關(guān)依賴也要安裝在開發(fā)者本地抓谴。
node.js有適合各種操作系統(tǒng)的,下載安裝非常方便仰泻,但一開始用有點(diǎn)別扭滩届。node.js沒有圖形化的操作界面,所有操作都是依靠命令行的指令運(yùn)行相關(guān)程序棠枉,其中npm(node打包管理)程序的基本使用方法需要掌握泡挺。因?yàn)橄胍褂萌魏我粋€(gè)node.js模塊娄猫,都需要運(yùn)行npm指令安裝到本地電腦指定目錄才行。但這里不多說了月幌。
2.微信小程序云開發(fā)項(xiàng)目
在微信小程序新建項(xiàng)目的時(shí)候選擇小程序云開發(fā)悬蔽,然后創(chuàng)建一個(gè)云函數(shù),比如叫payment缅帘,在IDE環(huán)境下右鍵點(diǎn)擊payment文件夾难衰,在終端下打開,出現(xiàn)命令行窗口失暂,運(yùn)行:npm install tenpay,tenpay就安裝在了這個(gè)目錄下凭峡,用資源管理器可以看到payment文件夾下多了一個(gè)node_modules文件夾决记,微信小程序IDE一般會隱藏這個(gè)文件夾系宫,文件資源管理器可以看到,tenpay及其依賴的全部模塊就在這里扩借,npm還會生成一個(gè)package-lock.json文件潮罪,描述全部依賴關(guān)系,微信小程序IDE可以看到package-lock.json沃暗,這個(gè)會上傳到目標(biāo)環(huán)境何恶,在目標(biāo)環(huán)境下安裝依賴關(guān)系。
3.云函數(shù)代碼index.js:
//云函數(shù)實(shí)現(xiàn)微信支付
const cloud = require('wx-server-sdk')
cloud.init({
env: 'shedeshushuXXXXXX'
})
// 步驟1、引入tenpay微信支付
const tenpay = require('tenpay');
// 步驟2今艺、配置支付信息
const config = {
appid: 'wx349b000d8703a000',
mchid: '1487305888',
partnerKey: 'aaaaIIIIgggg11112222333344445555', //就是微信支付賬戶里面設(shè)置的API密鑰
pfx: require('fs').readFileSync('apiclient_cert.p12'), //這是pfx格式的證書虚缎,支付不用證書钓株,但是退款什么的會用到
notify_url: 'http://www.weixin.qq.com/wxpay/pay.php', //隨便寫一個(gè),云函數(shù)無法實(shí)現(xiàn)返回結(jié)果创坞,但有巧妙的方法實(shí)現(xiàn)同樣功能
spbill_create_ip: '127.0.0.1' //隨便寫一個(gè)受葛,為一些POS場合用的
};
// 云函數(shù)入口函數(shù)
exports.main = async(event, context) => {
const wxContext = cloud.getWXContext()
//步驟3,初始化支付
const api = tenpay.init(config);
//步驟4纲堵,調(diào)用席函,想用一個(gè)云函數(shù)實(shí)現(xiàn)全部支付功能,包括支付正蛙、退款何之、查詢等
switch (event.command) {
case "pay": //支付功能
return await api.getPayParams({
out_trade_no: event.out_trade_no, //這是商戶的訂單號,要求商戶內(nèi)唯一
body: event.body,
total_fee: event.total_fee, //訂單金額(單位是分),
openid: wxContext.OPENID //付款用戶的openid徊件,直接拿就行
})
break
case "payOK": //想利用微信小程序得到付款成功消息后蒜危,給云函數(shù)來一個(gè)通知辐赞,解決付結(jié)果返回沒有服務(wù)器的問題
console.log("en payOK, I known:", event.out_trade_no);
break
case "refund": //退款功能
console.log("refund, event, wxContext.OPENID", event, wxContext.OPENID);
return await api.refund({
// transaction_id, out_trade_no 二選一
// transaction_id: '微信的訂單號',
out_trade_no: event.out_trade_no, //商戶訂單號
out_refund_no: event.out_trade_no + 're', //商戶退款訂單號,要求商戶內(nèi)唯一
total_fee: event.total_fee, //原單訂單金額(單位是分)
refund_fee: event.refund_fee,
refund_desc: event.refund_desc
})
// 相關(guān)默認(rèn)值:
// op_user_id - 默認(rèn)為商戶號(此字段在小程序支付文檔中出現(xiàn))
// notify_url - 默認(rèn)為初始化時(shí)傳入的refund_url, 無此參數(shù)則使用商戶后臺配置的退款通知地址
break
}
}
4.小程序端的代碼片段
//提交訂單
confirmOrder: function() {
let that = this;
wx.cloud.callFunction({
name: "payment",
data: {
command: "pay",
out_trade_no: "test0005",
body: 'a7r2相機(jī)租賃',
total_fee: 100
},
success(res) {
console.log("云函數(shù)payment提交成功:", res.result)
that.pay(res.result)
},
fail(res) {
console.log("云函數(shù)payment提交失斝滤肌:", res)
}
})
},
//實(shí)現(xiàn)小程序支付
pay(payData) {
//官方標(biāo)準(zhǔn)的支付方法
wx.requestPayment({ //已經(jīng)得到了5個(gè)參數(shù)
timeStamp: payData.timeStamp,
nonceStr: payData.nonceStr,
package: payData.package, //統(tǒng)一下單接口返回的 prepay_id 格式如:prepay_id=***
signType: 'MD5',
paySign: payData.paySign, //簽名
success(res) {
console.log("支付成功:", res)
wx.cloud.callFunction({ //巧妙利用小程序支付成功后的回調(diào)鲜漩,再次調(diào)用云函數(shù),通知其支付成功吝梅,以便進(jìn)行訂單狀態(tài)變更
name: "payment",
data: {
command: "payOK",
out_trade_no: "test0004"
},
})
},
fail(res) {
console.log("支付失敺峤椤:", res)
},
complete(res) {
console.log("支付完成:", res)
}
})
},
//退款
refund: function() {
let that = this;
wx.cloud.callFunction({
name: "payment",
data: {
command: "refund",
out_trade_no: "test0005",
body: 'a7r2相機(jī)租賃',
total_fee: 1,
refund_fee: 1,
refund_desc: '押金退款'
},
success(res) {
console.log("云函數(shù)payment提交成功:", res)
},
fail(res) {
console.log("云函數(shù)payment提交失斄涌场:", res)
}
})
}
5.利用小程序端支付成功回調(diào)云函數(shù)扇救,取向想商戶系統(tǒng)推送支付結(jié)果
小程序在調(diào)用 wx.requestPayment()發(fā)起支付后赊淑,如果支付成功陶缺,在回調(diào)函數(shù)中可以再次調(diào)用云函數(shù)洁灵,通知云函數(shù)支付成,以此取代向商戶系統(tǒng)服務(wù)推送支付結(jié)果苫费。這樣可以在不架設(shè)服務(wù)器的情況双抽,實(shí)現(xiàn)微信支付的全部功能。
6.實(shí)現(xiàn)退款功能必須要有證書
按照支付功能類似的方法做退款申請铐维,卻反復(fù)失敗報(bào)錯(cuò)慎菲!實(shí)際上露该,tenpay作者其實(shí)已經(jīng)在readme中講清楚了:
config說明:
pfx - 證書文件(選填, 在微信商戶管理界面獲取)
當(dāng)不需要調(diào)用依賴證書的API時(shí)可不填此參數(shù)
若業(yè)務(wù)流程中使用了依賴證書的API則需要在初始化時(shí)傳入此參數(shù)
微信支付平臺獲取證書,這里就不細(xì)說了抑党,按照官方說明一步一步做即可撵摆,生成的zip文件一定要保存好。這個(gè)zip文件中有3個(gè)文件,微信支付使用的是pfx苟呐,下圖中后綴為.p12的這個(gè)文件就是俐筋,將這個(gè)文件放在云函數(shù)payment的文件內(nèi),會與云函數(shù)其他文件一起上傳到運(yùn)行環(huán)境笆呆,退款申請就成功了。注意證書是由時(shí)效性的俄精,好像是1年榕堰,到期需要重新配置逆屡。
7.在一個(gè)云函數(shù)中實(shí)現(xiàn)全部微信支付API調(diào)用
除了支付功能外魏蔗,微信支付還有退款莺治、查詢訂單、查詢退款等多個(gè)功能产雹,這些功能可以集中在一個(gè)云函數(shù)中全部實(shí)現(xiàn)蔓挖。通過command參數(shù)區(qū)分需要調(diào)用哪個(gè)功能,微信小程序和云函數(shù)都可以調(diào)用這個(gè)微信支付云函數(shù)怨绣。具體代碼等舍得叔叔完善后在貼出吧拷获!