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


Posted in Javascript onDecember 28, 2018

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

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

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

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

现象:

        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的在页面模板中的管道功能失效。

例如,原先我们在页面模板中所使用的代码为

{{pageDto.balance+0 | number:'1.0-1'}}

经过修改后的代码为

{{pageDto.balance}}

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
可输入的下拉框
Jun 19 Javascript
BOM与DOM的区别分析
Oct 26 Javascript
js实现屏幕自适应局部代码分享
Jan 30 Javascript
javascript中typeof操作符和constucor属性检测
Feb 26 Javascript
JavaScript中几种排序算法的简单实现
Jul 29 Javascript
VUE JS 使用组件实现双向绑定的示例代码
Jan 10 Javascript
通过学习bootstrop导航条学会修改bootstrop颜色基调
Jun 11 Javascript
微信小程序中post方法与get方法的封装
Sep 26 Javascript
vue2.0 自定义 饼状图 (Echarts)组件的方法
Mar 02 Javascript
Vue引入sass并配置全局变量的方法
Jun 27 Javascript
vue通过cookie获取用户登录信息的思路详解
Oct 30 Javascript
k8s node节点重新加入master集群的实现
Feb 22 Javascript
小程序实现抽奖动画
Apr 16 #Javascript
微信小程序实现文字从右向左无限滚动
Nov 18 #Javascript
angular4自定义组件非input元素实现ngModel双向数据绑定的方法
Dec 28 #Javascript
小程序文字跑马灯效果
Dec 28 #Javascript
JS实现的图片选择顺序切换和循环切换功能示例【测试可用】
Dec 28 #Javascript
Angular使用Restful的增删改
Dec 28 #Javascript
原生js实现公告滚动效果
Jan 10 #Javascript
You might like
php中实现简单的ACL 完结篇
2011/09/07 PHP
ThinkPHP上使用多说评论插件的方法
2014/10/31 PHP
PHP中strcmp()和strcasecmp()函数字符串比较用法分析
2016/01/07 PHP
php文件上传类完整实例
2016/05/14 PHP
php中的抽象方法和抽象类
2017/02/14 PHP
PHP isset empty函数相关面试题及解析
2020/12/11 PHP
javascript预览上传图片发现的问题的解决方法
2010/11/25 Javascript
仿新浪微博返回顶部的jquery实现代码
2012/10/01 Javascript
javascript使用isNaN()函数判断变量是否为数字
2013/09/21 Javascript
js中substr,substring,indexOf,lastIndexOf的用法小结
2013/12/27 Javascript
JS实现横向拉伸动感伸缩菜单效果代码
2015/09/04 Javascript
jquery easyui如何实现格式化列
2017/07/30 jQuery
jQuery Ajax向服务端传递数组参数值的实例代码
2017/09/03 jQuery
Vue代码整洁之去重方法整理
2019/08/06 Javascript
[04:59]2018DOTA2亚洲邀请赛 4.7 Mineski夺冠时刻
2018/04/09 DOTA
python 动态生成变量名以及动态获取变量的变量名方法
2019/01/20 Python
200行python代码实现2048游戏
2019/07/17 Python
基于python实现的百度音乐下载器python pyqt改进版(附代码)
2019/08/05 Python
Python 仅获取响应头, 不获取实体的实例
2019/08/21 Python
基于python实现语音录入识别代码实例
2020/01/17 Python
Python cookie的保存与读取、SSL讲解
2020/02/17 Python
Python网络爬虫信息提取mooc代码实例
2020/03/06 Python
如何基于Python Matplotlib实现网格动画
2020/07/20 Python
南非最受欢迎的时尚品牌:MRP
2016/09/18 全球购物
西铁城美国官方网站:Citizen Watch美国
2019/11/08 全球购物
英国在线玫瑰专家:InterRose
2019/12/01 全球购物
JNI的定义
2012/11/25 面试题
华为python面试题
2016/05/03 面试题
深入开展党的群众路线教育实践活动方案
2014/02/04 职场文书
毕业实习评语
2014/02/10 职场文书
公司募捐倡议书
2014/05/14 职场文书
领导干部群众路线教育实践活动个人对照检查材料
2014/09/23 职场文书
专业技术职务聘任证明
2015/03/02 职场文书
工作感言一句话
2015/08/01 职场文书
Java实现经典游戏泡泡堂的示例代码
2022/04/04 Java/Android
详解Vue3使用axios的配置教程
2022/04/29 Vue.js