微信APP支付和退款(JAVA)

2018-12-05  本文已影响320人  Ludwigvan

微信APP支付和退款

  1. 微信支付流程说明
  2. Java demo实例
  3. 退款
  4. 转账

1、微信支付

1 微信支付流程说明

本文支付流程说明均参考微信支付开发文档,微信支付开发文档,用于商户在移动端APP中集成微信支付功能。
商户APP调用微信提供的SDK调用微信支付模块,商户APP会跳转到微信中完成支付,支付完后跳回到商户APP内,最后展示支付结果。

下面看一下官方文档提供的业务流程图:


业务流程图

(ps:纵观所有的支付场景,包括博主接口过的支付方式比如Paypal(贝宝),Stripe(信用卡)等支付,他们的流程基本一致。)

概括起来就是:商户后端根据支付应用配置信息生成支付信息->App获取到支付信息->通过SDK发起支付请求->用户客户端输入密码->App 发起三方三方服务器支付请求->三方服务器回调商户后端服务器支付信息

Java demo实例

主要实现APP发起支付将支付金额传给后端服务器-后端服务器请求微信服务器生成预支付订单信息-返回APP端;

一、微信预支付订单实体对象

public class UnifiedorderResponse{
    
    /**预付单信息**/
    private String prepay_id;
    /**签名**/
    private String nonce_str;
    /**微信开放平台申请的应用id**/
    private String appid;
    /**签名**/
    private String sign;
    /**请求方式**/
    private String trade_type;
    /**商户号id**/
    private String mch_id;
    /**返回提示信息**/
    private String return_msg;
    /**结果码**/
    private String result_code;
    /**返回码**/
    private String return_code;
    /**时间戳**/
    private String timestamp;
}

二、微信统一下单实体对象

public class UnifiedorderModel {
    
    /**微信开放平台申请的应用id**/
    private String appid;
    /**商户号*/
    private String mch_id;
    /**随机字符串*/
    private String nonce_str;
    /**签名(需要根据app应用商户密钥对整个统一下单实体对象就行签名)*/
    private String sign;
    /**签名方式*/
    private String sign_type;
    /**商品描述:腾讯充值中心-QQ会员充值*/
    private String body;    
    /**商品详情**/
    private String detail;
    /**附加数据**/
    private String attach;
    /**支付订单号*/
    private String out_trade_no;
    /**总金额(分)*/
    private Integer total_fee;
    /**终端IP(8.8.8.8)*/
    private String spbill_create_ip;
    /**异步通知地址*/
    private String notify_url;
    /**交易类型**/
    private String trade_type;
    /**用户标识**/
    private String openid;
    /**商品id**/
    private String product_id;
}

三、微信签名

对预支付请求实体属性进行字典排序,然后拼接字符串,用md5 加盐转大写生成签名字符串;

1、创建统一下单签名Map

        /**
         * 创建统一下单签名map
         * @param request
         * @param mchKey 商户密钥
         * @return
         */
      public static Map<String, String> createUnifiedSign(UnifiedorderModel request) {
            Map<String, String> map = new HashMap<>();
            map.put("appid", request.getAppid());
            map.put("mch_id", request.getMch_id());
            map.put("nonce_str", request.getNonce_str());
            map.put("sign", request.getSign());
            map.put("sign_type", request.getSign_type());
            map.put("attach", request.getAttach());
            map.put("body", request.getBody());
            map.put("detail", request.getDetail());
            map.put("notify_url", request.getNotify_url());
            map.put("openid", request.getOpenid());
            map.put("out_trade_no", request.getOut_trade_no());
            map.put("spbill_create_ip", request.getSpbill_create_ip());
            map.put("total_fee", String.valueOf(request.getTotal_fee()));
            map.put("trade_type", request.getTrade_type());
            return map;
        }

2、按字典序排序微信统一签名

       /**
       * 微信统一签名
       * @param params
       * @return
       */
      public static String sign(Map<String, String> params,String mchKey) {
           SortedMap<String, String> sortedMap = new TreeMap<>(params);

           StringBuilder toSign = new StringBuilder();
           for (String key : sortedMap.keySet()) {
               String value = params.get(key);
               if (value!=null && !"".equals(value) && !"sign".equals(key) 
                                           && !"key".equals(key)) {
                   toSign.append(key).append("=").append(value).append("&");
               }
           }

           toSign.append("key=").append(mchKey);
           return DigestUtils.md5Hex(toSign.toString()).toUpperCase();
      }

