嘮叨幾句
因?yàn)楸晃⑿拍莻€(gè)破爛文檔坑了我兩個(gè)星期报账,導(dǎo)致項(xiàng)目進(jìn)度慢了很多当宴。本來(lái)微信的 API 的確是設(shè)計(jì)得爛纵势,但爛我也覺(jué)得不要緊了踱阿,文檔也爛那我就真的火了,跟人捉迷藏似的東一塊西一塊(玩猜謎嗎你)钦铁。這里也記錄一下我做開(kāi)發(fā)遇到的坑软舌。
如何申請(qǐng)公眾號(hào)以及商戶(hù)平臺(tái)不在本文范疇內(nèi),因?yàn)轫?xiàng)目經(jīng)理已經(jīng)拿到這這些東西了牛曹,我所做的就是完成代碼的編寫(xiě)佛点。
環(huán)境
這里使用了 com.github.binarywang
的 jar,下面默認(rèn)都是在這個(gè)前期下討論黎比。其他自己實(shí)現(xiàn)的或者其他人的庫(kù)請(qǐng)配合文檔和其他人分享的資料看超营。
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>2.8.0</version>
</dependency>
正文
首先有一個(gè)十分重要的提醒:
配合前端開(kāi)發(fā)人員測(cè)試微信支付的時(shí)候千萬(wàn)千萬(wàn)不能用微信的沙箱。
那個(gè)沙箱沒(méi)有他們文檔說(shuō)的那么厲害阅虫,只能確認(rèn)微信回調(diào)我們服務(wù)器沒(méi)問(wèn)題演闭,不能用來(lái)模擬測(cè)試整個(gè)支付流程,而且這個(gè)沙箱設(shè)計(jì)得十分垃圾颓帝,連沙箱都算不上(支付金額只允許用例內(nèi)的米碰。你見(jiàn)過(guò)只能畫(huà)指定幾個(gè)圖案的沙箱嗎?购城?)吕座,所以還是乖乖地測(cè)一次給一分錢(qián)吧。
開(kāi)發(fā)
一般開(kāi)發(fā)主要用到微信的兩種支付方式
- JSAPI : 用于在微信自己的瀏覽器里面喚起微信支付
- NATIVE : 用于掃碼支付
JSAPI 不用多說(shuō)瘪板,就是觸發(fā)之后會(huì)喚起微信的支付對(duì)話(huà)框給用戶(hù)選擇支付與否吴趴。
NATIVE 就是生成訂單之后給用戶(hù)用微信掃碼付款的。這個(gè)方式我看了文檔侮攀,微信給本來(lái)的設(shè)計(jì)貌似是給那些自動(dòng)販賣(mài)機(jī)用的锣枝,不過(guò)稍微改變一下使用方法就可以適用于任意掃碼支付厢拭。
JSAPI 和 NATIVE 兩種支付方式均使用統(tǒng)一下單接口先獲取微信預(yù)付款訂單信息,然后再進(jìn)行剩下的操作惊橱。兩者傳參幾乎一樣,不同的是:
- JSAPI 需要傳入 openId箭昵,NATIVE 不需要税朴。
- NATIVE 需要傳入 productId,JSAPI 不需要家制。
上面所說(shuō)的稍微改變一下使用方法正林,就是在 NATIVE 支付的時(shí)候 productId 使用自己的訂單 ID 就好了。
NATIVE 支付方式
首先說(shuō)這個(gè)支付方式是因?yàn)椴梗@個(gè)方式很簡(jiǎn)單觅廓,而且我也很推薦用這個(gè),但掃碼就需要用另外一臺(tái)手機(jī)了涵但。
根據(jù)微信的文檔向統(tǒng)一下單接口傳入相應(yīng)的參數(shù)之后杈绸,就得到預(yù)付款訂單了。這里得到的預(yù)付款訂單包含了參數(shù) codeUrl
矮瘟,傳這個(gè)給前端開(kāi)發(fā)的去生成二維碼或者自己服務(wù)器端生成二維碼都可以瞳脓,掃碼之后就可以用微信付款。付款成功之后澈侠,微信會(huì)通過(guò)預(yù)先指定的回調(diào) API 發(fā)送訂單支付的結(jié)果劫侧。根據(jù)結(jié)果完成自己的訂單邏輯這個(gè)就不說(shuō)了。
JSAPI 支付方式
我覺(jué)得這個(gè)就是最坑爹的地方了哨啃。
首先傳入?yún)?shù)之后烧栋,微信返回了預(yù)付款訂單信息,然后這個(gè)信息需要返回給前端拳球。但是审姓,這之前需要對(duì)預(yù)付款訂單的某些字段拼接起來(lái),作一次簽名祝峻,簽名需要嚴(yán)格按照字段序排序以及注意大小寫(xiě):
appId
nonceStr
package
signType
timeStamp
StringBuilder params = new StringBuilder()
.append("appId=").append(wxPayService.getConfig().getAppId()).append("&")
.append("nonceStr=").append(nonceStr).append("&")
.append("package=").append("prepay_id=").append(orderResult.getPrepayId()).append("&")
.append("signType=").append("MD5").append("&")
.append("timeStamp=").append(timestamp);
//appId={appId}&nonceStr={nonceStr}&package=prepay_id={prepayId}&signType=MD5&timeStamp={timestamp}
(真心對(duì)微信大小寫(xiě)隨便來(lái)表示很無(wú)語(yǔ))其中
nonceStr
prepay_id
需要與微信返回的預(yù)付款訂單內(nèi)的一致邑跪。timeStamp
也要傳給前端,到時(shí)候前端需要把這個(gè) timeStamp
傳進(jìn)喚起微信支付對(duì)話(huà)框的函數(shù)呼猪。
拼接好這個(gè)之后画畅,再在后面拼接參數(shù) key
并進(jìn)行一次 MD5。
params.append("&").append("key=").append(wxPayService.getConfig().getMchKey());
String prepay_sign = DigestUtils.getMD5(true, params.toString());
所以所需要傳給前端的參數(shù)如下:
{
"appId":"你的 appId",
"nonceStr":"訂單內(nèi)的 nonceStr",
"timeStamp":"訂單參數(shù)簽名的時(shí)間",
"prepay_sign":"訂單簽名結(jié)果"
}
這時(shí)候不要急著去喚起微信的支付窗口宋距,因?yàn)檫€有后面一系列步驟轴踱。
這里需要注意這些返回的參數(shù):
nonceStr
timeStamp
這兩個(gè)參數(shù)在整個(gè)支付的過(guò)程中要一致,而且參數(shù)大小寫(xiě)也需要注意谚赎。流程內(nèi)的 API 有的地方給弄駝峰命名法有的地方則用全小寫(xiě)淫僻。
然后诱篷,前端需要再拿當(dāng)前調(diào)用 JSAPI 支付的瀏覽器地址欄的路徑,向服務(wù)器請(qǐng)求一個(gè)簽名雳灵,這個(gè)簽名就是前端喚起 JSAPI 所需要的簽名棕所,我這里請(qǐng)求的 API 以及示例如下:
POST -> https://shinonometn.com/WC/ticket
{
"url":"https://shinonometn.com/?#/order/11",
"nonceStr":"8897djsk09ll",
"timeStamp":1560789
}
url 那里一定一定要注意,對(duì)于 SPA 應(yīng)用悯辙,路由前綴那里琳省,絕對(duì)不能只有一個(gè)#
,微信的這個(gè)安全機(jī)制很傻屄躲撰。首先你需要去商戶(hù)平臺(tái)那里注冊(cè) JSAPI 支付允許的“支付目錄”(我暈针贬,目錄),然后在調(diào)用 JSAPI 支付的時(shí)候他們會(huì)校驗(yàn)?zāi)愕刂窓凇痹凇安弧痹凇耙炎?cè)的“支付目錄”拢蛋,不在就拒絕下單桦他。我猜他們這個(gè)機(jī)制是做給服務(wù)器端渲染頁(yè)面的應(yīng)用做的:你會(huì)發(fā)現(xiàn)你的 SPA 應(yīng)用拿到的 URL 經(jīng)常跟他們微信拿到的 URL 不匹配,從而一直提示你 URL 未注冊(cè) 然后拒絕下單谆棱。這其實(shí)不算坑快压,文檔在很隱蔽的地方提及需要前端拿這個(gè) sign 去調(diào)用 JSAPI 支付 and 支付前需要調(diào)用 config 一次才是坑死人。
這個(gè)簽名是這樣的垃瞧,如下參數(shù)全小寫(xiě)嗓节,嚴(yán)格按照字典順序排序:
-
jsapi_ticket
(我一直不知道這個(gè)東西的存在,因?yàn)槲臋n里面沒(méi)有提及) -
noncestr
(小寫(xiě)皆警,小心) -
timestamp
(小寫(xiě)拦宣,小心) -
url
(就是上面提及的 URL)
然后如此拼接:
jsapi_ticket={你拿到的 JSAPI TICKET}&noncestr={訂單上的 nonceStr}×tamp={你訂單的 timeStamp}&url={URL}
//不算大括號(hào),只是為了好看加上去的
然后對(duì)這這個(gè)拼接好的字符串信姓,SHA1 一次鸵隧,拿小寫(xiě)的字符串,返回給前端意推,那么前端就可以很愉快地填上對(duì)應(yīng)的參數(shù)去 wx.config 一下豆瘫,喚起微信支付窗口了。
那么這個(gè) URL 在商戶(hù)平臺(tái)注冊(cè)的時(shí)候需要注意什么呢菊值?對(duì)于服務(wù)器端渲染頁(yè)面外驱,你需要填寫(xiě)支付頁(yè)面的地址,刪掉最后的”目錄“:
//訂單支付頁(yè)面
https://shinonometn.com/order/pay/1
//注冊(cè)的 URL
https://shinonometn.com/order/pay/
對(duì)于 SPA (單頁(yè)應(yīng)用)來(lái)說(shuō)腻窒,你只需要填寫(xiě)你的應(yīng)用地址昵宇,路由那里怎么方便怎么做手腳。
//帶上路由的 SPA 的頁(yè)面
https://shinonometn.com/?#/order/pay/1
^我就弄了個(gè)問(wèn)號(hào)
//注冊(cè)的 URL
https://shinonometn.com/
唉儿子,就是因?yàn)?JSAPI 的沙雕設(shè)計(jì)我加班到凌晨 2 點(diǎn)陪前端的人調(diào)試瓦哎。
參考鏈接
在Web應(yīng)用中接入微信支付的流程之極簡(jiǎn)清晰
微信開(kāi)發(fā),分享部分出現(xiàn)的問(wèn)題
微信支付:“當(dāng)前頁(yè)面的URL未注冊(cè)”