Browse Source

微信退款功能不再调用集成平台的接口

lighter 2 years ago
parent
commit
44c6b5f749

+ 0 - 79
src/main/java/thyyxxk/wxservice_server/service/AutoRefundService.java

@@ -1,79 +0,0 @@
-package thyyxxk.wxservice_server.service;
-
-import com.alibaba.fastjson.JSONObject;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.RestTemplate;
-import thyyxxk.wxservice_server.config.exception.ExceptionEnum;
-import thyyxxk.wxservice_server.dao.WxApiDao;
-import thyyxxk.wxservice_server.entity.ResultVo;
-import thyyxxk.wxservice_server.entity.wxapi.RfndPrm;
-import thyyxxk.wxservice_server.entity.wxapi.WxPayOrder;
-import thyyxxk.wxservice_server.utils.DateUtil;
-import thyyxxk.wxservice_server.utils.ResultVoUtil;
-
-import java.util.Date;
-
-/**
- * @description: 自动退款服务
- * @author: DingJie
- * @create: 2021/7/518:13
- */
-@Slf4j
-@Service
-public class AutoRefundService {
-    private final WxApiDao dao;
-
-    @Autowired
-    public AutoRefundService(WxApiDao dao) {
-        this.dao = dao;
-    }
-
-    @SuppressWarnings("unchecked")
-    public ResultVo<String> autoRefund(String tradeNo, String reason) {
-        WxPayOrder order = dao.selectOrderByTradeNo(tradeNo);
-        if (null == order) {
-            log.info("自动退款失败,未找到订单号为【{}】的订单。", tradeNo);
-            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR);
-        }
-        String url = "http://172.16.32.160:8706/wxRefund/refundOrder";
-        JSONObject param = new JSONObject();
-        param.put("refundOpDatetime", DateUtil.formatDatetime(new Date(), "yyyy-MM-dd HH:mm:ss"));
-        param.put("totalFee", order.getTotalFee());
-        param.put("tradeNo", tradeNo);
-        param.put("refundOpCode", "99999");
-        param.put("refundReason", reason);
-        RestTemplate template = new RestTemplate();
-        ResultVo<String> resultVo;
-        try {
-            resultVo = template.postForObject(url, param, ResultVo.class);
-        } catch (Exception e) {
-            resultVo = ResultVoUtil.fail(ExceptionEnum.INTERNAL_SERVER_ERROR, "自动退款失败,请联系窗口处理。");
-        }
-        return resultVo;
-    }
-
-    public ResultVo<String> refund(RfndPrm prm) {
-        log.info("自助机退款:{}", prm);
-        WxPayOrder order = dao.selectOrderByTradeNo(prm.getTradeNo());
-        if (null == order) {
-            log.info("退款失败,未找到订单号为【{}】的订单。", prm.getTradeNo());
-            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "没有找到符合订单号的订单。");
-        }
-        String url = "http://172.16.32.160:8706/wxRefund/refundOrder";
-        JSONObject param = new JSONObject();
-        param.put("refundOpDatetime", DateUtil.formatDatetime(new Date(), "yyyy-MM-dd HH:mm:ss"));
-        param.put("totalFee", order.getTotalFee());
-        param.put("tradeNo", prm.getTradeNo());
-        param.put("refundOpCode", "slf_srvc_mchn");
-        param.put("refundReason", prm.getRefundReason());
-        RestTemplate template = new RestTemplate();
-        try {
-            return template.postForObject(url, param, ResultVo.class);
-        } catch (Exception e) {
-            return ResultVoUtil.fail(ExceptionEnum.INTERNAL_SERVER_ERROR);
-        }
-    }
-
-}

+ 170 - 0
src/main/java/thyyxxk/wxservice_server/service/WxRefundService.java

