微信支付(退款)

@Override
    @Transactional(readOnly = false,rollbackFor = Exception.class)
    public MessageBean refundOrder(HttpServletRequest request,Long id) {
        MessageBean result = new MessageBean();
        try {
            OrderAftersalesDetailsDO orderAftersales = orderAftersalesMapper.getOne(id);
            if (!orderAftersales.getStatus().equals(ConstantNumber.INTEGER_ONE)){
                throw new BDException("该售后订单流程异常");
            }
            Object Object = redisUtil.get("refundParam" + orderAftersales.getOrderCode());
            if (Object !=null){
                throw new BDException("该退款订单已申请退款,请等待微信处理");
            }
            Order order = orderMapper.getOne(orderAftersales.getOrderId());
			//订单支付金额
            String total_fee = order.getPayPrice().multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).toString();
            //退款金额
            String refund_fee = orderAftersales.getRefundPrice().multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).toString();
            //创建hashmap(用户获得签名)
            SortedMap<String, String> paraMap = new TreeMap<>();

            //设置请求参数(小程序ID)
            paraMap.put("appid", Constant.WX_LOGIN_APPID);
            //设置请求参数(商户号)
            paraMap.put("mch_id", Constant.MCH_ID);
            //设置请求参数(随机字符串)
            paraMap.put("nonce_str", WXPayUtil.generateNonceStr());
            //设置请求参数(商户订单号)
            paraMap.put("out_trade_no", order.getOrderCode());
            //设置请求参数(商户退款单号)
            paraMap.put("out_refund_no", orderAftersales.getOrderCode());
            //设置请求参数(订单金额)
//            paraMap.put("total_fee", total_fee);
            paraMap.put("total_fee", "1");
            //设置请求参数(退款金额)
            paraMap.put("refund_fee", "1");
            //设置请求参数(回调地址)
            paraMap.put("notify_url", "你自己的退款成功后回调地址");
            //调用逻辑传入参数按照字段名的 ASCII 码从小到大排序(字典序)
            String stringA = formatUrlMap(paraMap, false, false);

            //第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。(签名)
            String sign = MD5Util.MD5(stringA+"&key="+Constant.PATERNER_KEY).toUpperCase();
            //将参数 编写XML格式
            StringBuffer paramBuffer = new StringBuffer();
            paramBuffer.append("<xml>");
            paramBuffer.append("<appid>"+Constant.WX_LOGIN_APPID+"</appid>");
            paramBuffer.append("<mch_id>"+Constant.MCH_ID+"</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 = WXPayUtil.xmlToMap(doRefund(request,Constant.WXPAY_REFUND, new String(paramBuffer.toString().getBytes(), "ISO8859-1")));
                //应该创建 退款表数据
                if (map != null && (StringUtils.isNotBlank(map.get("return_code")) && "SUCCESS".equals(map.get("return_code")))) {
                    result.setData(map.get("return_code"));
                    result.setDesc(map.get("return_msg"));
                    redisUtil.set("refundParam"+orderAftersales.getOrderCode(),1);
                } else {
                    result.setData(map.get("return_code"));
                    result.setDesc(map.get("return_msg"));
                    result.setSuccess(false);
                    throw new BDException("退款失败,message:"+map.get("return_msg"));
                }
            } catch (UnsupportedEncodingException e) {
                logger.info("微信 退款 异常:" + e.getMessage());
                e.printStackTrace();
            } catch (Exception e) {
                logger.info("微信 退款 异常:" + e.getMessage());
                e.printStackTrace();
            }
            logger.info("微信 退款 成功");
        } catch (Exception e) {
            logger.error(ErrorInfoUtil.getErrorInfo(e));
            throw ExceptionFormatUtil.formatException(e, ErrorEnum.SYSTEM_ERROR);
        }
        return result;
    }

回调接口

