微信APP支付

最近公司在开发新项目,需要微信支付   经历了几天时间 终于写出来了  不过微信文档也不是很容易阅读  东西不全  很多东西全靠猜

注意:所有带加密的都需要带上微信商户秘钥(32位)  即key

这边主要是java服务端代码   :

        1.1.首先是统一下单接口

        一。

          微信提供的下单接口 https://api.mch.weixin.qq.com/pay/unifiedorder

          通过POST接口访问

          Url是访问地址   data是微信转成xml格式的传输参数   必须是UTF-8格式的参数

        

微信要求的格式是这样     我们只需要填写必要参数就行了。

<xml>
   <appid>wx2421b1c4370ec43b</appid>
   <attach>支付测试</attach>
   <body>APP支付测试</body>
   <mch_id>10000100</mch_id>
   <nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str>
   <notify_url>http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php</notify_url>
   <out_trade_no>1415659990</out_trade_no>
   <spbill_create_ip>14.23.150.211</spbill_create_ip>
   <total_fee>1</total_fee>
   <trade_type>APP</trade_type>
   <sign>0CB01533B8C1EF103065174F50BCA001</sign>
</xml>

==============Post 请求封装方法===================

 public static String POST(String url, String data){
         CloseableHttpClient client = HttpClients.createDefault();
         CloseableHttpResponse response = null;
 
         try {
             HttpPost method = new HttpPost(url);
             method.addHeader(HTTP.CONTENT_TYPE, "application/xml");
             StringEntity entity = new StringEntity(data,"UTF-8");
             method.setEntity(entity);
             response  = client.execute(method);
 
             String s = EntityUtils.toString(response.getEntity(), "UTF-8");
             return s;
         } catch (IOException e) {
            e.printStackTrace();
         }finally {
            try
             {
                response.close();
            }
             catch (Exception e)
             {
                e.printStackTrace();
             }
         }
 
 
         return null;
     }

//返回的数据也是xml格式的

     https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1    微信文档写的很清楚

 

 

     1.2.生成随机字符串

        这里我是通过UUID生成的32位随机字符串

    

 /**
     * 生成32位随机数
     * @return
     */
    public static String  getRandomNumberAlgorithm(){
        String uuid = UUID.randomUUID().toString().toUpperCase();
        String replace = uuid.replace("-", "");
        return  replace;
    }

     1.3 将Map 转换成xml格式的数据字符串

    

 /**
     * @author
     * @Description:将请求参数转换为xml格式的string 不包含CDATA标签
     * @param parameters
     *            请求参数
     * @return
     */
    public static String getRequestXml2(SortedMap<String, Object> parameters) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Set<?> es = parameters.entrySet();
        Iterator<?> it = es.iterator();
        while (it.hasNext()) {
            @SuppressWarnings("rawtypes")
            Map.Entry entry = (Map.Entry) it.next();
            String k = entry.getKey().toString();
            String v = entry.getValue().toString();

                sb.append("<" + k + ">" + v + "</" + k + ">");

        }
        sb.append("</xml>");
        return sb.toString();
    }

    1.4 生成签名

public static String createSign(SortedMap<String, Object> packageParams, String apiKey) {  
        StringBuffer sb = new StringBuffer();  
        Set<?> es = packageParams.entrySet();  
        Iterator<?> it = es.iterator();  
        while (it.hasNext()) {  
            @SuppressWarnings("rawtypes")
            Map.Entry entry = (Map.Entry) it.next();  
            String k = entry.getKey().toString();  
            String v = entry.getValue().toString();  
            if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {  
                sb.append(k + "=" + v + "&");  
            }  
        }  
        sb.append("key=" + apiKey);  
        String sign = MD5.encoder(sb.toString()).toUpperCase();  
        return sign;  
    }

    这个时候你将下单返回的参数  传递过去  你以为就成功了?    相信我   幻觉 绝对是幻觉     这个时候浏览安卓支付起掉文档    OK  需要二次加密