四、XML格式转换

因为微信的请求和响应都是以xml的格式,所以需要将请求订单对象转换成XML 然后请求;

public class XMLUtil {
     /**
    * 解析字符串(XML)
    * 
    * @param msg
    * @return
    * @throws Exception
    */
   @SuppressWarnings("unchecked")
   public static Map<String, String> parseXml(String msg)
           throws Exception {
       // 将解析结果存储在HashMap中
       Map<String, String> map = new HashMap<String, String>();
       // 从request中取得输入流
       InputStream inputStream = new ByteArrayInputStream(msg.getBytes("UTF-8"));
       // 读取输入流
       SAXReader reader = new SAXReader();
       //防止微信支付XXE漏洞
       reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);
       Document document = reader.read(inputStream);
       // 得到xml根元素
       Element root = document.getRootElement();
       // 得到根元素的所有子节点
       List<Element> elementList = root.elements();
       // 遍历所有子节点
       for (Element e : elementList){
           map.put(e.getName(), e.getText());
       }
           
       // 释放资源
       inputStream.close();
       inputStream = null;
       return map;
   }
   
   /**
    * 扩展xstream,使其支持CDATA块
    */
   private static XStream xstream = new XStream(new XppDriver(new NoNameCoder()) {
       
       @Override
       public HierarchicalStreamWriter createWriter(Writer out) {
           return new PrettyPrintWriter(out) {
               // 对所有xml节点的转换都增加CDATA标记
               boolean cdata = true;

               @Override
               @SuppressWarnings("rawtypes")
               public void startNode(String name, Class clazz) {
                   super.startNode(name, clazz);
               }
               
               @Override
               public String encodeNode(String name) {
                   return name;
               }
               

               @Override
               protected void writeText(QuickWriter writer, String text) {
                   if (cdata) {
                       writer.write("<![CDATA[");
                       writer.write(text);
                       writer.write("]]>");
                   } else {
                       writer.write(text);
                   }
               }
           };
       }
   });
   
   /**
    * xml转对象
    * @param xml
    * @param objClass
    * @return
    */
    public static Object fromXML(String xml, Class<?> objClass) {
       Serializer serializer = new Persister();
       try {
           return serializer.read(objClass, xml);
       } catch (Exception e) {
           e.printStackTrace();
       }
       return null;
   }
   
   /**
    * 对象转xml
    * @param obj
    * @return
    */
   public static String toXMl(Object obj) {
       //使用注解设置别名必须在使用之前加上注解类才有作用
    xstream.processAnnotations(obj.getClass());
       return xstream.toXML(obj);
   }
   
   private XStream inclueUnderlineXstream = new XStream(new DomDriver(null,new XmlFriendlyNameCoder("_-", "_")));

   public XStream getXstreamInclueUnderline() {
       return inclueUnderlineXstream;
   }
   public XStream xstream() {
       return xstream;
   }
}

这里用到的XML POM 配置:

        <!-- xml解析 -->
        <dependency>
              <groupId>org.simpleframework</groupId>
              <artifactId>simple-xml</artifactId>
              <version>2.7.1</version>
        </dependency>
        <dependency> 
            <groupId>com.thoughtworks.xstream</groupId> 
            <artifactId>xstream</artifactId> 
            <version>1.4.3</version> 
        </dependency>

五、微信HTTP请求

1、加载微信证书生成SSL请求(微信证书可以在微信商户后台下载)

       //随机字符串范围
       private static final String RANDOM_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHI"
                                               + "JKLMNOPQRSTUVWXYZ0123456789";
       private static final java.util.Random RANDOM = new java.util.Random();
       
       //支付证书认证
       private static SSLContext sslContext;

