到退款對(duì)賬的實(shí)戰(zhàn)指南)
說(shuō)到微信支付我經(jīng)手過(guò)的項(xiàng)目少說(shuō)也有十幾個(gè)了。從最開(kāi)始的個(gè)人公眾號(hào)H5支付到后來(lái)電商系統(tǒng)里的JSAPI支付和小程序支付再到退款、轉(zhuǎn)賬、對(duì)賬這些伴生功能幾乎每個(gè)環(huán)節(jié)都踩過(guò)坑。微信支付這套接口官方文檔寫(xiě)得不算差但真正下手去接的時(shí)候你會(huì)發(fā)現(xiàn)坑全藏在細(xì)節(jié)里。今天就把我在實(shí)際項(xiàng)目里驗(yàn)證過(guò)的那套流程和注意事項(xiàng)完整梳理一遍從賬號(hào)準(zhǔn)備到下單回調(diào)再到退款對(duì)賬盡量做到讓沒(méi)接過(guò)的人能少走彎路讓已經(jīng)接了一半的人能理清思路。1. 微信支付的賬本邏輯一次支付背后到底發(fā)生了什么很多第一次接微信支付的人最容易犯的毛病是一上來(lái)就找接口文檔。這沒(méi)錯(cuò)但如果你不理解微信支付背后的資金流轉(zhuǎn)邏輯后面處理訂單狀態(tài)、對(duì)賬、退款時(shí)一定會(huì)亂。1.1 一次支付背后參與的四個(gè)角色微信支付體系里每次交易至少有四個(gè)角色參與用戶、商戶、微信支付平臺(tái)、用戶的發(fā)卡行或零錢賬戶。用戶在商戶App或小程序里發(fā)起支付實(shí)際扣款動(dòng)作發(fā)生在用戶側(cè)。微信支付平臺(tái)做的事情是“撮合”確認(rèn)用戶資金足夠、確認(rèn)商戶合法、記錄這筆交易的狀態(tài)然后通過(guò)異步通知告訴商戶“錢已經(jīng)扣了”。注意這里有個(gè)關(guān)鍵點(diǎn)微信支付通知商戶時(shí)資金實(shí)際上還沒(méi)有結(jié)算到商戶的銀行卡里。從支付成功到資金結(jié)算給商戶中間還有一個(gè)“結(jié)算周期”和“結(jié)算賬戶”的概念。普通商戶一般默認(rèn)是T1結(jié)算也就是說(shuō)今天用戶支付成功錢可能在第二天甚至更晚才打到你的對(duì)公賬戶。如果只知道“支付成功”就發(fā)貨大部分業(yè)務(wù)是沒(méi)問(wèn)題的但如果涉及退款就要小心支付成功不等于錢已經(jīng)到你的銀行賬戶退款是微信支付在它的賬本里做“原路退回”即使錢還沒(méi)結(jié)算給你它也能先把這筆交易撤銷掉。1.2 支付狀態(tài)機(jī)與幾個(gè)容易混淆的狀態(tài)我在排查線上問(wèn)題時(shí)經(jīng)??吹接腥税延唵螤顟B(tài)寫(xiě)死成“已支付”“未支付”兩種。等遇到退款、關(guān)閉訂單、部分退款這些場(chǎng)景就徹底糊了。微信支付的訂單狀態(tài)大體上有這么幾類SUCCESS支付成功。這是最核心的狀態(tài)表示用戶的錢已扣交易成立。REFUND轉(zhuǎn)入退款。說(shuō)明這筆訂單已經(jīng)發(fā)生了部分或全額退款。NOTPAY未支付。用戶還沒(méi)完成支付訂單可以繼續(xù)支付。CLOSED已關(guān)閉。訂單超時(shí)未支付或者在未支付狀態(tài)下被商戶主動(dòng)關(guān)閉不能再發(fā)起支付。REVOKED已撤銷。主要是付款碼支付場(chǎng)景下用戶掃碼后長(zhǎng)時(shí)間未輸入密碼或取消支付微信支付自動(dòng)撤銷了這筆交易。PAYERROR支付失敗。通常是因?yàn)橛囝~不足、支付超時(shí)、風(fēng)控?cái)r截等原因。我建議在業(yè)務(wù)系統(tǒng)里用兩個(gè)字段管理訂單支付狀態(tài)一個(gè)是“支付平臺(tái)狀態(tài)”以微信的最終狀態(tài)為準(zhǔn)一個(gè)是“業(yè)務(wù)狀態(tài)”結(jié)合自己的發(fā)貨、退款流程。不要自己造狀態(tài)名直接映射微信支付這一套后續(xù)對(duì)賬會(huì)省掉很多麻煩。1.3 支付成功到底以誰(shuí)為準(zhǔn)這是整篇文章最核心的原則永遠(yuǎn)以微信支付異步回調(diào)為準(zhǔn)不要以用戶在前端看到的成功頁(yè)面為準(zhǔn)。前端頁(yè)面顯示“支付成功”只表示微信客戶端收到了成功結(jié)果。但你的服務(wù)器不一定收到了通知通知可能延遲、丟失、重復(fù)。如果前端一跳轉(zhuǎn)成功頁(yè)面就直接發(fā)貨就可能在通知丟失時(shí)造成“用戶付了錢但你沒(méi)發(fā)貨”甚至“用戶沒(méi)付錢你發(fā)了貨”的嚴(yán)重問(wèn)題。所以所有訂單狀態(tài)更新必須放在你自己的后端接口里由后端通過(guò)回調(diào)通知或主動(dòng)查詢接口確認(rèn)支付狀態(tài)后再更新。這也是我在后文反復(fù)強(qiáng)調(diào)回調(diào)驗(yàn)簽和冪等的原因。2. 動(dòng)手前的準(zhǔn)備商戶號(hào)、API密鑰、證書(shū)一個(gè)都不能少微信支付不是拿個(gè)AppID就能跑的。想要調(diào)通接口你得先有一套完整的商戶資質(zhì)和API憑證。很多新手卡在第一步就是因?yàn)椴磺宄降滓暾?qǐng)哪些東西以及這些憑證分別用在什么地方。2.1 賬號(hào)體系的三個(gè)核心憑證微信支付涉及三個(gè)層級(jí)的賬號(hào)和憑證憑證申請(qǐng)位置用途注意事項(xiàng)小程序/公眾號(hào)AppID微信公眾平臺(tái)標(biāo)識(shí)你的應(yīng)用用戶授權(quán)登錄需要用需要完成微信認(rèn)證且與商戶號(hào)綁定商戶號(hào)mch_id商戶平臺(tái)標(biāo)識(shí)你的商戶身份所有支付交易請(qǐng)求都要帶由微信支付審核通過(guò)后分配需要營(yíng)業(yè)執(zhí)照等資質(zhì)API密鑰/APIv3密鑰商戶平臺(tái)設(shè)置簽名和加解密的數(shù)據(jù)密鑰一個(gè)用于APIv2簽名一個(gè)用于APIv3簽名兩個(gè)不要搞混補(bǔ)充一個(gè)容易忽略的點(diǎn)AppID和商戶號(hào)之間需要“綁定授權(quán)”。如果你用的是別人開(kāi)發(fā)的小程序或者商戶號(hào)是總公司統(tǒng)一申請(qǐng)的一定要確認(rèn)AppID和mch_id已經(jīng)建立綁定關(guān)系否則下單時(shí)會(huì)直接報(bào)“商戶號(hào)與AppID不匹配”。2.2 APIv2和APIv3的選型新項(xiàng)目無(wú)腦選v3經(jīng)常有人問(wèn)我文檔里接口既有v2又有v3到底用哪個(gè)我的答案是新項(xiàng)目全部用APIv3老項(xiàng)目沒(méi)壞就不折騰。這兩個(gè)版本核心差異在簽名和報(bào)文格式上維度APIv2APIv3報(bào)文格式XMLJSON簽名算法MD5或HMAC-SHA256靠一個(gè)API密鑰RSA-SHA256靠商戶私鑰和平臺(tái)公鑰憑證要求API密鑰退款等敏感操作需要商戶證書(shū)雙向認(rèn)證商戶API證書(shū)、商戶私鑰、平臺(tái)證書(shū)回調(diào)驗(yàn)簽用API密鑰算簽名比對(duì)用平臺(tái)證書(shū)驗(yàn)簽更安全敏感信息加密一般明文手機(jī)號(hào)等除外敏感字段用公鑰加密如銀行卡號(hào)、姓名推薦程度老接口代碼簡(jiǎn)單但安全性一般新接口安全和規(guī)范更好APIv3的簽名邏輯我第一次看的時(shí)候也覺(jué)得繞但用順手之后會(huì)發(fā)現(xiàn)它其實(shí)更清晰用商戶私鑰對(duì)“請(qǐng)求方法、URL、時(shí)間戳、隨機(jī)串、請(qǐng)求體”拼接成的字符串簽名然后把商戶號(hào)、時(shí)間戳、隨機(jī)串、證書(shū)序列號(hào)、簽名放進(jìn)Authorization請(qǐng)求頭里。Authorization: WECHATPAY2-SHA256-RSA2048 mchid**** nonce_str*** timestamp*** serial_no*** signature***接收回調(diào)時(shí)再用微信支付平臺(tái)證書(shū)驗(yàn)證平臺(tái)簽名確?;卣{(diào)確實(shí)來(lái)自微信支付官方。這個(gè)雙向驗(yàn)證機(jī)制比v2那種“雙方拿同一個(gè)密鑰算MD5”要可靠得多。2.3 證書(shū)和密鑰的安全存放我見(jiàn)過(guò)最野的操作商戶平臺(tái)可以下載一個(gè)apiclient_cert.pem或apiclient_key.pemAPIv3還會(huì)用到一個(gè)平臺(tái)證書(shū)用來(lái)驗(yàn)簽。我見(jiàn)過(guò)有團(tuán)隊(duì)把這些證書(shū)文件直接提交到Git倉(cāng)庫(kù)里或者放在前端靜態(tài)目錄里這是絕對(duì)絕對(duì)不能做的事。建議的存放方式商戶私鑰和API密鑰存到環(huán)境變量、配置中心或KMS密鑰管理服務(wù)里不要硬編碼在代碼里。服務(wù)器上給證書(shū)文件加白名單權(quán)限只允許應(yīng)用進(jìn)程讀取。定期更換API密鑰尤其在人員變動(dòng)時(shí)。商戶平臺(tái)開(kāi)啟IP白名單只允許你自己的服務(wù)器IP調(diào)用接口。說(shuō)句實(shí)在話微信支付接口本身的安全性設(shè)計(jì)不差大多數(shù)被“盜刷”“被退款”的事故都是商戶自己把密鑰給丟了。3. 主流程拆解從統(tǒng)一下單到支付結(jié)果回調(diào)的完整鏈路搞定賬號(hào)和憑證之后就可以走主流程了。這里以最常見(jiàn)的JSAPI支付為例——也就是公眾號(hào)、H5里用戶在微信內(nèi)打開(kāi)的支付頁(yè)面小程序支付流程與之類似。3.1 統(tǒng)一下單參數(shù)、簽名、prepay_id前端要拉起微信支付服務(wù)端必須先替用戶向微信支付發(fā)起“統(tǒng)一下單”請(qǐng)求。這一步的目的是讓微信支付后臺(tái)生成一個(gè)預(yù)支付訂單并返回一個(gè)prepay_id。以APIv2為例請(qǐng)求地址是https://api.mch.weixin.qq.com/pay/unifiedorder請(qǐng)求體是XML但核心參數(shù)就那么幾個(gè)我列一下最常用的參數(shù)是否必填說(shuō)明appid是公眾號(hào)或小程序的AppIDmch_id是商戶號(hào)out_trade_no是商戶自己的訂單號(hào)必須唯一total_fee是訂單金額單位是分注意必須是整數(shù)且不能為0body是商品描述會(huì)展示在支付頁(yè)面上notify_url是異步通知回調(diào)地址trade_type是JSAPI、NATIVE、APP、MWEB等openid條件必填trade_type為JSAPI時(shí)必須傳即用戶的openidspbill_create_ip建議填用戶下單的IP有助于風(fēng)控time_expire建議填訂單失效時(shí)間比如下單后15分鐘支付超時(shí)所有參數(shù)除sign外按ASCII字典序排序拼接成keyvaluekeyvalue之后在末尾再拼上key你的API密鑰然后MD5或HMAC-SHA256算出來(lái)的就是sign。下單成功后微信會(huì)返回xml return_code![CDATA[SUCCESS]]/return_code result_code![CDATA[SUCCESS]]/result_code prepay_id![CDATA[wx201410272009395522657e690389285100]]/prepay_id /xml這個(gè)prepay_id就是調(diào)起前端支付的憑證。prepay_id有效期一般是2小時(shí)且只能用一次所以不要在緩存里存太久也不要試圖重復(fù)使用。3.2 拿到prepay_id之后前端如何調(diào)起支付服務(wù)端拿到prepay_id后不能直接把prepay_id丟給前端就完事還需要再生成一組“調(diào)起支付參數(shù)”。以JSAPI為例后端要返回給前端這幾個(gè)字段{ appId: wx************, timeStamp: 1640995200, nonceStr: 隨機(jī)字符串, package: prepay_idwx201410272009395522657e690389285100, signType: RSA, paySign: 用APIv3或APIv2規(guī)則生成的簽名 }這里最容易出錯(cuò)的點(diǎn)是package參數(shù)。很多人以為是直接傳prepay_id其實(shí)它要拼成prepay_idxxx。另外timeStamp是秒級(jí)時(shí)間戳不是毫秒前端做Number()轉(zhuǎn)換時(shí)別被坑了。生成paySign的簽名規(guī)則在不同版本里不同APIv3下是把a(bǔ)ppId、timeStamp、nonceStr、package按特定方式拼起來(lái)再用商戶私鑰簽名。簽名算法錯(cuò)了前端會(huì)一直停在“支付失敗”但后端明明下單成功這種問(wèn)題我排查過(guò)好幾次最后發(fā)現(xiàn)是簽名串格式里多了一個(gè)換行符。小程序端調(diào)起支付的寫(xiě)法是wx.requestPayment({ timeStamp: res.data.timeStamp, nonceStr: res.data.nonceStr, package: res.data.package, signType: RSA, paySign: res.data.paySign, success: function (res) { // 這里不要急著更新訂單狀態(tài)等后端回調(diào) }, fail: function (err) { console.error(支付失敗, err) } })3.3 異步回調(diào)驗(yàn)簽、金額校驗(yàn)、冪等處理下單之后最關(guān)鍵的環(huán)節(jié)就是異步回調(diào)。微信支付會(huì)在用戶支付成功后以POST方式把結(jié)果發(fā)送到你在統(tǒng)一下單時(shí)填寫(xiě)的notify_url。先把回調(diào)處理的完整步驟整理出來(lái)驗(yàn)簽確認(rèn)這個(gè)通知確實(shí)來(lái)自微信支付。在APIv3下用平臺(tái)證書(shū)驗(yàn)證通知自帶的簽名在APIv2下用API密鑰重新計(jì)算簽名進(jìn)行比對(duì)。解密APIv3的通知報(bào)文是加密的需要結(jié)合APIv3密鑰進(jìn)行解密得到明文訂單數(shù)據(jù)。校驗(yàn)業(yè)務(wù)參數(shù)重點(diǎn)核對(duì)out_trade_no是不是你自己的訂單、total_fee是否與訂單金額一致、mch_id是否匹配。冪等處理微信支付的通知可能重復(fù)發(fā)送服務(wù)端必須保證重復(fù)通知不會(huì)重復(fù)發(fā)貨、重復(fù)加余額。返回應(yīng)答處理成功后返回SUCCESS否則回調(diào)會(huì)按間隔重試15秒/15秒/30秒/3分鐘/10分鐘/20分鐘/30分鐘/30分鐘/30分鐘/60分鐘/3小時(shí)/3小時(shí)/3小時(shí)/6小時(shí)/6小時(shí)。這里我特別想強(qiáng)調(diào)冪等。微信支付為了保證回調(diào)可靠送達(dá)會(huì)多次通知。我第一次接支付的時(shí)候就沒(méi)做冪等用戶支付成功后連續(xù)收到了兩次回調(diào)于是數(shù)據(jù)庫(kù)里加了兩次余額第二天對(duì)賬才發(fā)現(xiàn)。從那以后我在回調(diào)入庫(kù)前一律先查一次訂單狀態(tài)只有“待支付”才更新為“已支付”否則直接返回SUCCESS。另外回調(diào)接口的耗時(shí)盡量控制在2秒以內(nèi)。如果你在回調(diào)里又去調(diào)別的業(yè)務(wù)接口、發(fā)短信、同步ERP一旦響應(yīng)慢了微信那邊會(huì)繼續(xù)重試最終造成大量重復(fù)通知堆積。4. 不只是收錢退款、轉(zhuǎn)賬與對(duì)賬這幾個(gè)伴生操作很多人把“微信支付詳解”理解成“怎么收錢”。但真實(shí)線上業(yè)務(wù)里退款、轉(zhuǎn)賬、對(duì)賬這三個(gè)操作才是最容易出生產(chǎn)事故的地方。4.1 退款原路退回的細(xì)節(jié)退款接口在APIv2里有單獨(dú)的地址https://api.mch.weixin.qq.com/secapi/pay/refund注意域名里多了個(gè)secapi說(shuō)明這個(gè)接口對(duì)安全性要求更高。APIv2下退款需要加載商戶證書(shū)做雙向TLS認(rèn)證直接用HTTP客戶端請(qǐng)求是過(guò)不去的。退款核心參數(shù)參數(shù)說(shuō)明out_trade_no原商戶訂單號(hào)transaction_id微信支付訂單號(hào)二選一即可out_refund_no商戶退款單號(hào)需要自己維護(hù)total_fee原訂單金額單位分refund_fee退款金額單位分不能大于total_feerefund_desc退款原因部分渠道展示給用戶notify_url退款結(jié)果異步回調(diào)地址退款還有個(gè)很容易踩的坑退款金額、退款單號(hào)必須要有冪等設(shè)計(jì)。如果你因?yàn)榫W(wǎng)絡(luò)超時(shí)重試退款給同一個(gè)訂單發(fā)兩次相同金額的退款微信不會(huì)自動(dòng)幫你合并。所以out_refund_no一定要用能保持穩(wěn)定的、不會(huì)因重試而變化的編號(hào)。另外退款也不是立刻到賬的。微信支付會(huì)異步處理退款部分銀行渠道可能需要1-5個(gè)工作日。業(yè)務(wù)系統(tǒng)里千萬(wàn)別在提交退款請(qǐng)求成功后就以為“已退款”要以退款回調(diào)為準(zhǔn)。4.2 企業(yè)付款到零錢微信紅包和轉(zhuǎn)賬的差別企業(yè)付款到零錢也就是“微信支付商戶號(hào)向外轉(zhuǎn)錢”比如返利、提現(xiàn)、報(bào)銷等場(chǎng)景。這個(gè)接口叫https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers這個(gè)接口同樣是雙向證書(shū)調(diào)用且對(duì)商戶號(hào)和收款用戶的實(shí)名要求非常嚴(yán)格。參數(shù)里需要openid、amount、desc以及可選的真實(shí)姓名re_user_name。注意金額單位是分跟你收錢時(shí)保持一致千萬(wàn)別在轉(zhuǎn)賬時(shí)當(dāng)成元用。這個(gè)接口目前有幾個(gè)限制收款用戶必須是微信實(shí)名用戶商戶一定有足夠可轉(zhuǎn)出的余額轉(zhuǎn)賬涉及風(fēng)控頻繁大額轉(zhuǎn)賬很容易觸發(fā)人工審核。我做提現(xiàn)功能時(shí)就曾經(jīng)因?yàn)閱喂P金額過(guò)大被限制后來(lái)拆分成多筆才通過(guò)。4.3 對(duì)賬每天的對(duì)賬單怎么用跟錢打交道對(duì)賬是底線。微信支付提供了下載對(duì)賬單的接口https://api.mch.weixin.qq.com/pay/downloadbill對(duì)賬單分日賬單和申請(qǐng)資金賬單按天下載下來(lái)是一個(gè)TXT文件CSV格式每行包含交易時(shí)間、商戶號(hào)、訂單號(hào)、微信訂單號(hào)、支付方式、金額、手續(xù)費(fèi)、狀態(tài)等字段。維護(hù)一個(gè)每日定時(shí)任務(wù)把對(duì)賬單下載下來(lái)和自己數(shù)據(jù)庫(kù)里的賬單比對(duì)是排查“用戶付了錢但訂單未支付成功”“金額不一致”最直接的手段。我自己一般每周跑一次手動(dòng)對(duì)賬每月做一次全面核對(duì)。別嫌麻煩線上支付沒(méi)有對(duì)賬機(jī)制等于大半夜不鎖門。5. 線上常見(jiàn)的坑從簽名錯(cuò)誤到重復(fù)通知微信支付的坑很多不是文檔沒(méi)寫(xiě)而是藏在各種邊界情況里。這一節(jié)專門記錄我踩過(guò)或幫別人排查過(guò)的高頻問(wèn)題。5.1 簽名錯(cuò)誤大多數(shù)人忽略的換行符與編碼問(wèn)題簽名錯(cuò)誤是新手遇到最多的報(bào)錯(cuò)。這里分享幾個(gè)容易被忽略的原因拼接參數(shù)時(shí)參數(shù)值里有中文、空格、特殊字符沒(méi)有做URL編碼或直接用了駝峰命名。APIv3的簽名串是按“實(shí)際請(qǐng)求路徑請(qǐng)求體”拼的如果你用HTTP客戶端時(shí)URL被加上了多余的/簽名就會(huì)失敗。生成MD5時(shí)原來(lái)的API密鑰用的是32位字符但如果你在商戶平臺(tái)把密鑰改成43位那就會(huì)變成APIv3的密鑰v2接口就不能用了。我在排查別人代碼時(shí)發(fā)現(xiàn)最典型的問(wèn)題就是代碼里用了String.trim()去掉參數(shù)首尾空格但微信的簽名原串要求嚴(yán)格保留原始請(qǐng)求體。千萬(wàn)別做二次格式化。5.2 回調(diào)重復(fù)通知與并發(fā)問(wèn)題前面已經(jīng)說(shuō)過(guò)重復(fù)通知的冪等。這里再補(bǔ)充一個(gè)并發(fā)場(chǎng)景如果同一個(gè)用戶快速重復(fù)支付或者兩次回調(diào)在同一秒到達(dá)你的查詢訂單狀態(tài)和更新?tīng)顟B(tài)如果不是一個(gè)原子操作仍然可能造成重復(fù)發(fā)貨。解決方案很簡(jiǎn)單在數(shù)據(jù)庫(kù)更新時(shí)帶上狀態(tài)條件UPDATE orders SET pay_status PAID, transaction_id xx WHERE order_id xx AND pay_status UNPAID受影響行數(shù)為1才表示本次更新成功。用這種方式即使回調(diào)過(guò)來(lái)10次也只會(huì)成功執(zhí)行一次。5.3 證書(shū)過(guò)期與密鑰輪換很多人在代碼上線時(shí)沒(méi)注意證書(shū)和密鑰的有效期。APIv2的商戶證書(shū)、APIv3的平臺(tái)證書(shū)都有有效期到期之后所有需要證書(shū)的接口都會(huì)突然失敗。如果項(xiàng)目里證書(shū)是手動(dòng)下載的一定要在證書(shū)到期前做提醒計(jì)劃。這里說(shuō)一個(gè)實(shí)用做法把證書(shū)過(guò)期時(shí)間寫(xiě)進(jìn)監(jiān)控指標(biāo)按周檢查同時(shí)預(yù)留好證書(shū)輪換接口確保在到期前可以平滑切換。否則訂單數(shù)據(jù)都在正常跑突然某天轉(zhuǎn)賬、退款全掛會(huì)非常被動(dòng)。6. 上線前的自查清單與幾條個(gè)人經(jīng)驗(yàn)最后這部分寫(xiě)給即將上線或者正在聯(lián)調(diào)的同學(xué)。微信支付接口雖然只有那么幾個(gè)但從開(kāi)發(fā)到上線很多細(xì)節(jié)是做之前想象不到的。我把自己的經(jīng)驗(yàn)整理成一份“上線前清單”盡量幫大家減少交學(xué)費(fèi)的概率。6.1 日志關(guān)鍵數(shù)據(jù)必須打全支付相關(guān)日志必須包含以下內(nèi)容缺一個(gè)你會(huì)后悔請(qǐng)求參數(shù)和響應(yīng)參數(shù)原文脫敏后特別是out_trade_no、transaction_id、total_fee、return_code。每次回調(diào)的完整報(bào)文、驗(yàn)簽結(jié)果、解密后的訂單數(shù)據(jù)。支付接口上下游的耗時(shí)包括下單接口、回調(diào)處理接口。訂單狀態(tài)遷移的前后值方便追蹤整個(gè)生命周期。日志和監(jiān)控是支付線上事故定位的生命線。沒(méi)有日志出問(wèn)題只能干瞪眼。6.2 測(cè)試真實(shí)驗(yàn)證不了就做邊界模擬微信支付沒(méi)有很好的全量沙箱環(huán)境很多測(cè)試依賴真實(shí)的小額支付。我的經(jīng)驗(yàn)是準(zhǔn)備一個(gè)測(cè)試商戶號(hào)所有功能聯(lián)調(diào)都用測(cè)試號(hào)。在測(cè)試環(huán)境里模擬用戶取消支付、支付超時(shí)、重復(fù)回調(diào)、金額不一致等各種異常場(chǎng)景。用幾筆1分錢的真實(shí)支付驗(yàn)證全鏈路包括下單、回調(diào)、退款、對(duì)賬單下載。確認(rèn)退款可以全額退、部分退并且退款成功后原訂單狀態(tài)正確。6.3 安全紅線防刷單、防篡改、防越權(quán)微信支付接口是公開(kāi)的如果服務(wù)端不校驗(yàn)很容易被別人刷。上線前一定要檢查這幾點(diǎn)統(tǒng)一下單接口必須校驗(yàn)用戶登錄態(tài)防止別人惡意下單?;卣{(diào)通知校驗(yàn)金額時(shí)必須用數(shù)據(jù)庫(kù)訂單金額為準(zhǔn)不能直接信任回調(diào)里的total_fee。我見(jiàn)過(guò)有人把回調(diào)里的金額直接寫(xiě)進(jìn)訂單結(jié)果被構(gòu)造假通知刷了貨。涉及退款的接口必須做管理員權(quán)限校驗(yàn)且驗(yàn)證退款金額不能大于原始訂單金額。服務(wù)器與微信接口的通信必須走HTTPS且不要關(guān)閉證書(shū)校驗(yàn)。最后分享一個(gè)小技巧在企業(yè)微信里加一個(gè)機(jī)器人把支付回調(diào)、退款回調(diào)的高優(yōu)先級(jí)異常直接推到群里。這樣哪怕半夜出問(wèn)題你也能第一時(shí)間知道。微信支付這套東西說(shuō)難不難說(shuō)簡(jiǎn)單也絕不簡(jiǎn)單但只要把“狀態(tài)機(jī)、回調(diào)冪等、金額校驗(yàn)、日志對(duì)賬”這四件事做好它就能跑得很穩(wěn)。