關於微信公衆號開發-微信支付-沒法支付的問題

前提:因爲涉及公司業務,部分核心代碼沒法展現,這裏僅僅是聊一下如何解決微信公衆號支付沒法支付的解決方案。javascript

問題:微信公衆號平臺支付失敗。php

頁面:大體頁面就是下面這張圖片(引自《公衆號支付開發者文檔》中的"公衆號支付"-"場景介紹")所展現的那樣,能夠選擇充值金額,能夠點擊當即充值,而後就能夠進行充值了。html

現象前端

        一、點擊"當即充值"按鈕,頁面將會顯示微信支付慣有的灰色加載(我也只能形容成這樣了),而後一閃而過,沒法進行正常的充值業務;java

        二、此充值頁面沒法正常加載。表現爲微信上方綠色進度條瞬間加載完成,但沒法顯示正常的頁面,是一片白色的屏幕;mysql

        三、點擊"當即充值"按鈕,頁面無跳轉,頁面無反應,頁面死活不動,死了。正則表達式

排查步驟編號spring

        從這裏開始是我對於整個問題的排查流程,其中由於涉及3個問題,爲了條理更加清晰,這裏的三大問題就用阿拉伯數字(一、二、3)表示,內在流程歸屬於1.一、1.二、1.3......3.1這樣的形式(這裏但願能夠獲得更好的分類建議,我這邊兒如今沒有太多的時間去查閱文章編號規則,寫論文的那點兒套路早就忘了)。
sql

排查&解決數據庫

        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進行格式化再返回也是能夠的(我幾天前作技術培訓,講的是《初探先後端分離》,表達了,先後端分離的最大好處就是能夠平衡壓力,固然,我知道分工明確也是很大的優勢[前者對物,後者對人],可是我認爲其中的一大亮點就是後端僅僅提供原始數據,而前端能夠進行數據過濾,這樣能夠達到一種"生態平衡",奈何這種"平衡"如今變得不是那麼平衡)。


        好的,謝謝觀看~~~