|
@@ -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");
|
|
|
+ }
|
|
|
+}
|