/**
        * 初始化微信证书
        * @param req
        * @return
        * @throws MyException 
        */
       public static SSLContext initSSLContext(HttpServletRequest req,
               String mchId) 
               throws MyException {
           FileInputStream inputStream = null;
           try {
               inputStream = new FileInputStream(new File(req.getSession().getServletContext().
                         getRealPath("") + "/WEB-INF/classes"+BasicInfo.KeyPath));
           } catch (IOException e) {
               log.error("商户证书不正确-->>"+e);
               throw new MyException("证书不正确!", e);
           }

           try {
               KeyStore keystore = KeyStore.getInstance("PKCS12");
               char[] partnerId2charArray = mchId.toCharArray();
               keystore.load(inputStream, partnerId2charArray);
               sslContext = SSLContexts.custom().loadKeyMaterial(keystore, partnerId2charArray).build();
               return sslContext;
           } catch (Exception e) {
               log.error("商户秘钥不正确-->>"+e);
               throw new MyException("秘钥不正确!", e);
           } finally {
              
           }
       }
 /**
        * 生成随机字符串
        * @return
        */
       public static String getRandomStr() {
         StringBuilder sb = new StringBuilder();
         for (int i = 0; i < 16; i++) {
           sb.append(RANDOM_STR.charAt(RANDOM.nextInt(RANDOM_STR.length())));
         }
         return sb.toString();
       }
/**
        * 发起微信支付相关请求
        * @return
        */
       public static String ssl(String url,String data,
               HttpServletRequest req,String mchId){
            StringBuffer message = new StringBuffer();
            try {
              //  KeyStore keyStore  = KeyStore.getInstance("PKCS12");
                SSLContext sslcontext = initSSLContext(req,mchId);
                SSLConnectionSocketFactory sslsf = 
                        new SSLConnectionSocketFactory(sslcontext, 
                                new String[] { "TLSv1" }, 
                                null, 
                                SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
                CloseableHttpClient httpclient = HttpClients.custom().                                 
                        setSSLSocketFactory(sslsf).build();
                
                HttpPost httpost = new HttpPost(url);
                httpost.addHeader("Connection", "keep-alive");
                httpost.addHeader("Accept", "*/*");
                httpost.addHeader("Content-Type", 
                            "application/x-www-form-urlencoded; charset=UTF-8");
                httpost.addHeader("Host", "api.mch.weixin.qq.com");
                httpost.addHeader("X-Requested-With", "XMLHttpRequest");
                httpost.addHeader("Cache-Control", "max-age=0");
                httpost.addHeader("User-Agent", 
                           "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
                httpost.setEntity(new StringEntity(data, "UTF-8"));
                
                CloseableHttpResponse response = httpclient.execute(httpost);
                try {
                    HttpEntity entity = response.getEntity();
                    log.info("----------------------------------------");
                    log.info(response.getStatusLine());
                    if (entity != null) {
                        log.info("Response content length: " + entity.getContentLength());
                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent(),"UTF-8"));
                        String text;
                        while ((text = bufferedReader.readLine()) != null) {
                            message.append(text);
                        }
                    }
                    EntityUtils.consume(entity);
                } catch (IOException e) {
                    log.error("发起微信支付请求过程异常--》》"+e);
                    e.printStackTrace();
                } finally {
                    response.close();
                }
            } catch (Exception e1) {
                log.error("发起微信支付请求异常--》》"+e1);
                e1.printStackTrace();
            } 
            return message.toString();
        }
       
       

六、发起预支付

