嘮叨幾句
因為被微信那個破爛文檔坑了我兩個星期,導(dǎo)致項目進度慢了很多。本來微信的 API 的確是設(shè)計得爛,但爛我也覺得不要緊了,文檔也爛那我就真的火了,跟人捉迷藏似的東一塊西一塊(玩猜謎嗎你)。這里也記錄一下我做開發(fā)遇到的坑。
如何申請公眾號以及商戶平臺不在本文范疇內(nèi),因為項目經(jīng)理已經(jīng)拿到這這些東西了,我所做的就是完成代碼的編寫。
環(huán)境
這里使用了 com.github.binarywang 的 jar,下面默認都是在這個前期下討論。其他自己實現(xiàn)的或者其他人的庫請配合文檔和其他人分享的資料看。
<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>
正文
首先有一個十分重要的提醒:
配合前端開發(fā)人員測試微信支付的時候千萬千萬不能用微信的沙箱。
那個沙箱沒有他們文檔說的那么厲害,只能確認微信回調(diào)我們服務(wù)器沒問題,不能用來模擬測試整個支付流程,而且這個沙箱設(shè)計得十分垃圾,連沙箱都算不上(支付金額只允許用例內(nèi)的。你見過只能畫指定幾個圖案的沙箱嗎??),所以還是乖乖地測一次給一分錢吧。
開發(fā)
一般開發(fā)主要用到微信的兩種支付方式
- JSAPI : 用于在微信自己的瀏覽器里面喚起微信支付
- NATIVE : 用于掃碼支付
JSAPI 不用多說,就是觸發(fā)之后會喚起微信的支付對話框給用戶選擇支付與否。
NATIVE 就是生成訂單之后給用戶用微信掃碼付款的。這個方式我看了文檔,微信給本來的設(shè)計貌似是給那些自動販賣機用的,不過稍微改變一下使用方法就可以適用于任意掃碼支付。
JSAPI 和 NATIVE 兩種支付方式均使用統(tǒng)一下單接口先獲取微信預(yù)付款訂單信息,然后再進行剩下的操作。兩者傳參幾乎一樣,不同的是:
- JSAPI 需要傳入 openId,NATIVE 不需要。
- NATIVE 需要傳入 productId,JSAPI 不需要。
上面所說的稍微改變一下使用方法,就是在 NATIVE 支付的時候 productId 使用自己的訂單 ID 就好了。
NATIVE 支付方式
首先說這個支付方式是因為,這個方式很簡單,而且我也很推薦用這個,但掃碼就需要用另外一臺手機了。
根據(jù)微信的文檔向統(tǒng)一下單接口傳入相應(yīng)的參數(shù)之后,就得到預(yù)付款訂單了。這里得到的預(yù)付款訂單包含了參數(shù) codeUrl ,傳這個給前端開發(fā)的去生成二維碼或者自己服務(wù)器端生成二維碼都可以,掃碼之后就可以用微信付款。付款成功之后,微信會通過預(yù)先指定的回調(diào) API 發(fā)送訂單支付的結(jié)果。根據(jù)結(jié)果完成自己的訂單邏輯這個就不說了。
JSAPI 支付方式
我覺得這個就是最坑爹的地方了。
首先傳入?yún)?shù)之后,微信返回了預(yù)付款訂單信息,然后這個信息需要返回給前端。但是,這之前需要對預(yù)付款訂單的某些字段拼接起來,作一次簽名,簽名需要嚴格按照字段序排序以及注意大小寫:
appIdnonceStrpackagesignTypetimeStamp
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}
(真心對微信大小寫隨便來表示很無語)其中
nonceStrprepay_id
需要與微信返回的預(yù)付款訂單內(nèi)的一致。timeStamp 也要傳給前端,到時候前端需要把這個 timeStamp 傳進喚起微信支付對話框的函數(shù)。
拼接好這個之后,再在后面拼接參數(shù) key 并進行一次 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ù)簽名的時間",
"prepay_sign":"訂單簽名結(jié)果"
}
這時候不要急著去喚起微信的支付窗口,因為還有后面一系列步驟。
這里需要注意這些返回的參數(shù):
nonceStrtimeStamp
這兩個參數(shù)在整個支付的過程中要一致,而且參數(shù)大小寫也需要注意。流程內(nèi)的 API 有的地方給弄駝峰命名法有的地方則用全小寫。
然后,前端需要再拿當(dāng)前調(diào)用 JSAPI 支付的瀏覽器地址欄的路徑,向服務(wù)器請求一個簽名,這個簽名就是前端喚起 JSAPI 所需要的簽名,我這里請求的 API 以及示例如下:
POST -> https://shinonometn.com/WC/ticket
{
"url":"https://shinonometn.com/?#/order/11",
"nonceStr":"8897djsk09ll",
"timeStamp":1560789
}
url 那里一定一定要注意,對于 SPA 應(yīng)用,路由前綴那里,絕對不能只有一個#,微信的這個安全機制很傻屄。首先你需要去商戶平臺那里注冊 JSAPI 支付允許的“支付目錄”(我暈,目錄),然后在調(diào)用 JSAPI 支付的時候他們會校驗?zāi)愕刂窓凇痹凇安弧痹凇耙炎缘摹爸Ц赌夸洝保辉诰途芙^下單。我猜他們這個機制是做給服務(wù)器端渲染頁面的應(yīng)用做的:你會發(fā)現(xiàn)你的 SPA 應(yīng)用拿到的 URL 經(jīng)常跟他們微信拿到的 URL 不匹配,從而一直提示你 URL 未注冊 然后拒絕下單。這其實不算坑,文檔在很隱蔽的地方提及需要前端拿這個 sign 去調(diào)用 JSAPI 支付 and 支付前需要調(diào)用 config 一次才是坑死人。
這個簽名是這樣的,如下參數(shù)全小寫,嚴格按照字典順序排序:
-
jsapi_ticket(我一直不知道這個東西的存在,因為文檔里面沒有提及) -
noncestr(小寫,小心) -
timestamp(小寫,小心) -
url(就是上面提及的 URL)
然后如此拼接:
jsapi_ticket={你拿到的 JSAPI TICKET}&noncestr={訂單上的 nonceStr}×tamp={你訂單的 timeStamp}&url={URL}
//不算大括號,只是為了好看加上去的
然后對這這個拼接好的字符串,SHA1 一次,拿小寫的字符串,返回給前端,那么前端就可以很愉快地填上對應(yīng)的參數(shù)去 wx.config 一下,喚起微信支付窗口了。
那么這個 URL 在商戶平臺注冊的時候需要注意什么呢?對于服務(wù)器端渲染頁面,你需要填寫支付頁面的地址,刪掉最后的”目錄“:
//訂單支付頁面
https://shinonometn.com/order/pay/1
//注冊的 URL
https://shinonometn.com/order/pay/
對于 SPA (單頁應(yīng)用)來說,你只需要填寫你的應(yīng)用地址,路由那里怎么方便怎么做手腳。
//帶上路由的 SPA 的頁面
https://shinonometn.com/?#/order/pay/1
^我就弄了個問號
//注冊的 URL
https://shinonometn.com/
唉,就是因為 JSAPI 的沙雕設(shè)計我加班到凌晨 2 點陪前端的人調(diào)試。
參考鏈接
在Web應(yīng)用中接入微信支付的流程之極簡清晰
微信開發(fā),分享部分出現(xiàn)的問題
微信支付:“當(dāng)前頁面的URL未注冊”