美文网首页
微信APP支付和退款(JAVA)

微信APP支付和退款(JAVA)

作者: Ludwigvan | 来源:发表于2018-12-05 14:28 被阅读320次

    微信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

    相关文章

      网友评论

          本文标题:微信APP支付和退款(JAVA)

          本文链接:https://www.haomeiwen.com/subject/utsicqtx.html