ok  我们按照参数  依次写进去

    

//返回数据
            CreateWeiXinPayOrderResponse createWeiXinPayOrderResponse = payResult(stringObjectSortedMap);
            //或者封装到map中将数据
            SortedMap<String, String> secondaryEncryption=new TreeMap<>();
            String appid = stringObjectSortedMap.get("appid").toString();
            secondaryEncryption.put("appid",appid);
            String partnerid = request.getMch_id().toString();
            secondaryEncryption.put("partnerid",partnerid);
            String prepay_id = stringObjectSortedMap.get("prepay_id").toString();
            secondaryEncryption.put("prepayid",prepay_id);
            String nonce_str = stringObjectSortedMap.get("nonce_str").toString();
            secondaryEncryption.put("noncestr",nonce_str);
            secondaryEncryption.put("package","Sign=WXPay");
            secondaryEncryption.put("timestamp",String.valueOf(time/1000));
            String sign1 = createSign("UTF-8", secondaryEncryption, request.getKey());
            stringObjectSortedMap.put("sign",sign1);
            createWeiXinPayOrderResponse.setMap(stringObjectSortedMap);

这个时候  APP 前端有报错  -1   来我们看看微信对这个描述是怎么样的

 

 坑爹的其他异常   搞了一天多    而后查看签名 规则 发现

    

也就是说  不管是签名几次  什么签名    都需要带上key  即商户密匙!!!

 顺带贴上加密 代码:

public static String createSign(String characterEncoding, SortedMap<String, String> parameters,String key) {
        StringBuffer sb = new StringBuffer();
        Set es = parameters.entrySet();
        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 + "&");
            }
        }
        sb.append("key=" + key);//最后加密时添加商户密钥,由于key值放在最后,所以不用添加到SortMap里面去,单独处理,编码方式采用UTF-8
        String sign = MD5Encode(sb.toString(), characterEncoding).toUpperCase();
        return sign;
    }

public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname))
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes()));
            else
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes(charsetname)));
        } catch (Exception exception) {
        }
        return resultString;
    }

 private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }
    private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };

   private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++)
            resultSb.append(byteToHexString(b[i]));

        return resultSb.toString();
    }

OK  完事!!!

 

然后是最重要的回调

 回调  

该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。

通知url必须为直接可访问的url,不能携带参数。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action”

 

不能带参数这个回调接口   需要自己提供  需要有证书的    

 这样的话我们就只能从二进制流中取得我们需要的数据了

将数据转换为xml格式的数据  后端进行验签

@Override
    public String weiXinNotify(HttpServletRequest request, HttpServletResponse response) {
     
        StringBuffer xmlStr = new StringBuffer();
        try {
            BufferedReader reader = request.getReader();
            String line = null;
            while ((line = reader.readLine()) != null) {
                xmlStr.append(line);
            }
            reader.close();
            logger.info("微信支付回调返回参数:" + xmlStr + ", 即将调用支付模块回调接口");
            //调用支付模块的回调接口,处理返回出具
            NotifyRequest req = new NotifyRequest();
            req.setXmlStr(xmlStr.toString().trim());
            NotifyResponse resp = paymentClient.weiXinPayNotify(req);
            logger.info("微信支付回调返回给微信端的数据:" + resp.getResultXml());
            BufferedOutputStream out = new BufferedOutputStream(
                    response.getOutputStream());
            out.write(resp.getResultXml().getBytes());
            out.flush();
            out.close();
        } catch (Exception e) {
            logger.error("微信回调出错: " + e.getMessage());
            throw new WebApiCommonException(WebApiCommonErrorCodeType.PayBackError);
        }

        return xmlStr.toString();
    }

后端取 验签  开发时间紧 我就没封装了