@@ -0,0 +1,170 @@
+package thyyxxk.wxservice_server.service;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.DefaultHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
+import org.apache.http.util.EntityUtils;
+import org.dom4j.Document;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import thyyxxk.wxservice_server.config.exception.ExceptionEnum;
+import thyyxxk.wxservice_server.dao.WxApiDao;
+import thyyxxk.wxservice_server.entity.ResultVo;
+import thyyxxk.wxservice_server.entity.wxapi.RfndPrm;
+import thyyxxk.wxservice_server.entity.wxapi.WxPayOrder;
+import thyyxxk.wxservice_server.utils.*;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.util.Date;
+import java.util.TreeMap;
+
+/**
+ * @description: 自动退款服务
+ * @author: DingJie
+ * @create: 2021/7/518:13
+ */
+@Slf4j
+@Service
+public class WxRefundService {
+    private final WxApiDao dao;
+
+    @Autowired
+    public WxRefundService(WxApiDao dao) {
+        this.dao = dao;
+    }
+
+    public ResultVo<String> autoRefund(String tradeNo, String reason) {
+        TradeVectorUtil.addBeingRefund(tradeNo);
+        WxPayOrder order = dao.selectOrderByTradeNo(tradeNo);
+        if (null == order) {
+            log.info("自动退款失败,未找到订单号为【{}】的订单。", tradeNo);
+            TradeVectorUtil.removeBeingRefund(tradeNo);
+            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR);
+        }
+        order.setRefundOpDatetime(new Date());
+        order.setRefundOpCode("99999");
+        order.setRefundReason(reason);
+        try {
+            return executeRefund(order);
+        } catch (Exception e) {
+            return ResultVoUtil.fail(ExceptionEnum.INTERNAL_SERVER_ERROR, "自动退款失败,请联系窗口处理。");
+        } finally {
+            TradeVectorUtil.removeBeingRefund(tradeNo);
+        }
+    }
+
+    public ResultVo<String> refund(RfndPrm params) {
+        log.info("自助机退款:{}", params);
+        WxPayOrder order = dao.selectOrderByTradeNo(params.getTradeNo());
+        TradeVectorUtil.addBeingRefund(params.getTradeNo());
+        if (null == order) {
+            log.info("退款失败,未找到订单号为【{}】的订单。", params.getTradeNo());
+            TradeVectorUtil.removeBeingRefund(params.getTradeNo());
+            return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "没有找到符合订单号的订单。");
+        }
+        order.setRefundOpDatetime(new Date());
+        order.setRefundOpCode("slf_srvc_mchn");
+        order.setRefundReason(params.getRefundReason());
+        try {
+            return executeRefund(order);
+        } catch (Exception e) {
+            return ResultVoUtil.fail(ExceptionEnum.INTERNAL_SERVER_ERROR);
+        } finally {
+            TradeVectorUtil.removeBeingRefund(params.getTradeNo());
+        }
+    }
+
+    public ResultVo<String> executeRefund(WxPayOrder order) throws Exception {
+        String nonceStr = SnowFlakeId.instance().nextId();
+        String outRefundNo = SnowFlakeId.instance().nextId();
+        String refundFee = DecimalTool.moneyYuanToFen(order.getTotalFee());
+        TreeMap<String, String> map = new TreeMap<>();
+        map.put("appid", WxCertUtil.APP_ID);
+        map.put("mch_id", WxCertUtil.MERCHANT_ID);
+        map.put("nonce_str", nonceStr);
+        map.put("out_refund_no", outRefundNo);
+        map.put("out_trade_no", order.getTradeNo());
+        map.put("refund_fee", refundFee);
+        map.put("total_fee", refundFee);
+        String refundSign = WxPaySignUtil.createWxPaySign(map);
+        String xml = "<xml>" +
+                "<appid>" + WxCertUtil.APP_ID + "</appid>" +
+                "<mch_id>" + WxCertUtil.MERCHANT_ID + "</mch_id>" +
+                "<nonce_str>" + nonceStr + "</nonce_str>" +
+                "<out_refund_no>" + outRefundNo + "</out_refund_no>" +
+                "<out_trade_no>" + order.getTradeNo() + "</out_trade_no>" +
+                "<refund_fee>" + refundFee + "</refund_fee>" +
+                "<total_fee>" + refundFee + "</total_fee>" +
+                "<sign>" + refundSign + "</sign>" +
+                "</xml>";
+        String str = requestWithSsl(xml);
+        Document document = DocumentHelper.parseText(str);
+        Element root = document.getRootElement();
+        if ("SUCCESS".equals(root.element("return_code").getStringValue())) {
+            Element refundIdEle = root.element("refund_id");
+            if (null == refundIdEle) {
+                String msg = root.element("err_code_des").getStringValue();
+                log.info("微信退款失败:{}", msg);
+                if ("订单已全额退款".equals(msg)) {
+                    dao.alreadyRefund(order.getTradeNo(), msg);
+                }
+                return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, msg);
+            }
+            order.setRefundId(refundIdEle.getStringValue());
+            dao.updateRefundId(order);
+            log.info("微信退款成功:{}", order.getTradeNo());
+            return ResultVoUtil.success(order.getRefundId());
+        }
+        final String message = root.element("return_msg").getStringValue();
+        log.info("微信退款失败:{}", message);
+        return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, message);
+    }
+
+    private String requestWithSsl(String xml) throws Exception {
+        BasicHttpClientConnectionManager connManager;
+        WxCertUtil wxCertUtil = new WxCertUtil();
+        char[] password = WxCertUtil.MERCHANT_ID.toCharArray();
+        InputStream certStream = wxCertUtil.getCertStream();
+        KeyStore keyStore = KeyStore.getInstance("PKCS12");
+        keyStore.load(certStream, password);
+        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+        kmf.init(keyStore, password);
+        SSLContext sslContext = SSLContext.getInstance("TLS");
+        sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
+        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
+                sslContext, new String[]{"TLSv1"}, null, new DefaultHostnameVerifier());
+        connManager = new BasicHttpClientConnectionManager(
+                RegistryBuilder.<ConnectionSocketFactory>create()
+                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                        .register("https", sslConnectionSocketFactory).build(),
+                null, null, null);
+        CloseableHttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connManager).build();
+        String url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
+        HttpPost httpPost = new HttpPost(url);
+        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(6000).build();
+        httpPost.setConfig(requestConfig);
+        StringEntity postEntity = new StringEntity(xml, "UTF-8");
+        httpPost.addHeader("Content-Type", "text/xml");
+        httpPost.setEntity(postEntity);
+        HttpResponse httpResponse = httpClient.execute(httpPost);
+        HttpEntity httpEntity = httpResponse.getEntity();
+        return EntityUtils.toString(httpEntity, "UTF-8");
+    }
+}

