首页 > 代码库 > Android中微信支付的流程(从请求统一支付接口到真正调起微信支付)

Android中微信支付的流程(从请求统一支付接口到真正调起微信支付)

  在公司做一款电商类的软件,接入支付是必不可少的环节。继上一次集成支付宝以后,微信支付又开启了另一段痛苦的历程。由于以前没有做过微信支付,所以这次在做的过程中还是遇到很大的问题。而且,公司目前没有自己的后台,所有的接口都是外包来承接的,在遇到问题时,外包一般会说,这是封装好的,以前都没有问题。然后,你只能自己查找原因,废话不多说,简单记录一下集成微信的整个过程。

1.微信支付的签名问题(包括微信的分享)

  虽然关于微信的签名是个老生常谈的问题了,但是在这里我还是想要简单的描述一下.首先,要得到一个签名,你得先有一个自己的应用(Android版).这就需要你到微信的开放平台上申请一个帐号,然后认证你的开发资质(这一步是不是必须我不太清楚),最后创建一个应用,进行应用的审核(这里需要填写你的应用包名和签名,当然这个后期也是可以修改的.这里的签名你可以在androidstudio上先对你的module进行签名,然后可以在微信的网站上下一个查看签名的工具,安装到手机上,输入你应用的包名,就可以查看你应用的签名了.查看签名工具的下载地址:
https://open.weixin.qq.com/zh_CN/htmledition/res/dev/download/sdk/Gen_Signature_Android.apk).审核通过以后,你就可以拿到你的appid和你的appsecreat(应该是通过以后,当然也可能不是).然后,你就需要申请开通app支付的功能,这期间就需要你作为一个商户之类的一些认证啊之类的东西,都是比较繁琐的.这些完成以后,就可以准备进行支付了.

2.导入微信支付的sdk
  参照这里: http://www.jianshu.com/p/c97639279d2e

3.关于微信支付的请求统一接口以及二次签名
  在完成上述的步骤以后,发现在调起微信支付的时候,还是会出现闪退的情况.于是就猜测会不会是后台返回给我的参数有问题,在这里跟后台核实了appid,partnerid,appsecret等参数.最后无果,还是在后台的一句以前都没有问题下无疾而终.所以在这种情况下,只能自己向微信去请求数据来得到自己需要的数据了(其实我是不想这么做的,因为以前没有接触过微信支付,但是在远程后台懒得管的情况下,只能自己去验证了).
  首先,还是查阅了微信的官方文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
  我承认,虽然我也看了几遍文档,可是对于怎么去完成请求还是不太理解.于是通过查阅网上各种信息,现总结如下:
  
3.1 准备工作
  在你请求微信的统一支付的接口时,有几个参数是必须的,微信的文档上已经罗列出来.
  (1) appid 应用的id 例如:wxd678efh567hg6787
  (2) mch_id 商户的id 例如:1230000109 (申请开通支付功能关联的商户的id)
  (3) nonce_str 随机字符串 例如:5K8264ILTKCH16CQ2502SI8ZNMTM67VS
  生成随机字符串你需要写一个方法,例如:

//一个10000以内的随机整数,并进行MD5加密
private String getNonce_str() {
    return MD5Utils.getMd5(new Random().nextInt(10000) + "");
}

  (4) body 商品描述 例如:”ceshishangpin”(不确定这里是不是涉及到转码的问题,所以用的是字母)
  (5) out_trade_no 订单号 例如:20150806125346(自家平台生成的订单号)
  (7) spbill_create_ip 终端ip 例如:123.12.12.123 (生成订单时设备的ip地址,我测试用的本机ip)
  (8) notify_url 通知地址 例如:http://www.weixin.qq.com/wxpay/pay.php(这个地址还是要后台给你的,测试的话随便填也行吧应该,可以试试,不能包含特殊字符)
  (9) trady_type 交易类型 例如:APP
  (10) sign 签名 例如:C380BEC2BFD727A4B6845133519F3AD6 (这是微信的第一次签名,在这里你又需要写到一个方法了,MD5Utils中是用的UTF-8的编码方式,请自行准备这个工具类)

    public  String createSign(SortedMap<Object, Object> parameters) {
    StringBuffer sb = new StringBuffer();
    Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序)
    Iterator it = es.iterator();
    while (it.hasNext()) {
        Map.Entry entry = (Map.Entry) it.next();
        String k = (String) entry.getKey();
        Object v = entry.getValue();
        if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
            sb.append(k + "=" + v + "&");
        }
    }
    //这个partnerkey是需要自己进行设置的,要登陆你的微信的商户帐号(注意是商户,不是开放平台帐号),然后到api什么接口安全之类的那去设置,然后获取到
    sb.append("key=" + Constant.WEIXIN_PARTERKEY);
    Log.e("TAG", sb.toString());
    String sign = MD5Utils.getMd5(sb.toString()).toUpperCase();
    Log.e("TAG", "sign的值为" + sign);
    return sign;
}

