zoukankan      html  css  js  c++  java
  • 微信小程序-微信自动退款(Java后台)

    微信小程序-微信自动退款

    1、首先分享

          微信自动退款接口: 

            https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4

          微信付款 代码案例 (很多共同的代码 都在付款逻辑里面)

            https://www.cnblogs.com/yi1036943655/p/7211275.html

    2、小程序端代码 栗子

        //获取openId
        wx.request({
          url: 'http://192.168.1.183:8081/order/refund',
          data: {
            'amount':1,
            'incrementId': outTradeNo,
            'orderId': orderId,
            'productId': productId,
            'amount': amount,
            'sku': sku,
            'name': name
    
          },
          method: 'POST',
          header: { 'content-type': 'application/x-www-form-urlencoded' },
          success: function (result) {
            
          }

    2、接口端代码 栗子

        @Transactional(rollbackFor=MyException.class)
        @Override
        public JSONObject refundOrder(HttpServletRequest request) {
            //设置最终返回对象
            JSONObject resultJson = new JSONObject();
            //接受参数(金额)
            String amount = request.getParameter("amount");
            //接受参数(订单Id)
            String orderId = request.getParameter("orderId");
            //接受参数(商品ID)
            String productId = request.getParameter("productId");
            //接受参数(商品sku)
            String sku = request.getParameter("sku");
            //接受参数(商品name)
            String name = request.getParameter("name");
            //接受参数(商品订单号)
            String incrementId = request.getParameter("incrementId");
            
            //创建hashmap(用户获得签名)
            SortedMap<String, String> paraMap = new TreeMap<String, String>();
            //设置随机字符串
            String nonceStr = Utils.getUUIDString().replaceAll("-", "");
            //设置商户退款单号
            Integer randomNumber = new Random().nextInt(900)+ 100;
            String orderIncrementId = DateUtil.formatDate(new Date(), DateUtil.DATE_FMT_FOR_ORDER_NUMBER)+randomNumber;
            
            //设置请求参数(小程序ID)
            paraMap.put("appid", Configuration.APPLYID);
            //设置请求参数(商户号)
            paraMap.put("mch_id", Configuration.MCHID);
            //设置请求参数(随机字符串)
            paraMap.put("nonce_str", nonceStr);
            //设置请求参数(商户订单号)
            paraMap.put("out_trade_no", incrementId);
            //设置请求参数(商户退款单号)
            paraMap.put("out_refund_no", orderIncrementId);
            //设置请求参数(订单金额)
            paraMap.put("total_fee", amount);
            //设置请求参数(退款金额)
            paraMap.put("refund_fee", amount);
            //TODO (这个回调地址 没有具体进行测试 需要写好逻辑 打版在测试)设置请求参数(通知地址)
            paraMap.put("notify_url", "http://abcdefg.nat123.cc:443/order/refundCallback");
            //调用逻辑传入参数按照字段名的 ASCII 码从小到大排序(字典序)
            String stringA = formatUrlMap(paraMap, false, false);
            //第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。(签名)
            String sign = MD5Util.MD5(stringA+"&key="+Configuration.KEY).toUpperCase();
            //将参数 编写XML格式
            StringBuffer paramBuffer = new StringBuffer();
            paramBuffer.append("<xml>");
            paramBuffer.append("<appid>"+Configuration.APPLYID+"</appid>");
            paramBuffer.append("<mch_id>"+Configuration.MCHID+"</mch_id>");
            paramBuffer.append("<nonce_str>"+paraMap.get("nonce_str")+"</nonce_str>");
            paramBuffer.append("<sign>"+sign+"</sign>");
            paramBuffer.append("<out_refund_no>"+paraMap.get("out_refund_no")+"</out_refund_no>");
            paramBuffer.append("<out_trade_no>"+paraMap.get("out_trade_no")+"</out_trade_no>");
            paramBuffer.append("<refund_fee>"+paraMap.get("refund_fee")+"</refund_fee>");
            paramBuffer.append("<total_fee>"+paraMap.get("total_fee")+"</total_fee>");
            paramBuffer.append("<notify_url>"+paraMap.get("notify_url")+"</notify_url>");
            paramBuffer.append("</xml>");
            
            try {
                //发送请求(POST)(获得数据包ID)(这有个注意的地方 如果不转码成ISO8859-1则会告诉你body不是UTF8编码 就算你改成UTF8编码也一样不好使 所以修改成ISO8859-1)
                Map<String,String> map = doXMLParse(doRefund(request,Configuration.REFUND_URL, new String(paramBuffer.toString().getBytes(), "ISO8859-1")));
                //应该创建 退款表数据
                if(map!=null && (StringUtils.isNotBlank(map.get("return_code")) && "SUCCESS".equals(map.get("return_code")))){
                    if(StringUtils.isBlank(map.get("err_code_des"))) {
                //接口调用成功 执行操作逻辑 返回成功状态码给前台 
                    }else {
                        resultJson.put("returnCode", "error");
                        resultJson.put("err_code_des", map.get("err_code_des"));
                    }
                }else {
                    resultJson.put("returnCode", map.get("return_code"));
                    resultJson.put("err_code_des", map.get("err_code_des"));
                }
            } catch (UnsupportedEncodingException e) {
                log.info("微信 退款 异常:"+e.getMessage());
                e.printStackTrace();
            } catch (Exception e) {
                log.info("微信 退款 异常:"+e.getMessage());
                e.printStackTrace();
            }
            log.info("微信 退款 失败");
            return resultJson;

    3、Http请求 代码(这块的代码逻辑和付款的是不一样的)

        private String doRefund(HttpServletRequest request,String url,String data) throws Exception{
            /**
             * 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
             */
            
            KeyStore keyStore  = KeyStore.getInstance("PKCS12");
            String substring = request.getSession().getServletContext().getRealPath("/").substring(0, request.getSession().getServletContext().getRealPath("/").lastIndexOf("webapp\"));
            FileInputStream instream = new FileInputStream(substring+"resources/refund_certificate/apiclient_cert.p12");//P12文件目录 证书路径
            try {
                /**
                 * 此处要改
                 * */
                keyStore.load(instream, Configuration.MCHID.toCharArray());//这里写密码..默认是你的MCHID
            } finally {
                instream.close();
            }
     
            // Trust own CA and all self-signed certs
            /**
             * 此处要改
             * */
            SSLContext sslcontext = SSLContexts.custom()
                    .loadKeyMaterial(keyStore, Configuration.MCHID.toCharArray())//这里也是写密码的  
                    .build();
            // Allow TLSv1 protocol only
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                    sslcontext,
                    new String[] { "TLSv1" },
                    null,
                    SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
            CloseableHttpClient httpclient = HttpClients.custom()
                    .setSSLSocketFactory(sslsf)
                    .build();
            try {
                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();
     
                    String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                    EntityUtils.consume(entity);
                   return jsonStr;
                } finally {
                    response.close();
                }
            } finally {
                httpclient.close();
            }
        }

    4、退款结果通知 后台代码 栗子

    AESUtil

    package com.bodi.repository;
    
    import javax.crypto.Cipher;
    import javax.crypto.spec.SecretKeySpec;
    
    public class AESUtil {
        /**
         * 密钥算法
         */
        private static final String ALGORITHM = "AES";
        /**
         * 加解密算法/工作模式/填充方式
         */
        private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding";
        /**
         * 生成key
         */
        private static SecretKeySpec key = new SecretKeySpec(MD5Util.MD5Encode(Configuration.KEY, "UTF-8").toLowerCase().getBytes(), ALGORITHM);
     
        /**
         * AES加密
         * 
         * @param data
         * @return
         * @throws Exception
         */
        public static String encryptData(String data) throws Exception {
            // 创建密码器
            Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
            // 初始化
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return Base64Util.encode(cipher.doFinal(data.getBytes()));
        }
     
        /**
         * AES解密
         * 
         * @param base64Data
         * @return
         * @throws Exception
         */
        public static String decryptData(String base64Data) throws Exception {
            Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
            cipher.init(Cipher.DECRYPT_MODE, key);
            return new String(cipher.doFinal(Base64Util.decode(base64Data)));
        }
    Base64Util 
    package com.bodi.repository;
    
    import java.io.ByteArrayOutputStream;
    
    public class Base64Util {
        private static final char[] base64EncodeChars = new char[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
                'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
                's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
     
        private static byte[] base64DecodeChars = new byte[] { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60,
                61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1,
                -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 };
     
        private Base64Util() {
        }
     
        /**
         * 将字节数组编码为字符串
         *
         * @param data
         */
        public static String encode(byte[] data) {
            StringBuffer sb = new StringBuffer();
            int len = data.length;
            int i = 0;
            int b1, b2, b3;
     
            while (i < len) {
                b1 = data[i++] & 0xff;
                if (i == len) {
                    sb.append(base64EncodeChars[b1 >>> 2]);
                    sb.append(base64EncodeChars[(b1 & 0x3) << 4]);
                    sb.append("==");
                    break;
                }
                b2 = data[i++] & 0xff;
                if (i == len) {
                    sb.append(base64EncodeChars[b1 >>> 2]);
                    sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
                    sb.append(base64EncodeChars[(b2 & 0x0f) << 2]);
                    sb.append("=");
                    break;
                }
                b3 = data[i++] & 0xff;
                sb.append(base64EncodeChars[b1 >>> 2]);
                sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
                sb.append(base64EncodeChars[((b2 & 0x0f) << 2) | ((b3 & 0xc0) >>> 6)]);
                sb.append(base64EncodeChars[b3 & 0x3f]);
            }
            return sb.toString();
        }
     
        public static byte[] decode(String str) throws Exception {
            byte[] data = str.getBytes("GBK");
            int len = data.length;
            ByteArrayOutputStream buf = new ByteArrayOutputStream(len);
            int i = 0;
            int b1, b2, b3, b4;
     
            while (i < len) {
     
                /* b1 */
                do {
                    b1 = base64DecodeChars[data[i++]];
                } while (i < len && b1 == -1);
                if (b1 == -1) {
                    break;
                }
     
                /* b2 */
                do {
                    b2 = base64DecodeChars[data[i++]];
                } while (i < len && b2 == -1);
                if (b2 == -1) {
                    break;
                }
                buf.write((b1 << 2) | ((b2 & 0x30) >>> 4));
     
                /* b3 */
                do {
                    b3 = data[i++];
                    if (b3 == 61) {
                        return buf.toByteArray();
                    }
                    b3 = base64DecodeChars[b3];
                } while (i < len && b3 == -1);
                if (b3 == -1) {
                    break;
                }
                buf.write(((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2));
     
                /* b4 */
                do {
                    b4 = data[i++];
                    if (b4 == 61) {
                        return buf.toByteArray();
                    }
                    b4 = base64DecodeChars[b4];
                } while (i < len && b4 == -1);
                if (b4 == -1) {
                    break;
                }
                buf.write(((b3 & 0x03) << 6) | b4);
            }
            return buf.toByteArray();
        }
    }

    MD5

    package com.bodi.repository;
    
    import java.security.MessageDigest;
    
    public class MD5Util {
        
        /**
         * 十六进制下数字到字符的映射数组
         */
        private final static String[] hexDigits = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"}; 
        
        /**
         * @Title: encodeByMD5
         * @Description: 对字符串进行MD5编码
         * @author yihj
         * @param @param originString
         * @param @return    参数
         * @return String    返回类型
         * @throws
         */
        public static String MD5(String originString){ 
            if (originString!=null) { 
                try { 
                    //创建具有指定算法名称的信息摘要 
                    MessageDigest md5 = MessageDigest.getInstance("MD5"); 
                    //使用指定的字节数组对摘要进行最后更新,然后完成摘要计算 
                    byte[] results = md5.digest(originString.getBytes()); 
                    //将得到的字节数组变成字符串返回  
                    String result = byteArrayToHexString(results); 
                    return result; 
                } catch (Exception e) { 
                    e.printStackTrace(); 
                } 
            } 
            return null; 
        } 
        
        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;
        }
    
    
        /**
         * @Title: byteArrayToHexString
         * @Description: 轮换字节数组为十六进制字符串 
         * @author yihj
         * @param @param b
         * @param @return    参数
         * @return String    返回类型
         * @throws
         */
        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(); 
        } 
        
        /**
         * @Title: byteToHexString
         * @Description: 将一个字节转化成十六进制形式的字符串 
         * @author yihj
         * @param @param b
         * @param @return    参数
         * @return String    返回类型
         * @throws
         */
        private static String byteToHexString(byte b){ 
            int n = b; 
            if(n<0) 
            n=256+n; 
            int d1 = n/16; 
            int d2 = n%16; 
            return hexDigits[d1] + hexDigits[d2]; 
        } 
        
        /**
         * MD5加密 byte 数据
         * 
         * @param source
         *            要加密字符串的byte数据
         * @return
         */
        public static String getMD5(byte[] source) {
            String s = null;
            char hexDigits[] = { // 用来将字节转换成 16 进制表示的字符
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
                    'e', 'f' };
            try {
                java.security.MessageDigest md = java.security.MessageDigest
                        .getInstance("MD5");
                md.update(source);
                byte tmp[] = md.digest(); // MD5 的计算结果是一个 128 位的长整数,
                                            // 用字节表示就是 16 个字节
                char str[] = new char[16 * 2]; // 每个字节用 16 进制表示的话,使用两个字符,
                                                // 所以表示成 16 进制需要 32 个字符
                int k = 0; // 表示转换结果中对应的字符位置
                for (int i = 0; i < 16; i++) { // 从第一个字节开始,对 MD5 的每一个字节
                                                // 转换成 16 进制字符的转换
                    byte byte0 = tmp[i]; // 取第 i 个字节
                    str[k++] = hexDigits[byte0 >>> 4 & 0xf]; // 取字节中高 4 位的数字转换,
                                                                // >>>
                                                                // 为逻辑右移,将符号位一起右移
                    str[k++] = hexDigits[byte0 & 0xf]; // 取字节中低 4 位的数字转换
                }
                s = new String(str); // 换后的结果转换为字符串
    
            } catch (Exception e) {
                e.printStackTrace();
            }
            return s;
        }
        
        
    }
    实际退款代码 逻辑
    @Override
        public void refundCallback(HttpServletRequest request, HttpServletResponse response) {
            log.info("退款  微信回调接口方法 start");
            String inputLine = "";
            String notityXml = "";
            try {
                while((inputLine = request.getReader().readLine()) != null){
                    notityXml += inputLine;
                }
                //关闭流
                request.getReader().close();
                log.info("退款  微信回调内容信息:"+notityXml);
                //解析成Map
                Map<String,String> map = doXMLParse(notityXml);
                //判断 退款是否成功
                if("SUCCESS".equals(map.get("return_code"))){
                    log.info("退款  微信回调返回是否退款成功:是");
                    //获得 返回的商户订单号
                    String passMap = AESUtil.decryptData(map.get("req_info"));
                    //拿到解密信息
                    map = doXMLParse(passMap);
                    //拿到解密后的订单号
                    String outTradeNo = map.get("out_trade_no");
                    
                    log.info("退款  微信回调返回商户订单号:"+map.get("out_trade_no"));
                    //支付成功 修改订单状态 通知微信成功回调
                    int sqlRow = orderJpaDao.updateOrderStatus("refunded",new Timestamp(System.currentTimeMillis()), outTradeNo);
                    if(sqlRow == 1) {
                        log.info("退款 微信回调 更改订单状态成功");
                    }
                }else {
                    //获得 返回的商户订单号
                    String passMap = AESUtil.decryptData(map.get("req_info"));
                    //拿到解密信息
                    map = doXMLParse(passMap);
                    //拿到解密后的订单号
                    String outTradeNo = map.get("out_trade_no");
                    //更改 状态为取消
                    int sqlRow = orderJpaDao.updateOrderStatus("canceled",new Timestamp(System.currentTimeMillis()), outTradeNo);
                    if(sqlRow == 1) {
                        log.info("退款 微信回调返回是否退款成功:否");
                    }
                }
           response.setContentType("text/xml");
           //给微信服务器返回 成功标示 否则会一直询问 咱们服务器 是否回调成功
                PrintWriter writer = response.getWriter();
                //封装 返回值
                StringBuffer buffer = new StringBuffer();
                buffer.append("<xml>");
                buffer.append("<return_code>SUCCESS</return_code>"); 
           buffer.append(
    "<return_msg>OK</return_msg>");
           buffer.append(
    "</xml>");
           
    //返回
           writer.print(buffer.toString()); }

        catch (IOException e) {
          e.printStackTrace();
        }
    catch (Exception e) {
          e.printStackTrace();
        }
       }

    5、注意事项

      1、退款 调用的时候需要证书 证书需要下载

      2、退款回调 需要解密 解密代码 在上面

  • 相关阅读:
    JAVA 一个接口多个实现类
    关于Web服务器
    美团买菜IOS版设备风控浅析与算法还原
    阿里App防Bot新版AliTigerTally方案浅析与算法还原1
    使用php的openssl_encrypt和python的pycrypt进行跨语言的对称加密和解密问题
    一个把人民币小写转换为大写中文的方法
    《重构》代码坏味道
    git 合并分支
    java中SPI机制 代码改变世界
    echo print print_r的区别
  • 原文地址:https://www.cnblogs.com/yi1036943655/p/9512522.html
Copyright © 2011-2022 走看看