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

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/KevenPotter/article/details/80054031

前提:由于涉及公司业务,部分核心代码无法展示,这里仅仅是聊一下如何解决微信公众号支付无法支付的解决方案。

问题:微信公众号平台支付失败。

页面:大致页面就是下面这张图片(引自《公众号支付开发者文档》中的"公众号支付"-"场景介绍")所展示的那样,可以选择充值金额,可以点击立即充值,然后就可以进行充值了。

现象

        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进行格式化再返回也是可以的(我几天前做技术培训,讲的是《初探前后端分离》,表达了,前后端分离的最大好处就是可以平衡压力,当然,我知道分工明确也是很大的优点[前者对物,后者对人],但是我认为其中的一大亮点就是后端仅仅提供原始数据,而前端可以进行数据过滤,这样可以达到一种"生态平衡",奈何这种"平衡"现在变得不是那么平衡)。


        好的,谢谢观看~~~

展开阅读全文

没有更多推荐了,返回首页