public NotifyResponse weiXinPayNotify(String xmlStr) {
        NotifyResponse notifyResponse=new NotifyResponse();
        // 获取微信配置信息并赋值
        QueryAppliactionConfigRequest req = new QueryAppliactionConfigRequest();
        req.setConfigType("weixinPay");
        req.setConfigName("key");
        QueryAppliactionConfigResponse resp = appliactionConfigClient.queryAppliactionConfig(req);
        if (resp.getData()==null||resp.getData().getList().size()<0){
            throw new PayCommonException(PayCommonErrorCodeType.PAY_CONFIG_ERROR);
        }
        try {
            SortedMap<String, Object> map = xmlParse(xmlStr);
            logger.info(xmlStr);
            //返回结果
            String return_code = map.get("return_code").toString();
            if ("SUCCESS".equals(return_code)){
                //签名
                String sign1 = map.get("sign").toString().toUpperCase();
                String sign = PayCommonUtil.createSign(map, resp.getData().getList().get(0).getConfigVal());
                //回调验签
                if(sign.equals(sign1)||runInTest){
                    //业务结果
                    String result_code = map.get("result_code").toString();
                    if ("SUCCESS".equals(return_code)&&"SUCCESS".equals(result_code)){
                        //商户订单号
                        String out_trade_no = map.get("out_trade_no").toString();
                        notifyResponse.setTradeNo(out_trade_no);
                        //根据商户订单号获取订单信息
                        PayOrderInfo payOrderInfo = iPayOrderService.queryPayOrderInfoByTradeNo(out_trade_no);
                        if ("5".equals(payOrderInfo.getCurrencyType())||"4".equals(payOrderInfo.getCurrencyType())){
                            notifyResponse.setResultXml(setXml("FAIL", "该订单已失败或超时"));
                            return notifyResponse;
                        }
                        //根据商户订单号查到的状态判断是否进行下一步骤
                        payOrderInfo.setCurrencyType("1");
                        //交易金额
                        String total_fee = map.get("total_fee").toString();
                        //商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。
                        if (Integer.valueOf(total_fee)!= payOrderInfo.getTradeAmount()) {
                            throw new PayCommonException(PayCommonErrorCodeType.PAY_INCORRECT_AMOUNT);
                        }
                        payOrderInfo.setTradeAmount(Integer.valueOf(total_fee));
                        //支付完成时间
                        String time_end = map.get("time_end").toString();
                        if (time_end!=null){
                            payOrderInfo.setTradeEndTime(new Date());
                        }
                        //充值方式
                        payOrderInfo.setTradeChannel(1004);
                        //微信订单号
                        String transaction_id = map.get("transaction_id").toString();
                        notifyResponse.setTradeNo(transaction_id);
                        payOrderInfo.setTransactionId(transaction_id);
                        //加入订单信息
                        if(time_end!=null){
                            //成功
                            payOrderInfo.setTradeStatus(3);
                        }
                        iPayOrderService.updatePayOrderByOrderNo(payOrderInfo);
                        logger.info("支付成功!订单号为:"+payOrderInfo.getTradeNo()+"-----"+"第三方订单号:"+payOrderInfo.getTransactionId());
                        notifyResponse.setResultXml(setXml("SUCCESS", "OK"));
                        return notifyResponse;
                    }else {
                        // 支付出错
                        notifyResponse.setResultXml(setXml("FAIL","支付订单校验出错"));
                    }

                }else {
                    //验签失败
                    notifyResponse.setResultXml(setXml("FAIL","支付验签失败"));
                }
            }else {
                notifyResponse.setResultXml(setXml("FAIL","支付不成功"));
            }


        } catch (Exception e) {
            logger.error("支付回调异常: " + e.getMessage());
            notifyResponse.setResultXml(setXml("FAIL","支付回调异常"));
            return notifyResponse;
        }

        return null;
    }

 

  

搞定

 

posted @ 2018-07-04 18:58  Kapa  阅读(435)  评论(0编辑  收藏  举报