可能有人要问了,你这个集合是个什么东西呢?我就是在别人那抄过来的其实…..

     //参数:开始生成签名(这个类把这些参数封装到了一起)
    Unifiedorder unifiedorder = new Unifiedorder();
    final SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
    parameters.put("appid", Constant.WEIXIN_APPID);
    unifiedorder.setAppid(Constant.WEIXIN_APPID);
    parameters.put("mch_id", Constant.WEIXIN_PARTERID);
    unifiedorder.setMch_id(Constant.WEIXIN_PARTERID);
    //上面提到的获取随机数的方法
    final String nonce_str = getNonce_str();
    parameters.put("nonce_str", nonce_str);
    unifiedorder.setNonce_str(nonce_str);
    parameters.put("body", "ceshiweixinqianming");
    unifiedorder.setBody("ceshiweixinqianming");
    //order_id就是订单号
    parameters.put("out_trade_no", order_id);
    unifiedorder.setOut_trade_no(order_id);
    //总金额
    parameters.put("total_fee", 1);
    unifiedorder.setTotal_fee("1");
    //ip地址
    parameters.put("spbill_create_ip", "123.123.123.123");
    unifiedorder.setSpbill_create_ip("123.123.123.123");
    //支付成功的回调地址
    String notify_url = "http://www.baidu.com/xxxx";
    parameters.put("notify_url", notify_url);
    unifiedorder.setNotify_url(notify_url);
    parameters.put("trade_type", "APP");
    unifiedorder.setTrade_type("APP");
    //这里就是用上面的方法生成的sign值了
    String sign = createSign(parameters);
    unifiedorder.setSign(sign);

还是把这个封装的类贴出来吧,毕竟搬砖也是挺累的……

    //封装请求微信支付参数的bean类
    class Unifiedorder {
    private String appid;
    private String mch_id;
    private String nonce_str;
    private String sign;
    private String body;
    private String out_trade_no;
    private String total_fee;
    private String spbill_create_ip;
    private String time_start;
    private String notify_url;
    private String trade_type;

    public String getAppid() {
        return appid;
    }

    public void setAppid(String appid) {
        this.appid = appid;
    }

    public String getMch_id() {
        return mch_id;
    }

    public void setMch_id(String mch_id) {
        this.mch_id = mch_id;
    }

    public String getNonce_str() {
        return nonce_str;
    }

    public void setNonce_str(String nonce_str) {
        this.nonce_str = nonce_str;
    }

    public String getSign() {
        return sign;
    }

    public void setSign(String sign) {
        this.sign = sign;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public String getOut_trade_no() {
        return out_trade_no;
    }

    public void setOut_trade_no(String out_trade_no) {
        this.out_trade_no = out_trade_no;
    }

    public String getTotal_fee() {
        return total_fee;
    }

    public void setTotal_fee(String total_fee) {
        this.total_fee = total_fee;
    }

    public String getSpbill_create_ip() {
        return spbill_create_ip;
    }

    public void setSpbill_create_ip(String spbill_create_ip) {
        this.spbill_create_ip = spbill_create_ip;
    }

    public String getTime_start() {
        return time_start;
    }

    public void setTime_start(String time_start) {
        this.time_start = time_start;
    }

    public String getNotify_url() {
        return notify_url;
    }

    public void setNotify_url(String notify_url) {
        this.notify_url = notify_url;
    }

    public String getTrade_type() {
        return trade_type;
    }

    public void setTrade_type(String trade_type) {
        this.trade_type = trade_type;
    }
}

好了,sign值也设置到bean类中了,下面要做的就是按照微信要求的格式把这个bean类中的信息传给他,这时候你又需要另外的一个方法了.

    //这个方法中需要注意的是,你在这个方法中拼接的参数,要和上面你已经赋给bean类的参数相一致,不能多也不能少,不然会出现签名错误的

    //请求微信的统一支付接口时需要用到的字符串信息
    String xmlInfo = xmlInfo(unifiedorder);
    //微信的统一支付接口的地址
    String wxUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    public  String xmlInfo(Unifiedorder unifiedorder) {
    if (unifiedorder != null) {
        StringBuffer bf = new StringBuffer();
        bf.append("<xml>");

        bf.append("<appid><![CDATA[");
        bf.append(unifiedorder.getAppid());
        bf.append("]]></appid>");


        bf.append("<body><![CDATA[");
        bf.append(unifiedorder.getBody());
        bf.append("]]></body>");


        bf.append("<mch_id><![CDATA[");
        bf.append(unifiedorder.getMch_id());
        bf.append("]]></mch_id>");

        bf.append("<nonce_str><![CDATA[");
        bf.append(unifiedorder.getNonce_str());
        bf.append("]]></nonce_str>");


        bf.append("<notify_url><![CDATA[");
        bf.append(unifiedorder.getNotify_url());
        bf.append("]]></notify_url>");

        bf.append("<out_trade_no><![CDATA[");
        bf.append(unifiedorder.getOut_trade_no());
        bf.append("]]></out_trade_no>");

        bf.append("<spbill_create_ip><![CDATA[");
        bf.append(unifiedorder.getSpbill_create_ip());
        bf.append("]]></spbill_create_ip>");

        bf.append("<total_fee><![CDATA[");
        bf.append(unifiedorder.getTotal_fee());
        bf.append("]]></total_fee>");

        bf.append("<trade_type><![CDATA[");
        bf.append(unifiedorder.getTrade_type());
        bf.append("]]></trade_type>");


        bf.append("<sign><![CDATA[");
        bf.append(unifiedorder.getSign());
        bf.append("]]></sign>");


        bf.append("</xml>");

        Log.e("TAG", bf.toString());

        return bf.toString();
    }

    return "";
}

好了,按照微信要求的格式准备好字符串了,接下来就是向微信的接口地址请求数据了,我用的是okhttputils

    OkHttpUtils.postString().content(xmlInfo).url(wxUrl).build().execute(new StringCallback() {
        @Override
        public void one rror(Call call, Exception e, int id) {

        }

        @Override
        public void onResponse(String response, int id) {
        //如果顺利的话,这里就可以获取到微信返回给我们的信息了,当然如果不顺利的话,检查一下前几步有没有错误吧....
            Log.e("TAG", response);
    }
    }

微信返回给我们的是一个这样的字符串:

<xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wxxxxxxxxxxxxx]]></appid>
<mch_id><![CDATA[xxxxxxxx]]></mch_id>
<nonce_str><![CDATA[hQBELjqvxjPAKK7b]]></nonce_str>
<sign><![CDATA[A499F6DC94AAC2648ADA31FD3AB7B806]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx20161208182704b5416397040869790726]]></prepay_id>
<trade_type><![CDATA[APP]]></trade_type></xml>

