關於微信公眾號開發-微信支付-無法支付的問題
前提:由於涉及公司業務,部分核心程式碼無法展示,這裡僅僅是聊一下如何解決微信公眾號支付無法支付的解決方案。
問題:微信公眾號平臺支付失敗。
頁面:大致頁面就是下面這張圖片(引自《公眾號支付開發者文件》中的"公眾號支付"-"場景介紹")所展示的那樣,可以選擇充值金額,可以點選立即充值,然後就可以進行充值了。
現象:
1、點選"立即充值"按鈕,頁面將會顯示微信支付慣有的灰色載入(我也只能形容成這樣了),然後一閃而過,無法進行正常的充值業務;
2、此充值頁面無法正常載入。表現為微信上方綠色進度條瞬間載入完成,但無法顯示正常的頁面,是一片白色的螢幕;
3、點選"立即充值"按鈕,頁面無跳轉,頁面無反應,頁面死活不動,死了。
排查步驟編號:
從這裡開始是我對於整個問題的排查流程,其中因為涉及3個問題,為了條理更加清晰,這裡的三大問題就用阿拉伯數字(1、2、3)表示,內在流程歸屬於1.1、1.2、1.3......3.1這樣的形式(這裡希望可以得到更好的分類建議,我這邊兒現在沒有太多的時間去查閱文章編號規則,寫論文的那點兒套路早就忘了)。
排查&解決:
1. 針對"點選'立即充值'按鈕,頁面將會顯示微信支付慣有的灰色載入(我也只能形容成這樣了),然後一閃而過,無法進行正常的充值業務"的問題解決。
1.1 起先排查後臺日誌,進入微信後臺模組所部署的生產環境內。拷貝當前日誌,通過vim命令檢視該日誌,依照客服提供的充值時間點進行排查,最終查到這樣一個錯誤:
<xml>
<return_code><![CDATA[FAIL]]></return_code>
<return_msg><![CDATA[invalid out_trade_no]]></return_msg>
</xml>
從字面意思可以看出這是在告訴我出現了非法的訂單號,這樣我又一次依據此日誌記錄向上排查其他日誌資訊,又發現了一個疑問(除卻紅色標註的out_trade_no,其餘內容資料已替換成《公眾號支付開發者文件》中的"API列表"-"統一下單"-"請求引數"提供的示例值
appid=wxd678efh567hg6787&
body=會員充值&
device_info=013467007045764&
mch_id=1230000109&
nonce_str=5K8264ILTKCH16CQ2502SI8ZNMTM67VS&
notify_url=http://www.weixin.qq.com/wxpay/pay.php&
openid=oUpF8uMuAJO_M2pxb1Q9zNjWeS6o&
out_trade_no=2018年04月08日17時1609001&
total_fee=1000&
trade_type=JSAPI&
key=KevenPotter
這個疑問就是發現我的訂單編號出現了中文字元,然而,在《公眾號支付開發者文件》中的"API列表"-"統一下單"中明文規定,out_trade_no(商戶訂單號)要求32個字元內,只能是數字、大小寫字母_-|* 且在同一個商戶號下唯一。
那麼也就是說我的"訂單號(out_trade_no)"確實出問題了;
1.2 定位問題後,開始檢視這個訂單號是如何生成的,怎麼會出現中文字元這種型別呢。因為有日誌資訊,那麼就去後臺程式碼中去依照日誌資訊找到這行出錯的程式碼,最後經過排查,定位到service層業務處理類。在此中業務處理類中,wxUnifiedOrder()方法內,這個out_trade_no就已經傳了進來。
這時候就考慮業務,充值這個行為是使用者行為,具體為使用者點選事件,那麼使用者為什麼點選,是因為有這個按鈕,那麼這個按鈕是在哪裡呢,理所當然的首先想到頁面。因為我們這個技術用到的是ionic3和cordova,所以也就立馬去找頁面觸發的這個方法內是什麼樣的業務處理邏輯。直到我看到ts檔案中out_trade_no是這樣定義的:
let out_trade_no = this.datePipe.transform(new Date(), 'yyyyMMddHHmmss') + xxx;
從這裡可以看到,當初寫這個方法的人確實在規避問題,進行了格式化,但是為什麼這塊格式化的程式碼並沒有起作用而是被汙染了,現在我也無法理解(有的同事說new Date()方法建立的是手機本地系統時間,有的手機系統時間就是中文格式,所以這裡的訂單號也就出現了中文)。而且到底是前端汙染還是後端格式化汙染無從排查(因為找了大量公司同事進行測試,都沒有發現錯誤訂單的發生,問題重現很難[能遇上這種問題的客戶,可以去買買彩票了~])。
現在先貼一下構建後端xml格式的程式碼,希望一些大神可以解釋一下String.format中的一些坑~
private String buildPayXml(String appid, String body, String mch_id, String nonce_str, String notify_url, String openid, String out_trade_no, String sign, String total_fee) {
String xmlStr = String.format(
"<xml>" +
"<appid><![CDATA[%s]]></appid>" +
"<body><![CDATA[%s]]></body>" +
"<device_info><![CDATA[XXX]]></device_info>" +
"<mch_id><![CDATA[%s]]></mch_id>" +
"<nonce_str><![CDATA[%s]]></nonce_str>" +
"<notify_url><![CDATA[%s]]></notify_url>" +
"<openid><![CDATA[%s]]></openid>" +
"<out_trade_no><![CDATA[%s]]></out_trade_no>" +
"<sign><![CDATA[%s]]></sign>" +
"<total_fee><![CDATA[%s]]></total_fee>" +
"<trade_type><![CDATA[JSAPI]]></trade_type>" +
"</xml>",
appid, body, mch_id, nonce_str, notify_url, openid, out_trade_no, sign, total_fee);
return xmlStr;
}
所以這裡的問題就是這個非法訂單的值是從前端傳過來的還是在後端格式化錯誤的,由此引發出兩種解決方案。
1.3 解決方案:
(1)、從前端頁面進行控制,不再使用之前的的格式化時間方式,而是重新建立一個方法叫做createTradeNo():
/**
* @Company {http://www.XXX.cn/}
* @author {KevenPotter}
* @description
* {Do not delete this method. This method is to increase for the number of users
* can not recharge by WeChat, because the formatting method before this method may result
* in illegal date format, which will lead to illegal order number of the user
* order Characters. This method is similar to a hard-coded effect and aims to
* forcibly obtain a numeric "year, month, and day" when creating a new Date class,
* rather than a wrong conversion by the previous formatting method.}
* @description
* {此方法請勿刪除.此方法的存在是為了處理部分使用者無法充值而增加的,因為此方法之前的格式化
* 方法可能會出現日期格式化非法的結果,這樣將會導致使用者訂單的訂單號出現非法字元.此方法屬於
* 類似硬編碼的效果,旨在當新建Date類時,強行獲取數字型的"年月日",而不是由之前的格式化方法
* 進行錯誤的轉換}
* @param {No Parameter}
* @returns {String}
*/
private createTradeNo(): string {
let dateNow = new Date();
let year: number = dateNow.getFullYear();
let month: string | number = (dateNow.getMonth() + 1) < 10 ? "0" + (dateNow.getMonth() + 1) : (dateNow.getMonth() + 1);
let day: string | number = dateNow.getDate() < 10 ? "0" + dateNow.getDate() : dateNow.getDate();
let hours: string | number = dateNow.getHours() < 10 ? "0" + dateNow.getHours() : dateNow.getHours();
let minutes: string | number = dateNow.getMinutes() < 10 ? "0" + dateNow.getMinutes() : dateNow.getMinutes().toString();
let seconds: string | number = dateNow.getSeconds() < 10 ? "0" + dateNow.getSeconds() : dateNow.getSeconds();
let out_trade_no: string = "" + year + month + day + hours + minutes + seconds + userId;
return out_trade_no;
}
這種方式類似於硬編碼的方式,就是強行獲取數字型年月日時分秒等值,然後轉換為字串進行傳參; (2)、從後端業務進行攔截,攔截的地方就是傳參的開始,我這裡採用網上比較通用的正則表示式的方式,只要是數字的就要,其他的剔除:
String regEx = "[^0-9]";
Pattern pattern = Pattern.compile(regEx);
Matcher matcher = pattern.matcher(out_trade_no);
String outTradeNo = matcher.replaceAll("").trim(); // 過濾後的訂單
1.4 這兩種方案出現之後,經過和同事商議,決定採用第二種解決方法,但不刪除第一種解決策略,如果第二種方法不可以,再採用第一種解決策略。經過部署於客戶反饋,微信充值問題大部分已解決(75%)。
2. 針對"頁面無法正常載入,微信上方綠色進度條瞬間載入完成,無法顯示正常的頁面,是一片白色的螢幕"的問題解決。
2.1 首先依據客服的反饋,我們在公司的內部進行了一次測試,目的是問題的重現。總共測試了20個手機,遺憾的是全部通過,指導硬體部門有一個人也想來做一下測試,這時發生了頁面白屏現象。我們後來進過對比,才發現這種情況的出現好似和微信暱稱有關聯。即,這個人的暱稱帶有特殊符號。
這時,我們專案經理指出,這應該是資料庫編碼出現了問題,特殊符號(emoji)無法存入。但是還需進行測驗,要在內部把問題重現出來。
2.2 搭建本地測試環境,進行測試(就是改變自己的微信暱稱同時加入emoji表情符號)。但是進行了大致四次的更換,還是無法重現問題。之後經過同事提醒發來了蘋果手機的表情,再次進行測試,問題重現~
重現問題之後,檢視日誌記錄,現貼上如下:
2018-04-23 22:59:06.432 INFO 120 --- [p-nio-80-exec-1] com.hh.rest.app.service.WechatService :
xml msg:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[oUpF8uMuAJO_M2pxb1Q9zNjWeS6o]]></FromUserName>
<CreateTime>1524495419</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
<EventKey><![CDATA[]]></EventKey>
</xml>
2018-04-23 22:59:06.521 INFO 120 --- [p-nio-80-exec-1] com.hh.rest.app.service.WechatService : enter subscribe
{"country":"中國","qr_scene":0,"subscribe":1,"city":"朝陽","openid":"oUpF8uMuAJO_M2pxb1Q9zNjWeS6o","tagid_list":[],"sex":1,"groupid":0,
"language":"zh_CN","remark":"","subscribe_time":1524495419,"province":"北京","subscribe_scene":"ADD_SCENE_QR_CODE","nickname":"口哈哈哈",
"headimgurl":"http://thirdwx.qlogo.cn/mmopen/iaXTwdhNbibo6cBH1GClwSgkEictOnsAN8v6JY6eB1O7ibddGXXn1iceAnZlrd8OiaqdWNAL1wGqPAc3ibDNBCQFqulvXwhEzHSnwJ8/132",
"qr_scene_str":""}
2018-04-23 22:59:06.917 INFO 120 --- [p-nio-80-exec-1] com.hh.rest.app.service.WechatService : userInfoObj:
{"country":"中國","qr_scene":0,"subscribe":1,"city":"朝陽","openid":"oUpF8uMuAJO_M2pxb1Q9zNjWeS6o","tagid_list":[],"sex":1,"groupid":0,
"language":"zh_CN","remark":"","subscribe_time":1524495419,"province":"北京","subscribe_scene":"ADD_SCENE_QR_CODE","nickname":"口哈哈哈",
"headimgurl":"http://thirdwx.qlogo.cn/mmopen/iaXTwdhNbibo6cBH1GClwSgkEictOnsAN8v6JY6eB1O7ibddGXXn1iceAnZlrd8OiaqdWNAL1wGqPAc3ibDNBCQFqulvXwhEzHSnwJ8/132",
"qr_scene_str":""}
Hibernate: select userentity0_.id as id1_13_, userentity0_.balance as balance2_13_, userentity0_.coupon as coupon3_13_, userentity0_.credit as credit4_13_, userentity0_.is_admin as is_admin5_13_, userentity0_.is_agent as is_agent6_13_, userentity0_.is_partner as is_partn7_13_, userentity0_.wx_icon as wx_icon8_13_, userentity0_.wx_name as wx_name9_13_, userentity0_.wx_open_id as wx_open10_13_, userentity0_.wx_subscribe_ts as wx_subs11_13_, userentity0_.wx_subscribed as wx_subs12_13_ from user userentity0_ where userentity0_.wx_open_id=?
Hibernate: insert into user (balance, coupon, credit, is_admin, is_agent, is_partner, wx_icon, wx_name, wx_open_id, wx_subscribe_ts, wx_subscribed) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2018-04-23 22:59:06.970 WARN 120 --- [p-nio-80-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1366, SQLState: HY000
2018-04-23 22:59:06.970 ERROR 120 --- [p-nio-80-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : Incorrect string value: '\xF0\x9F\x8D\xB4\xE5\x93...' for column 'wx_name' at row 1
2018-04-23 22:59:06.993 ERROR 120 --- [p-nio-80-exec-1] c.h.r.a.e.GlobalExceptionHandler : /rest/wechat/checkSign?signature=f3cac2272ab3f442b32d8560f024919240ab96e1×tamp=1524495419&nonce=451675791&openid=oUpF8uMuAJO_M2pxb1Q9zNjWeS6o; Error: could not execute statement; nested exception is org.hibernate.exception.GenericJDBCException: could not execute statement
2018-04-23 22:59:07.008 WARN 120 --- [p-nio-80-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved exception caused by Handler execution: org.springframework.orm.jpa.JpaSystemException: could not execute statement; nested exception is org.hibernate.exception.GenericJDBCException: could not execute statement
我們從日誌顯示上來看,其實就可以看出來,所有的使用者資訊都已正常獲取,但是之後卻有兩個ERROR(紅字標註部分)報錯。其中第一條ERROR告訴我們SQL語句錯誤,那麼第二條ERROR提示的更加明顯(向wx_name欄位插入了不正確的字串值)。那麼,從網上借鑑的解決方法來看,確實是資料庫編碼問題,無法存入emoji特殊表情。
2.3 解決方案:
依據網上的解決方法,我們在測試環境下(我們使用的資料庫的版本為MySQL5.6.39)修改my.cnf(Linux下為my.cnf,Windows下為my.ini)資料庫的配置檔案,在下面新增(無則新增,有則修改):
[client]
default-character-set = utf8mb4
[mysql]
default-character-set = utf8mb4
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
修改完資料庫全域性配置之後,再修改我們測試庫的編碼為utf8mb4,同時再修改emoji特殊符號所存入欄位wx_name的編碼為utf8mb4,此時,進行本地測試,問題不再出現,之後,在生產環境同樣應用上述配置,問題解決。從這裡我們其實可以看出更多的問題,就是現如今,已然出現了更好的編碼方式,而公司內部,依舊使用的是舊有的編碼模式,而不考量日後的擴充套件。說的與時俱進,其實也是一種換湯不換藥的死硬做法,這是我們需要警惕的。現貼出成功後的日誌記錄:
2018-04-23 23:07:54.218 INFO 120 --- [p-nio-80-exec-9] com.hh.rest.app.service.WechatService :
xml msg:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[oUpF8uMuAJO_M2pxb1Q9zNjWeS6o]]></FromUserName>
<CreateTime>1524495947</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
<EventKey><![CDATA[]]></EventKey>
</xml>
2018-04-23 23:07:54.226 INFO 120 --- [p-nio-80-exec-9] com.hh.rest.app.service.WechatService : enter subscribe
{"country":"中國","qr_scene":0,"subscribe":1,"city":"朝陽","openid":"oUpF8uMuAJO_M2pxb1Q9zNjWeS6o","tagid_list":[],"sex":1,"groupid":0,"language":"zh_CN","remark":"","subscribe_time":1524495947,"province":"北京","subscribe_scene":"ADD_SCENE_QR_CODE","nickname":"口哈哈哈","headimgurl":"http://thirdwx.qlogo.cn/mmopen/iaXTwdhNbibo6cBH1GClwSgkEictOnsAN8v6JY6eB1O7ibddGXXn1iceAnZlrd8OiaqdWNAL1wGqPAc3ibDNBCQFqulvXwhEzHSnwJ8/132","qr_scene_str":""}
2018-04-23 23:07:54.542 INFO 120 --- [p-nio-80-exec-9] com.hh.rest.app.service.WechatService : userInfoObj: {"country":"中國","qr_scene":0,"subscribe":1,"city":"朝陽","openid":"oUpF8uMuAJO_M2pxb1Q9zNjWeS6o","tagid_list":[],"sex":1,"groupid":0,"language":"zh_CN","remark":"","subscribe_time":1524495947,"province":"北京","subscribe_scene":"ADD_SCENE_QR_CODE","nickname":"口哈哈哈","headimgurl":"http://thirdwx.qlogo.cn/mmopen/iaXTwdhNbibo6cBH1GClwSgkEictOnsAN8v6JY6eB1O7ibddGXXn1iceAnZlrd8OiaqdWNAL1wGqPAc3ibDNBCQFqulvXwhEzHSnwJ8/132","qr_scene_str":""}
Hibernate: select userentity0_.id as id1_13_, userentity0_.balance as balance2_13_, userentity0_.coupon as coupon3_13_, userentity0_.credit as credit4_13_, userentity0_.is_admin as is_admin5_13_, userentity0_.is_agent as is_agent6_13_, userentity0_.is_partner as is_partn7_13_, userentity0_.wx_icon as wx_icon8_13_, userentity0_.wx_name as wx_name9_13_, userentity0_.wx_open_id as wx_open10_13_, userentity0_.wx_subscribe_ts as wx_subs11_13_, userentity0_.wx_subscribed as wx_subs12_13_ from user userentity0_ where userentity0_.wx_open_id=?
Hibernate: insert into user (balance, coupon, credit, is_admin, is_agent, is_partner, wx_icon, wx_name, wx_open_id, wx_subscribe_ts, wx_subscribed) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
此時,微信充值問題絕大大部分解決(95%)。
3. 針對"點選立即充值,頁面無跳轉,頁面無反應,頁面死活不動,死了"的問題......
關於這個問題,我們沒有解決,不過現在問題定位很是明晰,這個問題的出現,99%的使用者使用的是蘋果手機,其版本為9抑或是9以下。關於這個問題,原諒我們能力有限,無法去解決。
問題解決,這裡一筆帶過,因為原因特別簡單,TBS服務(騰訊瀏覽服務)的核心基線升級,導致了Angular的在頁面模板中的管道功能失效。
例如,原先我們在頁面模板中所使用的程式碼為
<span style="font-size: 30px;">{{pageDto.balance+0 | number:'1.0-1'}}</span>
經過修改後的程式碼為
<span style="font-size: 30px;">{{pageDto.balance}}</span>
這樣,想要展示的值由後臺Java進行格式化再返回也是可以的(我幾天前做技術培訓,講的是《初探前後端分離》,表達了,前後端分離的最大好處就是可以平衡壓力,當然,我知道分工明確也是很大的優點[前者對物,後者對人],但是我認為其中的一大亮點就是後端僅僅提供原始資料,而前端可以進行資料過濾,這樣可以達到一種"生態平衡",奈何這種"平衡"現在變得不是那麼平衡)。
好的,謝謝觀看~~~