/**
     * APP微信支付
     * @param req
     * @param model
     * @return
     * @throws Exception
     */
    public static UnifiedorderResponse payApp(HttpServletRequest req,
               UnifiedorderModel model) throws Exception {

            model.setNonce_str(PayUtil.getRandomStr());
            model.setSign(SignUtil.sign(SignUtil.createUnifiedSign(model),
                    BasicInfo.APP_MchKey));     
            try{
                
              XMLUtil xmlUtil= new XMLUtil();
              xmlUtil.xstream().alias("xml", model.getClass());
            
              String xml = xmlUtil.xstream().toXML(model);
              String response = PayUtil.ssl(BasicInfo.unifiedordersurl, xml,
                      req,BasicInfo.APP_MchId);
              
              UnifiedorderResponse ret = (UnifiedorderResponse) 
                XMLUtil.fromXML(response,UnifiedorderResponse.class); 
              
              System.out.println("-------------------");
              System.out.println(JSONObject.toJSONString(ret)); 

              if("SUCCESS".equals(ret.getResult_code())){
                  
                  //再次签名
                  Map<String, String> finalpackage = new TreeMap<String, String>();
                  String timestamp = (System.currentTimeMillis()/1000)+"";
                  String noncestr = PayUtil.getRandomStr();
                 
                  finalpackage.put("appid", BasicInfo.APP_AppID); 
                  finalpackage.put("timestamp", timestamp);  
                  finalpackage.put("noncestr", noncestr);       
                  finalpackage.put("prepayid", ret.getPrepay_id());
                  finalpackage.put("package", "Sign=WXPay"); 
                  finalpackage.put("partnerid",BasicInfo.APP_MchId); 
        
                  String sign = SignUtil.sign(finalpackage, BasicInfo.APP_MchKey);
                          
                  ret.setSign(sign);
                  ret.setNonce_str(noncestr);
                  ret.setTimestamp(timestamp);
                          
                  return ret;
              }else{
                
                 log.error("微信下单失败》》"+"错误码:"+ret.getReturn_code()+"  ;"
                                    + "描述:"+ret.getReturn_msg());
                 return ret;
              }
            }catch (Exception e) {
                  log.error("微信下单异常》》"+e);
                  throw new MyException(SystemError.WX_PAY_ERROR.getCode(), 
                       SystemError.WX_PAY_ERROR.getMessage());
            }

     }

预支付信息返回给前端;前端通过SDK发起支付请求;后端通过配置的回调地址接收微信的支付响应;

七、微信回调

 @ResponseBody
    @RequestMapping(value="/callBackWeiXinPay")
    @Transactional(rollbackFor = Exception.class)
    public void callBackWeiXinPay(HttpServletRequest req,
                                  HttpServletResponse response) throws Exception{
        try {
            log.info("------------微信支付回调开始------------");
            //支付回调
            PayResponse pay = WxPay.callBackWeiXin(req);
            //订单号
            String orderNO = pay.getOut_trade_no();
            log.info("微信支付回调订单号:"+orderNO);
            //微信流水号
            String thirdOrderNo = pay.getTransaction_id();
            log.info("微信支付回调三方订单号:"+thirdOrderNo);
            //支付金额
            Integer money = pay.getTotal_fee();
            log.info("微信支付回调金额(分):"+money);
            
            //todo 业务处理比如更新订单状态等
     
            //响应微信
            CallBackWxModel call = new CallBackWxModel();
            call.setReturn_code("SUCCESS");
            call.setReturn_msg("");
            // 响应xml格式数据
            String resXml = XMLUtil.toXMl(call);
            response.getWriter().write(resXml);
            log.info("------------微信支付回调结束------------");

        } catch (Exception e) {
            log.error("微信支付回调异常CallBackController-->>callBackPay:\r\n"+ e.getMessage());
            log.error("微信支付回调异常CallBackController-->>callBackPay:\r\n", e);
        }

    }

2、退款

一、退款实体对象

public class RefundModel {
    
    /**Appid*/
    private String appid;
    /**商户号*/
    private String mch_id;
    /**随机字符串*/
    private String nonce_str;
    /**签名*/
    private String sign;
    /**签名方式*/
    private String sign_type;
    /**支付订单号*/
    private String out_trade_no;
    /**退款订单号*/
    private String out_refund_no;
    /**总金额*/
    private Integer total_fee;
    /**退款金额*/
    private Integer refund_fee;
    /**退款原因*/
    private String refund_desc;
    
}