得到这个字符串以后,需要进行解析,并提取出来我们需要的信息,我是这么做的…

            //用来接收服务器返回的prepay_id参数
            String prepay_id = "";
            String nonce_str = "";
            //我到现在都不明白服务器返回给我这个值有毛用
            String sign = "";
            XmlPullParser parser = Xml.newPullParser();
            StringReader stringReader = new StringReader(response);
            try {
                parser.setInput(stringReader);

                int eventType = parser.getEventType();

                while (eventType != XmlPullParser.END_DOCUMENT) {
                    String nodeName = parser.getName();

                    switch (eventType) {
                        case XmlPullParser.START_TAG:
                            if ("prepay_id".equals(nodeName))
                                prepay_id = parser.nextText();
                            else if ("nonce_str".equals(nodeName)) {
                                nonce_str = parser.nextText();
                            } else if ("sign".equals(nodeName)) {
                                sign = parser.nextText();
                            }
                            break;
                    }
                    //这一行代码不能丢,我把这丢了,然后,死循环了...
                    eventType = parser.next();

                }

            } catch (Exception e) {
                e.printStackTrace();
            }
            //关闭流..有用么?
            stringReader.close();

好了,到这是不是觉得我们需要的参数都已经得到了?其实我起初也是这么想的..然而..还是会出现闪退..于是乎..想到了前辈们说的二次签名,然后查了一下,最后抱着试一试的心态……

            req.appId = Constant.WEIXIN_APPID;
            req.partnerId = Constant.WEIXIN_PARTERID;
            req.prepayId = prepay_id;
            req.packageValue = "http://www.mamicode.com/Sign=WXPay";
            req.nonceStr = nonce_str;
            //这是得到一个时间戳(除以1000转化成秒数)
            req.timeStamp = System.currentTimeMillis() / 1000 + "";
            //这个集合是上面用到的那个集合,因为我是写在一起的,就直接clear了一下接着用了,下面的这些就是二次签名
            parameters.clear();
            parameters.put("appid", Constant.WEIXIN_APPID);
            parameters.put("partnerid", Constant.WEIXIN_PARTERID);
            parameters.put("prepayid", prepay_id);
            parameters.put("noncestr", nonce_str);
            parameters.put("timestamp", req.timeStamp);
            parameters.put("package", req.packageValue);
            //调用获得签名的方法,这里直接把服务器返回来的sign给覆盖了,所以我不是很明白服务器为什么返回这个sign值,然后调起支付,基本上就可以了(我的反正是可以了....)
            sign = createSign(parameters);

            Log.e("TAG", "timestamp=====" + req.timeStamp);
            req.sign = sign;
            // 在支付之前,如果应用没有注册到微信,应该先调用IWXMsg.registerApp将应用注册到微信
            api.sendReq(req);

  到这里,基本上我遇到的问题都解决完了,我觉得最大的问题还是因为没有后台的支持,需要自己对这些参数进行检验,而且从前没有进行过类似的工作.在此进行一下记录,希望对遇到同样问题的同学有所帮助.

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Android中微信支付的流程(从请求统一支付接口到真正调起微信支付)