+ 35 - 0
src/main/java/thyyxxk/wxservice_server/utils/WxCertUtil.java

@@ -0,0 +1,35 @@
+package thyyxxk.wxservice_server.utils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+/**
+ * @author dj
+ */
+@Slf4j
+public class WxCertUtil {
+    public static final String APP_ID = "wxbde6b16acad84204";
+    public static final String MERCHANT_ID = "1574204121";
+    public static final String MERCHANT_KEY = "lilaiwflzIOLJI2320JLZL2Llisd02ak";
+    public static final String CERT_PATH = "/home/wxcerts/apiclient_cert.p12";
+    public static byte[] CERTDATA;
+    static {
+        try {
+            File file = new File(CERT_PATH);
+            InputStream certStream = new FileInputStream(file);
+            CERTDATA = new byte[(int) file.length()];
+            //noinspection ResultOfMethodCallIgnored
+            certStream.read(CERTDATA);
+            certStream.close();
+        } catch (Exception e) {
+            log.error("读取微信支付证书出错", e);
+        }
+    }
+    public InputStream getCertStream() {
+        return new ByteArrayInputStream(CERTDATA);
+    }
+}

+ 51 - 43
src/main/java/thyyxxk/wxservice_server/utils/WxPaySignUtil.java

@@ -1,7 +1,6 @@
 package thyyxxk.wxservice_server.utils;
 
 import javax.crypto.Cipher;
-import javax.crypto.NoSuchPaddingException;
 import javax.crypto.spec.GCMParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 import java.nio.charset.StandardCharsets;