二、退款

 /**
    * 微信退款
    * @param rdfund
    * @param req
    * @param type 1-公众号  2-小程序  3-app
    * @return
    * @throws Exception
    */
   public static Map<String, Object>  refundUtil(RefundModel  rdfund
           , HttpServletRequest req,Integer type) throws Exception{
       Map<String, Object> mp = new HashMap<String, Object>();
       //设置支付账户信息
        String MchKey = ""; //商户号秘钥
        String MchId = ""; //商户号id
        rdfund.setAppid(BasicInfo.APP_AppID);
        rdfund.setMch_id(BasicInfo.APP_MchId); 
        MchKey = BasicInfo.APP_MchKey;
        MchId = BasicInfo.APP_MchId;
     

       rdfund.setNonce_str(PayUtil.getRandomStr());
       rdfund.setSign_type("MD5");
       rdfund.setSign(SignUtil.sign(SignUtil.createRefundSign(rdfund),MchKey));
       
       try{
           XMLUtil xmlUtil= new XMLUtil();
           xmlUtil.xstream().alias("xml", rdfund.getClass());
          
           String xml = xmlUtil.xstream().toXML(rdfund);
           String response = PayUtil.ssl(BasicInfo.refundurl, xml,req,MchId);
           System.out.println(response);
           Map<String, String> map = xmlUtil.parseXml(response);
           if("SUCCESS".equals(map.get("result_code"))){
                mp.put("stu", true);
                return mp;
           }else{
             mp.put("stu", false);
             mp.put("errMsg", map.get("return_msg"));
             mp.put("errDes", map.get("err_code_des"));
             log.error("微信退款失败》》"+"错误码:"+map.get("return_msg")+"  ;"
                                + "描述:"+map.get("err_code_des"));
             return mp;
           }
      }catch (Exception e) {
             log.error("微信退款异常》》"+e);
             throw new MyException(SystemError.WX_RETREIEV_ERROR.getCode(), 
                   SystemError.WX_RETREIEV_ERROR.getMessage());
      }
   }

主要逻辑跟支付差不多,可以参考前面的demo;

3、转账

一、转账实体对象

    /***appId****/
    private String mch_appid;
    /***商户号****/
    private String mchid;
    /***随机字符串****/
    private String nonce_str;
    /***签名****/
    private String sign;
    /***商户订单号****/
    private String partner_trade_no;
    /***用户openid****/   
    private String openid;
    /****校验用户姓名选项 
     *  NO_CHECK:不校验真实姓名
        FORCE_CHECK:强校验真实姓名***/
    private String check_name;
    /***收款用户姓名****/
    private String re_user_name;
    /****金额(分)***/
    private Integer amount;
    /***企业付款描述信息****/
    private String desc;
    /***Ip地址****/
    private String spbill_create_ip;

二、转账

   /**
    * 企业转账接口
    * @param transfer
    * @param req
    * @return
    * @throws Exception
    */
   public static Map<String, Object>  transfersUtil(TransfersModel  transfer
           , HttpServletRequest req) throws Exception{
       Map<String, Object> mp = new HashMap<String, Object>();
       transfer.setMch_appid(BasicInfo.appID);
       transfer.setMchid(BasicInfo.MchId);
       transfer.setNonce_str(PayUtil.getRandomStr());
       transfer.setCheck_name("NO_CHECK");
       transfer.setSpbill_create_ip("127.0.0.1");
       transfer.setSign(SignUtil.sign(SignUtil.createtransfersSign(transfer),BasicInfo.MchKey));
       
       try{
           XMLUtil xmlUtil= new XMLUtil();
           xmlUtil.xstream().alias("xml", transfer.getClass());
          
           String xml = xmlUtil.xstream().toXML(transfer);
           String response = PayUtil.ssl(BasicInfo.transfersurl, xml,req,BasicInfo.MchKey);
           System.out.println(response);
           Map<String, String> map = xmlUtil.parseXml(response);

           if("SUCCESS".equals(map.get("result_code"))){
                mp.put("stu", true);
                return mp;
           }else{
             mp.put("stu", false);
             mp.put("errMsg", map.get("return_msg"));
             mp.put("errDes", map.get("err_code_des"));
             log.error("企业转账失败》》"+"错误码:"+map.get("return_msg")+"  ;"
                       + "描述:"+map.get("err_code_des"));
             return mp;
           }
      }catch (Exception e) {
             log.error("企业转账异常》》"+e);
             throw new MyException(SystemError.WX_RETREIEV_ERROR.getCode(), 
                   SystemError.WX_RETREIEV_ERROR.getMessage());
      }
   }

完整微信支付Demo:EzHomeSixGod.git

上一篇下一篇

猜你喜欢

热点阅读