支付--微信APP支付

写在最前:关于文中涉及到隐私的部分已经隐去,只做技术分享。

先简单介绍一下背景,公司app要改成H5混合开发需要对原有的app后台进行移植,增加新的接

口功能满足新需求。在移植的时候我直接把原来的微信支付模块全部搬过来,前端那边微信支付一直没做

好,后台这边我也没法测试移植的模块能否正常支付。等前端把微信支付部分做好以后,我才开始测试这

部分代码,本想着之前app中都用得好好的,现在应该也没什么问题,实际上这时我已经掉进了微信支付

的坑里,给大家分享一下我爬过的坑,免得后来的人重蹈覆辙。

照例还是先简单介绍一下微信APP支付的思路:
1、从前台获取到用户提交的订单参数,生成第一次签名。
2、调用微信统一下单接口URL https://api.mch.weixin.qq.com/pay/unifiedorder 生成预支付id:
prepayId。
3、对参数进行二次签名。
4、将所需的数据传给前台,调起微信支付。
5、微信将将支付通知给后台。
6、后台执行回调操作,完成整个支付流程。

这是官方给出的流程:
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_3

这里写图片描述

本地测试,预支付id已经成功获取到了,说明1、2步都没有问题。

这里写图片描述

这里二次签名的参数也已经拿到,但是前台调起微信之后提示支付验证签名失败。

这里写图片描述

这里写图片描述


至此,我遇到的问题就出现在第三步,对参数进行二次签名,所有的参数都获取到了,传给前台调起微信一直提示签名无效。

然后我就围绕二次签名这里去找问题出现的原因。因为是二次移植,刚开始怀疑是微信开发者后台参数秘

钥这些的有误,登上后台看参数都没问题。然后想可能是微信的工具类中生成二次签名传的参数不全,把

该传的参数都传入以后还是同一个错误。最后我把目光转向二次签名的参数名称上面,想着这么重要的东

西应该不会出错吧,查阅官方文档在APP支付里的业务流程中发现了猫腻。

这里写图片描述
这里写图片描述

下面这图是该文档16年和17年不同时间的对比图,看完以后终于发现问题所在。之前项目做的时候,字段名是有大写的,现在再看已经全都改成小写了。而且发放在这么不起眼的地方,也没个提示啥的。真是给微信跪了,不带这么坑人的。

这里写图片描述

我把参数名称全改成小写以后,终于能够顺利完成支付了,说起来真是一把辛酸泪!

这里写图片描述


说完整个流程把部分支付代码贴出来,供大家参考。

商户号、商户秘钥、APP秘钥、回调地址等配置参数这些的就略过了,用的时候提前配置好就行。

