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


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 相关文章推荐
JS 有名函数表达式全面解析
Mar 19 Javascript
Javascript中call的两种用法实例
Dec 13 Javascript
jquery显示隐藏input对象
Jul 21 Javascript
PHP实现的各种中文编码转换类分享
Jan 23 Javascript
ECMAScript6新增值比较函数Object.is
Jun 12 Javascript
Vuejs第十一篇组件之slot内容分发实例详解
Sep 09 Javascript
Angular.js基础学习之初始化
Mar 10 Javascript
关于JavaScript中的this指向问题总结篇
Jul 23 Javascript
Vue实现远程获取路由与页面刷新导致404错误的解决
Jan 31 Javascript
elementUI select组件使用及注意事项详解
May 29 Javascript
layui 对弹窗 form表单赋值的实现方法
Sep 04 Javascript
vue配置型表格基于el-table拓展之table-plus组件
Apr 12 Vue.js
小程序实现抽奖动画
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
Syphon 使用方法
2021/03/03 冲泡冲煮
smarty实例教程
2006/11/19 PHP
详解YII关联查询
2016/01/10 PHP
Laravel框架使用monolog_mysql实现将系统日志信息保存到mysql数据库的方法
2018/08/16 PHP
laravel框架中视图的基本使用方法分析
2019/11/23 PHP
Javascript Tab 导航插件 (23个)
2009/06/11 Javascript
用JQuery 实现AJAX加载XML并解析的脚本
2009/07/25 Javascript
js中eval()函数和trim()去掉字符串左右空格应用
2013/02/02 Javascript
jQuery 获取和设置select下拉框的值实现代码
2013/11/08 Javascript
Easyui form combobox省市区三级联动
2016/01/13 Javascript
一步步教大家编写酷炫的导航栏js+css实现
2016/03/14 Javascript
JS随机打乱数组的方法小结
2016/06/22 Javascript
Bootstrap框架实现广告轮播效果
2016/11/28 Javascript
利用浮层使select不可选的实现方法
2016/12/03 Javascript
js按条件生成随机json:randomjson实现方法
2017/04/07 Javascript
在JavaScript中如何访问暂未存在的嵌套对象
2019/06/18 Javascript
微信小程序中为什么使用var that=this
2019/08/27 Javascript
p5.js临摹旋转爱心
2019/10/23 Javascript
javascript+css实现进度条效果
2020/03/25 Javascript
[55:56]NB vs Infamous 2019国际邀请赛淘汰赛 败者组 BO3 第二场 8.22
2019/09/05 DOTA
[02:03]完美世界DOTA2联赛10月30日赛事集锦
2020/10/31 DOTA
Python对列表排序的方法实例分析
2015/05/16 Python
Windows中安装使用Virtualenv来创建独立Python环境
2016/05/31 Python
python pip源配置,pip配置文件存放位置的方法
2019/07/12 Python
Python秒算24点实现及原理详解
2019/07/29 Python
Python中生成一个指定长度的随机字符串实现示例
2019/11/06 Python
Django自定义列表 models字段显示方式
2020/04/03 Python
锐步英国官网:Reebok英国
2019/11/29 全球购物
护理自我鉴定范文
2013/10/06 职场文书
员工工作表现评语
2014/04/26 职场文书
单方离婚协议书范本2014
2014/10/28 职场文书
营业用房租赁协议书
2014/11/26 职场文书
高中生个性发展自我评价
2015/03/09 职场文书
法制主题班会教案
2015/08/13 职场文书
html+css实现文字折叠特效实例
2021/06/02 HTML / CSS
攻略丨滑雪究竟该选哪款对讲机?
2022/02/18 无线电