前提:由于涉及公司业务,部分核心代码无法展示,这里仅仅是聊一下如何解决微信公众号支付无法支付的解决方案。
问题:微信公众号平台支付失败。
页面:大致页面就是下面这张图片(引自《公众号支付开发者文档》中的"公众号支付"-"场景介绍")所展示的那样,可以选择充值金额,可以点击立即充值,然后就可以进行充值了。
现象:
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():