这个代码段里构造请求参数。先构造第一次签名的参数,获得requestSign。然后构造xml参数调用微信统一下单接口URL获得预支付id。接着构造第二次签名的参数,调用同第一次的签名方法生成最终传给前台的sign签名参数。最后,将所有参数一并传给前台。

                        String currTime = TenpayUtil.getCurrTime();
                        //八位日期相关的字符串
                        String strTime = currTime.substring(8, currTime.length());
                        //四位随机数的字符串
                        String strRandom = TenpayUtil.buildRandom(4) + "";
                        //十位随机字符串
                        String nonceStr = strTime + strRandom; 
                        /**
                         * 商品描述,订单号,订单总金额从手机端获取
                         */
                        String spbillCreateIp = request.getRemoteAddr();
                        SortedMap<String, String> requestParams = new TreeMap<String, String>();
                        requestParams.put("appid", Constant.APP_ID);  
                        requestParams.put("mch_id", Constant.PARTNER);  
                        requestParams.put("nonce_str", nonceStr);  
                        requestParams.put("body", body);//商品描述  
                        requestParams.put("out_trade_no", outTradeNo);//订单号  
                        requestParams.put("total_fee", String.valueOf(prices));//订单总金额
                        requestParams.put("spbill_create_ip", spbillCreateIp);//APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器ip 
                        requestParams.put("notify_url", Constant.WALLET_NOTIFIY_URL);//通知地址  
                        requestParams.put("trade_type", "APP");//交易类型
                        RequestHandler reqHandler = new RequestHandler(request, response);
                        reqHandler.init(Constant.APP_ID, Constant.APP_SECRET, Constant.PARTNER_KEY);
                        String requestSign = reqHandler.createSign(requestParams);

                        //构造请求参数
                        String xml = "<xml>" 
                                + "<appid>" + Constant.APP_ID + "</appid>"
                                + "<mch_id>" + Constant.PARTNER + "</mch_id>"
                                + "<nonce_str>" + nonceStr + "</nonce_str>"
                                + "<sign>" + requestSign + "</sign>"
                                + "<body><![CDATA[" + body + "]]></body>"
                                + "<out_trade_no>" + outTradeNo + "</out_trade_no>"
                                + "<total_fee>" + prices + "</total_fee>"
                                + "<spbill_create_ip>" + spbillCreateIp + "</spbill_create_ip>"
                                + "<notify_url>" + Constant.WALLET_NOTIFIY_URL + "</notify_url>"
                                + "<trade_type>APP</trade_type>"
                                + "</xml>";
                        new GetWxOrderNo();
                        String  prepayId = GetWxOrderNo.getPayNo(Constant.PRE_URL, xml);//获得预支付单信息
                        System.out.println("获取到的预支付ID:" + prepayId);
                        /**
                         * 统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。
                         * 参与签名的字段名为appId,partnerId,prepayId,nonceStr,timeStamp,package。
                         * 注意:package的值格式为Sign=WXPay    
                         * ~~~~~~~~2017年微信文档上字段名都要小写!!!!! 
                         */
                        SortedMap<String, String> getParams = new TreeMap<String, String>();
                        String timeStamp = Sha1Util.getTimeStamp();
                        getParams.put("appid", Constant.APP_ID);
                        getParams.put("partnerid", Constant.PARTNER);
                        getParams.put("prepayid", prepayId);
                        getParams.put("noncestr", nonceStr);
                        getParams.put("timestamp", timeStamp);
                        getParams.put("package", "Sign=WXPay");

                        String getSign = reqHandler.createSign(getParams);
                        System.out.println("--微信充值 ---"+getSign);

                        map1.put("msg", "成功");//当统一下订单接口执行完成之后,返回调起支付接口所需要的所有参数给APP
                        map2.put("appId", Constant.APP_ID);
                        map2.put("partnerId", Constant.PARTNER);
                        map2.put("prepayId", prepayId);
                        map2.put("nonceStr", nonceStr);
                        map2.put("timeStamp", timeStamp);
                        map2.put("package", "Sign=WXPay");
                        map2.put("sign", getSign);

下图红框中的参数就是二次签名所需要的参数,一定要注意参数名称的大小写,目前官网参考文档都是小写,以后会不会再变还是未知数。还有需要注意的是timestamp时间戳一定要是10位,别问我为什么,因为官方就是这样规定的。

这里写图片描述

最后贴出工具类中比较重要的一个签名方法。

首先要对参数进行排序,在最末尾拼接上商户私钥,接着进行MD5加密编码格式为UTF-8,最后将加密后的参数转成大写,完成签名。

    /**
     * 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
     */
    public String createSign(SortedMap<String, String> packageParams) {
        StringBuffer sb = new StringBuffer();
        Set es = packageParams.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if (null != v && !"".equals(v) && !"sign".equals(k)
                    && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + this.getKey());
        System.out.println("md5 sb:" + sb);
        String sign = MD5Util.MD5Encode(sb.toString(), this.charset)
                .toUpperCase();
        System.out.println("packge签名:" + sign);
        return sign;

    }

将所有参数传给前台,完成微信支付,回调中验证支付完成,剩下的就写自己的业务逻辑,先略过了。至此,整个微信APP支付流程就走完了。

在实际开发中,可能每个人遇到的情况和我并不一定相同,仅给大家一个参考借鉴。希望能给困惑中你带来一些启发和帮助爬出微信支付的坑。

附上用到的工具类下载链接 点我下载

好了,有时间再把支付宝APP支付的坑也一填,整理出来分享给大家。

猜你喜欢

转载自blog.csdn.net/tonyfreak/article/details/75168747