关于微信公众号开发-微信支付-没法支付的问题

前提:因为涉及公司业务,部分核心代码没法展现,这里仅仅是聊一下如何解决微信公众号支付没法支付的解决方案。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进行格式化再返回也是能够的(我几天前作技术培训,讲的是《初探先后端分离》,表达了,先后端分离的最大好处就是能够平衡压力,固然,我知道分工明确也是很大的优势[前者对物,后者对人],可是我认为其中的一大亮点就是后端仅仅提供原始数据,而前端能够进行数据过滤,这样能够达到一种"生态平衡",奈何这种"平衡"如今变得不是那么平衡)。


        好的,谢谢观看~~~