@@ -19,12 +18,12 @@ public class WxPaySignUtil {
 
     /**
      * 生成签名,用于在微信支付前,获取预支付时候需要使用的参数sign
-     * <p>算法参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3</p>
+     * 算法参考:<a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3">...</a>
      *
      * @param params 需要发送的所有数据设置为的Map
      * @return 签名sign
      */
-    public static String createSign(TreeMap<String, String> params) {
+    public static String createSign(TreeMap<String, String> params) throws Exception {
         List<Map.Entry<String, String>> infoIds = new ArrayList<>(params.entrySet());
         // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
         infoIds.sort(Map.Entry.comparingByKey());
@@ -40,31 +39,50 @@ public class WxPaySignUtil {
         return encryptByMd5(stringSignTemp).toUpperCase();
     }
 
+    /**
+     * 生成签名,用于在微信支付前,获取预支付时候需要使用的参数sign
+     * 算法参考:<a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3">...</a>
+     *
+     * @param params 需要发送的所有数据设置为的Map
+     * @return 签名sign
+     */
+    public static String createWxPaySign(TreeMap<String, String> params) throws Exception {
+        List<Map.Entry<String, String>> infoIds = new ArrayList<>(params.entrySet());
+        // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
+        infoIds.sort(Map.Entry.comparingByKey());
+        StringBuilder stringA = new StringBuilder();
+        for (Map.Entry<String, String> item : infoIds) {
+            String key = item.getKey();
+            String val = item.getValue();
+            if (StringUtil.notBlank(val)) {
+                stringA.append(key).append("=").append(val).append("&");
+            }
+        }
+        String stringSignTemp = stringA.substring(0, stringA.length() - 1) + "&key=" + WxCertUtil.MERCHANT_KEY;
+        return encryptByMd5(stringSignTemp).toUpperCase();
+    }
+
     /**
      * MD5加密
      */
-    private static String encryptByMd5(String sourceStr) {
+    private static String encryptByMd5(String sourceStr) throws Exception {
         String result = "";
-        try {
-            MessageDigest md = MessageDigest.getInstance("MD5");
-            md.update(sourceStr.getBytes(StandardCharsets.UTF_8));
-            byte[] b = md.digest();
-            int i;
-            StringBuilder buf = new StringBuilder();
-            for (byte value : b) {
-                i = value;
-                if (i < 0) {
-                    i += 256;
-                }
-                if (i < 16) {
-                    buf.append("0");
-                }
-                buf.append(Integer.toHexString(i));
+        MessageDigest md = MessageDigest.getInstance("MD5");
+        md.update(sourceStr.getBytes(StandardCharsets.UTF_8));
+        byte[] b = md.digest();
+        int i;
+        StringBuilder buf = new StringBuilder();
+        for (byte value : b) {
+            i = value;
+            if (i < 0) {
+                i += 256;
+            }
+            if (i < 16) {
+                buf.append("0");
             }
-            result = buf.toString();
-        } catch (Exception e) {
-            e.printStackTrace();
+            buf.append(Integer.toHexString(i));
         }
+        result = buf.toString();
         return result;
     }
 
@@ -85,31 +103,21 @@ public class WxPaySignUtil {
         return buf.toString();
     }
 
-    public static String sha1Encode(String str) {
+    public static String sha1Encode(String str) throws Exception {
         if (str == null) {
             return null;
         }
-        try {
-            MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
-            messageDigest.update(str.getBytes());
-            return getFormattedText(messageDigest.digest());
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
+        MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
+        messageDigest.update(str.getBytes());
+        return getFormattedText(messageDigest.digest());
     }
 
-    public static String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException {
-        try {
-            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
-            SecretKeySpec key = new SecretKeySpec(AES_KEY, "AES");
-            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
-            cipher.init(Cipher.DECRYPT_MODE, key, spec);
-            cipher.updateAAD(associatedData);
-            return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
-        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
-            throw new IllegalStateException(e);
-        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
-            throw new IllegalArgumentException(e);
-        }
+    public static String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws Exception {
+        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+        SecretKeySpec key = new SecretKeySpec(AES_KEY, "AES");
+        GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
+        cipher.init(Cipher.DECRYPT_MODE, key, spec);
+        cipher.updateAAD(associatedData);
+        return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
     }
 }