@Override
    @Transactional(readOnly = false,rollbackFor = Exception.class)
    public void refundCallback(HttpServletRequest request, HttpServletResponse response) {
        logger.info("退款 微信回调接口方法 start");
        String inputLine = "";
        String notityXml = "";
        try {
            while((inputLine = request.getReader().readLine()) != null){
                notityXml += inputLine;
            }
            //关闭流
            request.getReader().close();
            logger.info("退款  微信回调内容信息:"+notityXml);
            //解析成Map
            Map<String,String> map = WXPayUtil.xmlToMap(notityXml);
            //判断 退款是否成功
            if("SUCCESS".equals(map.get("return_code"))){
                logger.info("退款 微信回调返回是否退款成功:是");
                //获得 返回的商户订单号
                String passMap = AESUtil.decryptData(map.get("req_info"));
                //拿到解密信息
                map = WXPayUtil.xmlToMap(passMap);
                //拿到解密后的订单号
                String outTradeNo = map.get("out_trade_no");
                //商家退款订单号
                String outRefundNo = map.get("out_refund_no");
                //修改售后单状态
                OrderAftersales orderAftersales = orderAftersalesMapper.getByOrderCode(outRefundNo);
                if (orderAftersales.getStatus().equals(ConstantNumber.INTEGER_ONE)) {
                    orderAftersales.setStatus(ConstantNumber.INTEGER_FOUR);
                    orderAftersales.setResponseTime(new Date());
                    orderAftersalesMapper.update(orderAftersales);
                    //修改订单状态
                    Order one = orderMapper.getOne(orderAftersales.getOrderId());
                    if (orderAftersales.getType().equals(Constant.INTEGER_ZERO)) {
                        one.setStatus(ConstantNumber.INTEGER_FIVE);
                        one.setRefundStatus(ConstantNumber.INTEGER_TWO);
                        orderMapper.update(one);
                        //修改库存
                        List<OrderCommodity> orderCommodities = orderCommodityMapper.getByOrderId(one.getId());
                        if (one.getOrderType()==0) {
                            orderCommodities.forEach(x -> {
                                CommoditySpecs commoditySpecs = commoditySpecsMapper.selectByPrimaryKey(x.getSpecId());
                                commoditySpecs.setStock(commoditySpecs.getStock() + x.getReturnQuantity());
                                commoditySpecsMapper.update(commoditySpecs);

                                x.setRefundStatus(ConstantNumber.INTEGER_TWO);
                                x.setReturnQuantity(x.getNum());
                                orderCommodityMapper.update(x);
                            });
                        }else {
                            orderCommodities.forEach(x -> {
                                SeckillCommoditySpecs commoditySpecs = seckillCommoditySpecsMapper.selectByPrimaryKey(x.getSpecId());
                                commoditySpecs.setStock(commoditySpecs.getStock() + x.getReturnQuantity());
                                seckillCommoditySpecsMapper.update(commoditySpecs);

                                CommoditySpecs commoditySpecs1 = commoditySpecsMapper.selectByPrimaryKey(commoditySpecs.getCommoditySpecsId());
                                commoditySpecs1.setStock(commoditySpecs1.getStock() + x.getReturnQuantity());
                                commoditySpecsMapper.update(commoditySpecs1);

                                x.setRefundStatus(ConstantNumber.INTEGER_TWO);
                                x.setReturnQuantity(x.getNum());
                                orderCommodityMapper.update(x);
                            });
                        }
                    } else {
                        OrderCommodity orderCommodity = orderCommodityMapper.selectByPrimaryKey(orderAftersales.getOrderCommodity());
                        orderCommodity.setRefundStatus(ConstantNumber.INTEGER_TWO);
                        orderCommodity.setReturnQuantity(orderCommodity.getReturnQuantity() + orderAftersales.getReturnQuantity());
                        orderCommodityMapper.update(orderCommodity);
                        if (one.getOrderType()==0) {
                            //修改库存
                            CommoditySpecs commoditySpecs = commoditySpecsMapper.selectByPrimaryKey(orderCommodity.getSpecId());
                            commoditySpecs.setStock(commoditySpecs.getStock() + orderCommodity.getReturnQuantity());
                            commoditySpecsMapper.update(commoditySpecs);
                        }else {
                            SeckillCommoditySpecs commoditySpecs = seckillCommoditySpecsMapper.selectByPrimaryKey(orderCommodity.getSpecId());
                            commoditySpecs.setStock(commoditySpecs.getStock() + orderCommodity.getReturnQuantity());
                            seckillCommoditySpecsMapper.update(commoditySpecs);

                            CommoditySpecs commoditySpecs1 = commoditySpecsMapper.selectByPrimaryKey(commoditySpecs.getCommoditySpecsId());
                            commoditySpecs1.setStock(commoditySpecs1.getStock() + orderCommodity.getReturnQuantity());
                            commoditySpecsMapper.update(commoditySpecs1);

                            orderCommodity.setRefundStatus(ConstantNumber.INTEGER_TWO);
                            orderCommodity.setReturnQuantity(orderCommodity.getNum());
                            orderCommodityMapper.update(orderCommodity);
                        }
                    }
                }
                logger.info("退款 微信回调返回商户订单号:"+map.get("out_refund_no"));
                //支付成功 修改订单状态 通知微信成功回调
                redisUtil.del("refundParam" + outRefundNo);
            }else {
                //获得 返回的商户订单号
                String passMap = AESUtil.decryptData(map.get("req_info"));
                //拿到解密信息
                map = WXPayUtil.xmlToMap(passMap);
                //拿到解密后的订单号
                String outTradeNo = map.get("out_trade_no");
                //更改 状态为取消

            }
            response.setContentType("text/xml");
            //给微信服务器返回 成功标示 否则会一直询问 咱们服务器 是否回调成功
            //封装 返回值
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("<xml>");
            stringBuilder.append("<return_code><![CDATA[SUCCESS]]></return_code>");
            stringBuilder.append("<return_msg><![CDATA[OK]]></return_msg>");
            stringBuilder.append("</xml>");
            response.getWriter().write(stringBuilder.toString());

        }catch (IOException e){
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
/**
     *
     * 方法用途: 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序),并且生成url参数串<br>
     * 实现步骤: <br>
     *
     * @param paraMap   要排序的Map对象
     * @param urlEncode   是否需要URLENCODE
     * @param keyToLower    是否需要将Key转换为全小写
     *            true:key转化成小写,false:不转化
     * @return
     */
    private static String formatUrlMap(Map<String, String> paraMap, boolean urlEncode, boolean keyToLower){
        String buff = "";
        Map<String, String> tmpMap = paraMap;
        try
        {
            List<Map.Entry<String, String>> infoIds = new ArrayList<>(tmpMap.entrySet());
            // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
            Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>()
            {
                @Override
                public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2)
                {
                    return (o1.getKey()).toString().compareTo(o2.getKey());
                }
            });
            // 构造URL 键值对的格式
            StringBuilder buf = new StringBuilder();
            for (Map.Entry<String, String> item : infoIds)
            {
                if (StringUtils.isNotBlank(item.getKey()))
                {
                    String key = item.getKey();
                    String val = item.getValue();
                    if (urlEncode)
                    {
                        val = URLEncoder.encode(val, "utf-8");
                    }
                    if (keyToLower)
                    {
                        buf.append(key.toLowerCase() + "=" + val);
                    } else
                    {
                        buf.append(key + "=" + val);
                    }
                    buf.append("&");
                }

            }
            buff = buf.toString();
            if (buff.isEmpty() == false)
            {
                buff = buff.substring(0, buff.length() - 1);
            }
        } catch (Exception e)
        {
            return null;
        }
        return buff;
    }

MD5Util

package com.guangyi.project.utils;

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;
    }


}

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(FileUtils.getResourcesPath()+"refund_certificate/apiclient_cert.p12");//P12文件目录 证书路径,文件在文章下面
        try {
            /**
             * 此处要改
             * */
            keyStore.load(instream, Constant.MCH_ID.toCharArray());//这里写密码..默认是你的MCHID
        } finally {
            instream.close();
        }

        // Trust own CA and all self-signed certs
        /**
         * 此处要改
         * */
        SSLContext sslcontext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, Constant.MCH_ID.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();
        }
    }

AESUtil

package com.guangyi.project.utils;

import com.guangyi.project.config.Constant;

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(Constant.PATERNER_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(Base64Utils.decode(base64Data)));
    }
}

其他的微信Utils在上一章有

链接:点击下载PKCS12证书
提取码:0121

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值