凡是和錢打交道的事,沒有一樣是容易的。這是我第一次接觸微信支付,發現網上還是有很多同學在求助,XXX了怎麼辦?XXX是什麼情況?為了幫助更多的小夥伴脫離“苦海”,我決定寫下這次的踩坑之旅,給更多的人幫助。
介紹
微信支付方式分為刷卡支付、公眾號支付、掃碼支付、APP支付、H5支付、小程式支付。
先從應用場景來各自說一說,這樣,能夠最快的判斷出應該選擇哪一種支付。
- 刷卡支付:使用掃描裝置(掃描槍)多見於超市、便利店使用
- 公眾號支付:嵌入公眾號的H5頁面
- 掃碼支付:使用者開啟“微信掃一掃”,掃描商戶的二維碼並支付
- APP支付:外部APP應用,使用者觸發支付時,轉到微信內完成支付
- H5支付:非微信內建瀏覽器請求微信支付
- 小程式支付:使用者在微信小程式中使用微信支付
背景
我們公司申請的是微信服務號,需要微信支付的是嵌入服務號內部的網頁,所以根據介紹,應該選擇“公眾號支付”。
開發步驟
首先不要被微信支付的開發嚇著,其實它很簡單。先仔細看公眾號支付的文件,看不懂的多看幾遍,還看不懂的,動手操作一下,試一試。
文件在此:
pay.weixin.qq.com/wiki/doc/ap…
- 步驟一:統一下單
跟著文件,我們一點點來,搞明白每一步是為什麼,就不會迷迷糊糊搞不清楚了。
首先說一下,這個介面是後臺需要完成的,這個介面的目的就是獲取prepay_id,它是預支付交易回話標識。將prepay_id傳給前臺,前臺呼叫js-sdk,這屬於步驟二的範圍了,一會講。
介面連結
URL地址:api.mch.weixin.qq.com/pay/unified…
在文件中說明了,必須使用post 方法請求微信給的介面連結,傳入的資料也必須是xml格式,返回的也是xml的。醉了?不要醉,微信是這樣的,支付寶也是這樣的。手動微笑,接受吧。
接著來。
簡單粗暴貼程式碼:
// '/addOrder'是留給前臺的呼叫介面
router.post('/addOrder',(req,res)=>{
const addOrderUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
var client_ip = "";
client_ip = req.body.ipaddr;
var appid = "1234567890"; // 服務號|公眾號的appid
var body = "商品簡單描述-測試"; // 商品簡單描述
var mch_id= "1234567890"; // 商戶號,申請微信支付,騰訊給的商戶號
// var device_info = "WEB";
var nonce_str = getRanId(32); // 隨機字串
var out_trade_no = "" +new Date().getTime() + Math.floor( Math.random() * 10 ); //商戶訂單號
var total_fee = req.body.total_fee; //支付金額,單位:分
var sign = "";
var notify_url = "http://123.456.789"; //非同步接收微信支付結果通知的回撥地址
var trade_type = "JSAPI"; // 交易型別
var openid = req.session.openId;
console.log(openid);
var stringA = `appid=${appid}&body=${body}&mch_id=${mch_id}&nonce_str=${nonce_str}¬ify_url=${notify_url}&openid=${openid}&out_trade_no=${out_trade_no}&spbill_create_ip=${client_ip}&total_fee=${total_fee}&trade_type=${trade_type}`;
var stringSighTemp = stringA+"&key=****#####jiaoyuguihuayuan----***"; //32位的商戶key,自定義的,這裡為了隱私,我用的特殊符號給你們展示
sign = md5(stringSighTemp).toUpperCase();
var xml = `<xml>
<appid>${appid}</appid>
<body>${body}</body>
<mch_id>${mch_id}</mch_id>
<nonce_str>${nonce_str}</nonce_str>
<notify_url>${notify_url}</notify_url>
<openid>${openid}</openid>
<out_trade_no>${out_trade_no}</out_trade_no>
<spbill_create_ip>${client_ip}</spbill_create_ip>
<total_fee>${total_fee}</total_fee>
<trade_type>${trade_type}</trade_type>
<sign>${sign}</sign>
</xml>`;
var Res = res;axios({
method: 'post',
url: addOrderUrl,
data: xml,
responseType: 'text/xml',
headers: {
'Content-Type': 'text/xml'
}
}).then( res=>{
console.log(res)
Res.send(res.data)
}).catch( err=>{
console.log( err)})
})複製程式碼
說明
client_ip 引數 是客戶端的ip地址,本來我是在後臺獲取客戶端ip地址的,因為我們使用了nginx代理,req.ip 返回的都是 ::ffff:127.0.0.1 這是IPV6格式的字串。網上有一個答案對此做出瞭解釋: stackoverflow.com/questions/2…
在這裡,我用的一個網上的指令碼在前臺獲取的, http://pv.sohu.com/cityjson?ie=utf-8
使用方法: window.ipaddr = returnCitySN[‘cip’];
其他的引數,都是參考微信支付的要求去寫的。
出現的錯誤
XML格式錯誤
而檢視文件,原因是這樣的
我:#&(%#@+%),也不給個詳細點的說明…
這種錯誤需要“頓悟”,我突然發現了我的錯誤。是我理解錯了!我給body標籤加了一個 <![CDATA[]]> 導致我的xml格式錯誤,其實是有detail欄位才需要新增 <[CDATA[]]>, 其他的不需要。
我:咳咳,低階錯誤。注意看文件,按照要求來,既不多添什麼,也不要少什麼。
我把<[CDATA[]]>去掉之後,發現果真是這個原因,不再出現XML格式錯誤了,然而,還是高興的太早,因為它報了簽名錯誤。呵呵呵~
簽名錯誤
文件中說的簽名計算很嚴格:
第一步,設所有傳送或者接收到的資料為集合M,將集合M內非空引數值的引數按照引數名ASCII碼從小到大排序(字典序),使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字串stringA。
特別注意以下重要規則:
第一步,設所有傳送或者接收到的資料為集合M,將集合M內非空引數值的引數按照引數名ASCII碼從小到大排序(字典序),使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字串stringA。
◆ 引數名ASCII碼從小到大排序(字典序);
◆ 如果引數的值為空不參與簽名;
◆ 引數名區分大小寫; (公眾號支付全是小寫)
◆ 驗證呼叫返回或微信主動通知簽名時,傳送的sign引數不參與簽名,將生成的簽名與該sign值作校驗。
◆ 微信介面可能增加欄位,驗證簽名時必須支援增加的擴充套件欄位
第二步,在stringA最後拼接上key得到stringSignTemp字串,並對stringSignTemp進行MD5運算,再將得到的字串所有字元轉換為大寫,得到sign值signValue。
◆ key設定路徑:微信商戶平臺(pay.weixin.qq.com)–>賬戶設定–>API安全–>金鑰設定
這裡我查閱了一些資料,看到過有這樣幾種錯誤情況:
key 看錯了,這裡應該寫商戶的key,而這個key 是使用者手動設定的,長32位。注意:自己儲存一份,因為設定好了之後是沒法開啟檢視的。
還有一種錯誤,我覺得很離譜啊,body裡面不能有中文,然而,我把body中的文字改為英文,發現並不能改變現狀,其實用中文是可以的。
…
總之,找到的這些錯誤,通通對我的情況沒用!
然後這又需要“頓悟”,原來stringA字串我用了換行符把很長的字元隔開,這導致換行符被轉換為Ascall碼中的 \n 寫進了簽名裡面,所以,sign錯誤,所以要麼,把換行符通通去掉,要麼用“”連線,捨棄。我把換行都去掉之後,就沒有簽名錯誤了。
噹噹噹當 ~
終於完成了第一步,後臺成功的返回了我們需要的prepay_id
這裡為了安全,對於返回sign,和傳送的sign進行對比,完全相等之後,才能把結果返回給前臺。
- 步驟二:呼叫微信js-sdk介面
微信支付
發起一個微信支付請求
前臺收到的是xml資料,要先解析一下,得到prepay_id
然後呼叫微信支付js-sdk,為了大家少走一些彎路,我先來正確的寫法,關鍵步驟如下:
var {prepay_id,appid} = getInfo(res.data); //從後臺資料中獲取appid 和 prepay_id
nonceStr = getRanId(32);
timeStamp = new Date().getTime();
var stringA = "appId="+appid+"&nonceStr="+nonceStr+"&package=prepay_id="+prepay_id+"&signType=MD5&timeStamp="+timeStamp;
var stringSignTemp = stringA+"&key=****#####jiaoyuguihuayuan----***";
paySign = md5(stringSignTemp).toUpperCase();
window.wx.chooseWXPay({
timestamp: timeStamp, // 支付簽名時間戳,注意微信jssdk中的所有使用timestamp欄位均為小寫。但最新版的支付後臺生成簽名使用的timeStamp欄位名需大寫其中的S字元
nonceStr: nonceStr, // 支付簽名隨機串,不長於 32 位
package: "prepay_id=" + prepay_id, // 統一支付介面返回的prepay_id引數值,提交格式如:prepay_id=\*\*\*)
signType: 'MD5', // 簽名方式,預設為'SHA1',使用新版支付需傳入'MD5'
paySign: paySign, // 支付簽名
success: function (res) {
// 支付成功後的回撥函式
console.log(res)
},
fail: function(err){
console.log(err)
}});複製程式碼
備註:prepay_id 通過微信支付統一下單介面拿到,paySign 採用統一的微信支付 Sign 簽名生成方法,注意這裡 appId 也要參與簽名,appId 與 config 中傳入的 appId 一致,即最後參與簽名的引數有appId, timeStamp, nonceStr, package, signType。
注意,我要講個坑點~
呼叫js-sdk時,簽名中的欄位都是小駝峰的寫法,timeStamp是這樣寫的,但是wx.config中,timestamp 是全小寫的,所以,親們,千萬不要搞錯了,我在這裡就被坑了好一會呢。
寫完簽名之後,當你用微信web開發者工具去測試的話,就會看到,“不支援模擬”這樣的提示。這個時候,不要猶豫,直接上真機去測試,這並不是我們的程式出現了問題。
小tips: 在真機上,我們是沒有辦法看到console出的一些除錯資訊,所以,要想個辦法,可以用alert,也可以把除錯資訊列印在螢幕上面,我選擇列印在螢幕上。這裡要說一些,微信給的文件沒有那麼齊全,有一些是要試試才指導的,比如wx.config中的success和fail函式,引數資訊怎麼列印,其實是res.errMsg和err.errMsg.
上面我說的這些你都注意到了,但是微信支付的控制元件你依然調動不起來的話,可能是微信商戶平臺的開發配置出現了問題,在 產品中心-開發配置-支付配置-公眾號支付 中進行配置,配置的時候,注意一定要到最後一級目錄,比如我要在cms.123.456/book/list/index.html頁面中去進行微信支付,那麼你的配置應該是 cms.123.456/book/list/
好了,開發中基本上所有的坑都提到了,這是建立在你配置沒有出錯的情況下。接下來,看看到底能不能真的支付。
完成
大功告成,讓我想起了,最近流行的一句話,你這磨人的小妖精,微信支付!
希望能給你們帶來幫助~
作者:justsso
連結:https://juejin.im/post/5a81aaa66fb9a0635f7e7c22
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。