Procházet zdrojové kódy

Merge remote-tracking branch 'origin/master'

SheepHy před 1 týdnem
rodič
revize
a6f057d510
15 změnil soubory, kde provedl 597 přidání a 25 odebrání
  1. 50 10
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/service/WeChatPayService.java
  2. 5 2
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/service/impl/CoachServiceImpl.java
  3. 14 11
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/service/impl/OrderServiceImpl.java
  4. 2 2
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/config/WechatUrlConstants.java
  5. 74 0
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/entity/WxchatCallbackRefundData.java
  6. 58 0
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/paytest/WXTransferAesUtil.java
  7. 141 0
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/paytest/WechatCertificateService.java
  8. 34 0
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/paytest/WechatPayProperties.java
  9. 52 0
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/paytest/WxPayUtils.java
  10. 6 0
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/system/app/entity/AppOrder.java
  11. 104 0
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/system/app/entity/AppOrderRefundsInfo.java
  12. 10 0
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/system/app/mapper/AppOrderRefundsInfoMapper.java
  13. 8 0
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/system/app/service/IAppOrderRefundsInfoService.java
  14. 15 0
      national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/system/app/service/impl/AppOrderRefundsInfoServiceimpl.java
  15. 24 0
      national-motion-module-system/national-motion-system-biz/src/main/resources/cert/platform_cert.pem

+ 50 - 10
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/service/WeChatPayService.java

@@ -17,8 +17,10 @@ import org.jeecg.modules.pay.config.WechatUrlConstants;
 import org.jeecg.modules.pay.serverPay.WXPayUtility;
 import org.jeecg.modules.system.app.entity.AppOrder;
 import org.jeecg.modules.system.app.entity.AppOrderProInfo;
+import org.jeecg.modules.system.app.entity.AppOrderRefundsInfo;
 import org.jeecg.modules.system.app.mapper.AppOrderMapper;
 import org.jeecg.modules.system.app.mapper.AppOrderProInfoMapper;
+import org.jeecg.modules.system.app.mapper.AppOrderRefundsInfoMapper;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
@@ -29,6 +31,7 @@ import javax.crypto.spec.SecretKeySpec;
 import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
 import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.nio.charset.StandardCharsets;
 import java.security.GeneralSecurityException;
 import java.security.InvalidAlgorithmParameterException;
@@ -58,6 +61,9 @@ public class WeChatPayService {
     @Resource
     private AppOrderProInfoMapper appOrderProInfoMapper;
 
+    @Resource
+    private AppOrderRefundsInfoMapper appOrderRefundsInfoMapper;
+
 
     /**
      * 小程序支付拉起
@@ -233,7 +239,7 @@ public class WeChatPayService {
      * @param out_trade_no 发起支付时创建的商户订单号
      * @return null代表查询失败 SUCCESS-成功 USERPAYING和ACCEPT为中间态 其他为支付失败
      */
-    public String orderQueryByOutTradeNo(String out_trade_no) {
+    public JSONObject orderQueryByOutTradeNo(String out_trade_no) {
         String url = WechatUrlConstants.PAY_V3_QUERY_OUT;
         url = url.replace("{out_trade_no}", WXPayUtility.urlEncode(out_trade_no));
         Map<String, Object> args = new HashMap<>();
@@ -241,7 +247,7 @@ public class WeChatPayService {
         args.put("sub_mchid", WechatConstants.WECHAT_SUB_MCH_ID);
         url = url + "?" + WXPayUtility.urlEncode(args);
         JSONObject res = wechatPayV3Utils.sendGet(url);
-        return res == null ? null : res.getString("trade_state");
+        return res ;
     }
 
     /**
@@ -253,32 +259,53 @@ public class WeChatPayService {
         log.info("进入退款接口------>");
         log.info("执行操作的 原支付交易对应的商户订单号:{}", orderCode);
 
-        Integer totalFee = 1;
-        Integer total = 1;
+        BigDecimal totalFee = BigDecimal.ZERO;
+        BigDecimal total = BigDecimal.ZERO;
         AppOrder appOrder = appOrderMapper.selectOne(Wrappers.<AppOrder>lambdaQuery().eq(AppOrder::getOrderCode, orderCode).last("limit 1"));
         if (ObjectUtil.isNotEmpty(appOrder)) {
-            total = appOrder.getPrice().intValue();
-            totalFee = appOrder.getPrice().intValue();
+            total = appOrder.getPrice();
+            totalFee = total;
+            List<AppOrderProInfo> proInfoList = appOrderProInfoMapper.selectList(Wrappers.<AppOrderProInfo>lambdaQuery().eq(AppOrderProInfo::getOrderId, appOrder.getId()).eq(AppOrderProInfo::getType, CommonConstant.ORDER_PRO_INFO_TYPE_6));
+            if (ObjectUtil.isNotEmpty(proInfoList)) {
+                for (AppOrderProInfo appOrderProInfo : proInfoList) {
+                    totalFee = totalFee.subtract(appOrderProInfo.getPrice());
+                }
+            }
         }
         //退款单号
         String out_refund_no = generateOrderNumber(1);
 
-        //todo 创建退款订单
+        // 创建退款订单
+        AppOrderRefundsInfo appOrderRefundsInfo = new AppOrderRefundsInfo();
+
+        appOrderRefundsInfo.setOrderId(appOrder.getId());
+        appOrderRefundsInfo.setOrderCode(orderCode);
+        appOrderRefundsInfo.setOutRefundNo(out_refund_no);
+//        appOrderRefundsInfo.setRefundId();
+        appOrderRefundsInfo.setTransactionId(appOrder.getTransactionId());
+        appOrderRefundsInfo.setReason(reason);
+//        appOrderRefundsInfo.setSuccessTime();
+        appOrderRefundsInfo.setAmount(total);
+//        appOrderRefundsInfo.setNotifyRequest();
+        appOrderRefundsInfo.setCreateTime(new Date());
+
+        appOrderRefundsInfoMapper.insert(appOrderRefundsInfo);
 
 
         //todo 发起分账回退
 
         try {
             JSONObject params = new JSONObject();
+            params.put("sub_mchid", WechatConstants.WECHAT_SUB_MCH_ID);
             params.put("out_trade_no", orderCode);//商户订单号
             params.put("out_refund_no", out_refund_no);//商户退款单号
             params.put("reason", reason);//退款原因
             params.put("notify_url", WechatUrlConstants.PAY_V3_REFUND_NOTIFY);//退款通知
 
             JSONObject amount = new JSONObject();
-            amount.put("refund", (long) (totalFee * 100));//退款金额
+            amount.put("refund", (totalFee.multiply(BigDecimal.valueOf(100))).longValue());//退款金额
             amount.put("currency", "CNY");
-            amount.put("total", (long) (total * 100));//原订单金额
+            amount.put("total", (total.multiply(BigDecimal.valueOf(100))).longValue());//原订单金额
             params.put("amount", amount);
             // 执行请求POST 请求发送到微信退款接口
             JSONObject res = wechatPayV3Utils.sendPost(WechatUrlConstants.PAY_V3_REFUND, params);
@@ -329,7 +356,7 @@ public class WeChatPayService {
     public Map<String, Object> refundOrderNotify(String jsonData) throws IOException, GeneralSecurityException {
 
         //转为map格式
-        Map<String, String> jsonMap = JSONObject.parseObject(jsonData, Map.class);
+        Map jsonMap = JSONObject.parseObject(jsonData, Map.class);
 
         //退款成功后返回一个加密字段resource,以下为解密
         /**
@@ -345,6 +372,8 @@ public class WeChatPayService {
         String resultStr = decryptToString(associated_data.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
         Map<String, String> reqInfo = JSONObject.parseObject(resultStr, Map.class);
 
+        log.info("微信返回的退款通知数据:" + reqInfo);
+
         String refund_status = reqInfo.get("refund_status");//退款状态
         String out_trade_no = reqInfo.get("out_trade_no"); //订单号
 
@@ -370,6 +399,17 @@ public class WeChatPayService {
                     }
                 }
             }
+
+            // 创建退款订单
+            AppOrderRefundsInfo appOrderRefundsInfo = appOrderRefundsInfoMapper.selectOne(Wrappers.lambdaQuery(AppOrderRefundsInfo.class).eq(AppOrderRefundsInfo::getOrderId, order.getId()));
+
+            if (ObjectUtil.isNotEmpty(appOrderRefundsInfo)){
+                //        appOrderRefundsInfo.setRefundId();
+                appOrderRefundsInfo.setSuccessTime(new Date());
+//        appOrderRefundsInfo.setNotifyRequest();
+                appOrderRefundsInfoMapper.updateById(appOrderRefundsInfo);
+            }
+
             parm.put("code", "SUCCESS");
             parm.put("message", "成功");
         } else {

+ 5 - 2
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/service/impl/CoachServiceImpl.java

@@ -47,6 +47,9 @@ public class CoachServiceImpl implements ICoachService {
 
     @Autowired
     private AppOrderProInfoMapper appOrderProInfoMapper;
+    @Autowired
+    EvaluateMapper evaluateMapper;
+
 
     @Override
     public Result<List<AppCoachVO>> findCoachList() {
@@ -96,8 +99,8 @@ public class CoachServiceImpl implements ICoachService {
                     double km = PositionUtil.calculateDistance(appCoachDetailsRequestVO.getLatitude(), appCoachDetailsRequestVO.getLongitude(), courseResponseVo.getLatitude().doubleValue(), courseResponseVo.getLongitude().doubleValue());
                     courseResponseVo.setKm(km);
                 }
-
-
+                String positiveRating = evaluateMapper.findPositiveRating(courseResponseVo.getId());
+                courseResponseVo.setGoodRate(positiveRating);
             }
         }
         appCoachDetailsVO.setClassesNumber((long) getInstructorTeachingNum(appCoachDetailsVO.getUserId()));

+ 14 - 11
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/service/impl/OrderServiceImpl.java

@@ -17,6 +17,7 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
+import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.extern.log4j.Log4j2;
 import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -896,15 +897,15 @@ public class OrderServiceImpl implements IOrderService {
                             .eq(AppOrderProInfo::getProductId, appCourse.getId())
                             .eq(AppOrderProInfo::getFamilyUserId, familyUserId));
                     List<AppOrderProInfo> infoList = infos.stream().filter(info -> Objects.equals(info.getOrFreePro(), CommonConstant.STATUS_0_INT)).collect(Collectors.toList());
-//                    if (ObjectUtil.isNotEmpty(infoList)) {
-//                        throw new JeecgBootException("当前课程已下过单,请勿重复下单");
-//                    }
-//                    if (Objects.equals(createOrderForm.getOrFreeOrder(), CommonConstant.STATUS_1_INT)){
-//                        List<AppOrderProInfo> freeProList = infos.stream().filter(info -> Objects.equals(info.getOrFreePro(), CommonConstant.STATUS_0_INT)).collect(Collectors.toList());
-//                        if (ObjectUtil.isNotEmpty(freeProList)) {
-//                            throw new JeecgBootException("当前试听课课程已下过单,请勿重复下单");
-//                        }
-//                    }
+                    if (ObjectUtil.isNotEmpty(infoList)) {
+                        throw new JeecgBootException("当前课程已下过单,请勿重复下单");
+                    }
+                    if (Objects.equals(createOrderForm.getOrFreeOrder(), CommonConstant.STATUS_1_INT)) {
+                        List<AppOrderProInfo> freeProList = infos.stream().filter(info -> Objects.equals(info.getOrFreePro(), CommonConstant.STATUS_0_INT)).collect(Collectors.toList());
+                        if (ObjectUtil.isNotEmpty(freeProList)) {
+                            throw new JeecgBootException("当前试听课课程已下过单,请勿重复下单");
+                        }
+                    }
                     AppOrderProInfo appOrderProInfo = new AppOrderProInfo();
                     appOrderProInfo.setProductId(createOrderForm.getProductIds());
                     appOrderProInfo.setProductName(appCourse.getName());
@@ -1627,17 +1628,19 @@ public class OrderServiceImpl implements IOrderService {
     }
 
     @Override
-    public String orderQuery(String orderCode) throws IOException {
+    public String orderQuery(String orderCode){
 
         //查询订单
         AppOrder appOrder = appOrderMapper.selectOne(Wrappers.<AppOrder>lambdaQuery().eq(AppOrder::getOrderCode, orderCode).last("limit 1"));
 
         //null代表查询失败 SUCCESS-成功 USERPAYING和ACCEPT为中间态 其他为支付失败
-        String s = weChatPayService.orderQueryByOutTradeNo(orderCode);
+        JSONObject res = weChatPayService.orderQueryByOutTradeNo(orderCode);
+        String s = res == null ? null : res.getString("trade_state");;
         if ("SUCCESS".equals(s) || appOrder.getOriginalPrice().compareTo(BigDecimal.ZERO)==0) {
 
             if (ObjectUtil.isNotEmpty(appOrder) && Objects.equals(appOrder.getOrderStatus(), CommonConstant.ORDER_STATUS_0)) {
                 appOrder.setOrderStatus(1);
+                appOrder.setTransactionId(res.getString("transaction_id"));
                 appOrderMapper.updateById(appOrder);
                 List<AppOrderProInfo> proInfoList = appOrderProInfoMapper.selectList(Wrappers.<AppOrderProInfo>lambdaQuery().eq(AppOrderProInfo::getOrderId, appOrder.getId()));
                 if (CollUtil.isNotEmpty(proInfoList)) {

+ 2 - 2
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/config/WechatUrlConstants.java

@@ -17,10 +17,10 @@ public class WechatUrlConstants {
     public final static String PAY_V3_QUERY_OUT = "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/out-trade-no/{out_trade_no}";
 
     //微信支付v3 支付通知接口地址
-    public final static String PAY_V3_NOTIFY = "https://1caa0b6f.r28.cpolar.top/jeecg-boot/app/order/wechatPayNotify";
+    public final static String PAY_V3_NOTIFY = "https://37bfd286.r28.cpolar.top/jeecg-boot/app/order/wechatPayNotify";
 
     //微信支付v3 退款通知接口地址
-    public final static String PAY_V3_REFUND_NOTIFY = "https://1caa0b6f.r28.cpolar.top/jeecg-boot/app/order/refundOrderNotify";
+    public final static String PAY_V3_REFUND_NOTIFY = "https://37bfd286.r28.cpolar.top/jeecg-boot/app/order/refundOrderNotify";
 
     //服务商 添加分账接收方
     public final static String PAY_V3_RECEIVERS_ADD = "https://api.mch.weixin.qq.com/v3/profitsharing/receivers/add";

+ 74 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/entity/WxchatCallbackRefundData.java

@@ -0,0 +1,74 @@
+package org.jeecg.modules.pay.entity;
+
+import cn.hutool.core.date.DateUtil;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@Slf4j
+public class WxchatCallbackRefundData {
+
+    /**
+     * 商户订单号
+     */
+    private String orderId;
+
+
+    /**
+     * 商户退款单号,out_refund_no
+     */
+    private String refundId;
+
+    /**
+     * 微信支付系统生成的订单号
+     */
+    private String transactionId;
+
+    /**
+     * 微信支付系统生成的退款订单号
+     */
+    private String transactionRefundId;
+
+    /**
+     * 退款渠道
+     * ORIGINAL:原路退款
+     * BALANCE:退回到余额
+     * OTHER_BALANCE:原账户异常退到其他余额账户
+     * OTHER_BANKCARD:原银行卡异常退到其他银行卡
+     */
+    private String channel;
+
+    /**
+     * 退款成功时间
+     * 当前退款成功时才有此返回值
+     */
+    private Date successTime;
+
+    /**
+     * 退款状态
+     * 退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台-交易中心,手动处理此笔退款。
+     * SUCCESS:退款成功
+     * CLOSED:退款关闭
+     * PROCESSING:退款处理中
+     * ABNORMAL:退款异常
+     */
+    private String status;
+
+    /**
+     * 退款金额
+     */
+    private BigDecimal refundMoney;
+
+
+    public Date getSuccessTime() {
+        return successTime;
+    }
+
+    public void setSuccessTime(String successTime) {
+        // Hutool工具包的方法,自动识别一些常用格式的日期字符串
+        this.successTime = DateUtil.parse(successTime);
+    }
+}

+ 58 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/paytest/WXTransferAesUtil.java

@@ -0,0 +1,58 @@
+package org.jeecg.modules.pay.paytest;
+
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+
+public class WXTransferAesUtil {
+
+    static final int KEY_LENGTH_BYTE = 32;
+    static final int TAG_LENGTH_BIT = 128;
+    private final byte[] aesKey;
+
+    /**
+     * @param key ApiV3Key
+     */
+    public WXTransferAesUtil(byte[] key) {
+        if (key.length != KEY_LENGTH_BYTE) {
+            throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
+        }
+        this.aesKey = key;
+    }
+
+    /**
+     * AES 解密
+     *
+     * @param associatedData 附加数据
+     * @param nonce          随机串
+     * @param ciphertext     数据密文
+     */
+    public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) {
+        try {
+            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+            SecretKeySpec key = new SecretKeySpec(aesKey, "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 (GeneralSecurityException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    public static void main(String[] args) {
+        String associatedData = "certificate";
+        String nonce = "61f9c719728a";
+        String ciphertext = "sRvt… ";
+        WXTransferAesUtil WXTransferAesUtil = new WXTransferAesUtil("APIv3密钥".getBytes());
+        String s = WXTransferAesUtil.decryptToString(associatedData.getBytes(), nonce.getBytes(), ciphertext);
+        System.out.println(s);
+    }
+}

+ 141 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/paytest/WechatCertificateService.java

@@ -0,0 +1,141 @@
+package org.jeecg.modules.pay.paytest;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+//@Service
+@Slf4j
+public class WechatCertificateService {
+
+    //    @Resource
+    private WechatPayProperties properties;
+
+    private final Map<String, X509Certificate> certificateCache = new ConcurrentHashMap<>();
+
+//    // 初始化证书加载
+//    @PostConstruct
+//    public void init() {
+//        properties.getCertificates().forEach(cert -> {
+//            try {
+//                certificateCache.put(cert.getSerialNo(), loadCertificate(cert));
+//            } catch (Exception e) {
+//                log.error("证书加载失败: {}", cert.getSerialNo(), e);
+//            }
+//        });
+//    }
+//
+//    // 动态获取证书
+//    public X509Certificate getCertificate(String serialNo) throws Exception {
+//        if (certificateCache.containsKey(serialNo)) {
+//            return certificateCache.get(serialNo);
+//        }
+//
+//        // 调用微信接口获取证书列表
+//        List<Certificate> certs = getCertificatesFromApi();
+//        Certificate targetCert = certs.stream()
+//                .filter(c -> c.getSerialNo().equals(serialNo))
+//                .findFirst()
+//                .orElseThrow(() -> new RuntimeException("未找到有效证书"));
+//
+//        // 解密证书
+//        String decryptedCert = decryptCertificate(
+//                targetCert.getEncryptCertificate().getAssociatedData(),
+//                targetCert.getEncryptCertificate().getNonce(),
+//                targetCert.getEncryptCertificate().getCiphertext()
+//        );
+//
+//        // 保存证书文件
+//        saveCertificateFile(targetCert.getSerialNo(), decryptedCert);
+//
+//        // 加载证书
+//        X509Certificate x509Cert = loadCertificate(decryptedCert);
+//        certificateCache.put(serialNo, x509Cert);
+//        return x509Cert;
+//    }
+//
+//    // 自动刷新证书
+//    @Scheduled(fixedRateString = "${wechat.pay.auto-refresh-interval}000")
+//    public void autoRefreshCertificates() {
+//        try {
+//            List<Certificate> certs = getCertificatesFromApi();
+//            certs.forEach(cert -> {
+//                String serialNo = cert.getSerialNo();
+//                if (!certificateCache.containsKey(serialNo)) {
+//                    getCertificate(serialNo); // 触发加载
+//                }
+//            });
+//            log.info("微信证书自动刷新完成,当前缓存证书数:{}", certificateCache.size());
+//        } catch (Exception e) {
+//            log.error("证书刷新失败", e);
+//        }
+//    }
+//
+//    // 调用微信API获取证书列表
+//    private List<Certificate> getCertificatesFromApi() throws Exception {
+//        CloseableHttpClient httpClient = WechatHttpClientBuilder.create()
+//                .withMerchant(properties.getMchId(), properties.getSerialNo(),
+//                             privateKeyFromPath(properties.getPrivateKeyPath()))
+//                .build();
+//
+//        HttpGet request = new HttpGet("https://api.mch.weixin.qq.com/v3/certificates");
+//        request.setHeader("Accept", MediaType.APPLICATION_JSON_VALUE);
+//
+//        try (CloseableHttpResponse response = httpClient.execute(request)) {
+//            String body = EntityUtils.toString(response.getEntity());
+//            return JSON.parseObject(body, CertificatesResponse.class).getData();
+//        }
+//    }
+//
+//    // 证书解密工具
+//    private String decryptCertificate(String associatedData, String nonce, String ciphertext) {
+//        try {
+//            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+//            SecretKeySpec keySpec = new SecretKeySpec(
+//                    properties.getApiV3Key().getBytes(StandardCharsets.UTF_8),
+//                    "AES"
+//            );
+//            GCMParameterSpec gcmSpec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));
+//
+//            cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
+//            cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));
+//
+//            byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
+//            return new String(decrypted, StandardCharsets.UTF_8);
+//        } catch (Exception e) {
+//            throw new RuntimeException("证书解密失败", e);
+//        }
+//    }
+//
+//    // 证书文件存储
+//    private void saveCertificateFile(String serialNo, String certContent) {
+//        Path path = Paths.get(properties.getCertDir(), "cert_" + serialNo + ".pem");
+//        try {
+//            Files.write(path, ("-----BEGIN CERTIFICATE-----\n" + certContent + "\n-----END CERTIFICATE-----").getBytes());
+//        } catch (IOException e) {
+//            log.error("保存证书失败:{}", path, e);
+//        }
+//    }
+//
+//    // 加载证书
+//    private X509Certificate loadCertificate(String certContent) throws CertificateException {
+//        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+//        try (InputStream is = new ByteArrayInputStream(certContent.getBytes())) {
+//            return (X509Certificate) cf.generateCertificate(is);
+//        }
+//    }
+//
+//    // 私钥加载
+//    private PrivateKey privateKeyFromPath(String path) throws Exception {
+//        Resource resource = new ClassPathResource(path);
+//        try (InputStream is = resource.getInputStream()) {
+//            return PemUtils.loadPrivateKey(is);
+//        }
+//    }
+}

+ 34 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/paytest/WechatPayProperties.java

@@ -0,0 +1,34 @@
+package org.jeecg.modules.pay.paytest;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.List;
+
+@Data
+public class WechatPayProperties {
+    private String mchId;
+    private String apiV3Key;
+    private String privateKeyPath;
+    private String certDir;
+    private Long autoRefreshInterval;
+
+    // 证书响应实体
+    public static class CertificatesResponse {
+        private List<Certificate> data;
+    }
+
+    public static class Certificate {
+        private String serialNo;
+        private String effectiveTime;
+        private String expireTime;
+        private EncryptCertificate encryptCertificate;
+    }
+
+    public static class EncryptCertificate {
+        private String algorithm;
+        private String nonce;
+        private String associatedData;
+        private String ciphertext;
+    }
+}

+ 52 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/paytest/WxPayUtils.java

@@ -0,0 +1,52 @@
+package org.jeecg.modules.pay.paytest;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.wechat.pay.java.core.RSAAutoCertificateConfig;
+import com.wechat.pay.java.core.http.*;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+
+@Slf4j
+public class WxPayUtils {
+
+    /**
+     * 下载证书
+     */
+    public void downLoadCertificates() {
+        OkHttpClient okHttpClient = new OkHttpClient();
+        HttpClient httpClient = new DefaultHttpClientBuilder()
+                .config(rsaAutoCertificateConfig())
+                .okHttpClient(okHttpClient)
+                .build();
+        HttpHeaders headers = new HttpHeaders();
+        headers.addHeader("Accept", MediaType.APPLICATION_JSON.getValue());
+        HttpRequest executeSendGetHttpRequest = new HttpRequest.Builder()
+                .httpMethod(HttpMethod.GET)
+                .url("https://api.mch.weixin.qq.com/v3/certificates")
+                .headers(headers)
+                .build();
+        try {
+            HttpResponse<JSONObject> execute = httpClient.execute(executeSendGetHttpRequest, JSONObject.class);
+            JSONObject responseBody = execute.getServiceResponse();
+            log.info("下载平台证书返回:{}", responseBody.toString());
+        } catch (Exception e) {
+            log.error("下载平台证书异常", e);
+        }
+    }
+
+    /**
+     * API安全加密配置
+     */
+    private RSAAutoCertificateConfig rsaAutoCertificateConfig() {
+        return new RSAAutoCertificateConfig.Builder()
+                // 商户号
+                .merchantId("xxxxxxxxxxx")
+                // 商户API证书私钥的存放路径
+                .privateKeyFromPath("v3/apiclient_key.pem")
+                // 商户API证书序列号
+                .merchantSerialNumber("xxxxxxxxxxx")
+                // APIv3密钥
+                .apiV3Key("xxxxxxxxxxx")
+                .build();
+    }
+}

+ 6 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/system/app/entity/AppOrder.java

@@ -112,6 +112,12 @@ public class AppOrder implements Serializable {
     @Schema(description = "回调状态 0-未回调 1-已回调")
     private Integer callbackStatus;
 
+    /**
+     * 微信支付订单号
+     */
+    @Schema(description = "transactionId")
+    private String transactionId;
+
 	/**参赛资质*/
 	@Excel(name = "参赛资质", width = 15)
     @Schema(description = "参赛资质")

+ 104 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/system/app/entity/AppOrderRefundsInfo.java

@@ -0,0 +1,104 @@
+package org.jeecg.modules.system.app.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@TableName("nm_order_refunds_info")
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@Schema(description = "订单退款信息")
+public class AppOrderRefundsInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @TableId(type = IdType.ASSIGN_ID)
+    @Schema(description = "id")
+    private String id;
+
+    /**
+     * 订单ID
+     */
+    @Schema(description = "orderId")
+    private String orderId;
+
+    /**
+     * 商户订单号
+     */
+    @Schema(description = "orderCode")
+    private String orderCode;
+
+
+    /**
+     * 商户退款单号
+     */
+    @Schema(description = "outRefundNo")
+    private String outRefundNo;
+
+    /**
+     * 微信支付退款单号
+     */
+    @Schema(description = "refundId")
+    private String refundId;
+
+
+    /**
+     * 微信支付订单号
+     */
+    @Schema(description = "transactionId")
+    private String transactionId;
+
+    /**
+     * 退款原因
+     */
+    @Schema(description = "reason")
+    private String reason;
+
+    /**
+     * 退款成功时间
+     */
+    @Schema(description = "successTime")
+    private Date successTime;
+
+    /**
+     * 退款金额(元)
+     */
+    @Schema(description = "amount")
+    private BigDecimal amount;
+
+    /**
+     * 微信退款回调请求
+     */
+    @Schema(description = "notifyRequest")
+    private String notifyRequest;
+
+    /**
+     * 退款状态
+     */
+    @Schema(description = "status")
+    private String status;
+
+    /**
+     * 创建时间
+     */
+    @Schema(description = "createTime")
+    private Date createTime;
+
+    /**
+     * 修改时间
+     */
+    @Schema(description = "updateTime")
+    private Date updateTime;
+
+}

+ 10 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/system/app/mapper/AppOrderRefundsInfoMapper.java

@@ -0,0 +1,10 @@
+package org.jeecg.modules.system.app.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.jeecg.modules.system.app.entity.AppOrderProInfo;
+import org.jeecg.modules.system.app.entity.AppOrderRefundsInfo;
+
+@Mapper
+public interface AppOrderRefundsInfoMapper extends BaseMapper<AppOrderRefundsInfo> {
+}

+ 8 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/system/app/service/IAppOrderRefundsInfoService.java

@@ -0,0 +1,8 @@
+package org.jeecg.modules.system.app.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.jeecg.modules.system.app.entity.AppOrderProInfo;
+import org.jeecg.modules.system.app.entity.AppOrderRefundsInfo;
+
+public interface IAppOrderRefundsInfoService extends IService<AppOrderRefundsInfo> {
+}

+ 15 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/system/app/service/impl/AppOrderRefundsInfoServiceimpl.java

@@ -0,0 +1,15 @@
+package org.jeecg.modules.system.app.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.apache.ibatis.annotations.Mapper;
+import org.jeecg.modules.system.app.entity.AppOrderProInfo;
+import org.jeecg.modules.system.app.entity.AppOrderRefundsInfo;
+import org.jeecg.modules.system.app.mapper.AppOrderProInfoMapper;
+import org.jeecg.modules.system.app.mapper.AppOrderRefundsInfoMapper;
+import org.jeecg.modules.system.app.service.IAppOrderProInfoService;
+import org.jeecg.modules.system.app.service.IAppOrderRefundsInfoService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AppOrderRefundsInfoServiceimpl extends ServiceImpl<AppOrderRefundsInfoMapper, AppOrderRefundsInfo> implements IAppOrderRefundsInfoService {
+}

+ 24 - 0
national-motion-module-system/national-motion-system-biz/src/main/resources/cert/platform_cert.pem

@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEFDCCAvygAwIBAgIUXrZcwPkS6irdM2PqPaAjDfbakLwwDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
+FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
+Q0EwHhcNMjUwOTA1MDE0MTEwWhcNMzAwOTA0MDE0MTEwWjBuMRgwFgYDVQQDDA9U
+ZW5wYXkuY29tIHNpZ24xEzARBgNVBAoMClRlbnBheS5jb20xHTAbBgNVBAsMFFRl
+bnBheS5jb20gQ0EgQ2VudGVyMQswCQYDVQQGEwJDTjERMA8GA1UEBwwIU2hlblpo
+ZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZIxyPi1dMyGTlN2L3
+BJCzhHerkmnDX3EEci6TFHTag6/CSfF1RmgxUCfb6AJUn33gGwsACrf/HNTfA+ox
+okzeWmnLrAYMXsiZhO2IZaQJE4oel4pI51nBNaMRcN3HVu9QOCKm9P9USHj4hWPY
+iJvuUpiiMpDmcdrNRLfCS43FSCgozkbO0KYfiCMEhD30RBdOE/WhlCi61A1GeTBK
+9nuQeIsMUQikZH9a4uNi3AMNDwygfR65DSYkqw6feQibo96BYJRpZUILzVM033MA
+fpn1MApk1OUjrO/eq7FR3qOZN4BKsu7gz4nxzydYmnVLNZZFVLHlMMtSp0F5+Xch
+QCGrAgMBAAGjgbkwgbYwCQYDVR0TBAIwADALBgNVHQ8EBAMCA/gwgZsGA1UdHwSB
+kzCBkDCBjaCBiqCBh4aBhGh0dHA6Ly9ldmNhLml0cnVzLmNvbS5jbi9wdWJsaWMv
+aXRydXNjcmw/Q0E9MUJENDIyMEU1MERCQzA0QjA2QUQzOTc1NDk4NDZDMDFDM0U4
+RUJEMiZzZz1IQUNDNDcxQjY1NDIyRTEyQjI3QTlEMzNBODdBRDFDREY1OTI2RTE0
+MDM3MTANBgkqhkiG9w0BAQsFAAOCAQEAk6Oz4E11WD9tT2oG87uj2C1Zp1IjZSOc
+F88cR8CKgbqnGc+gcF8cmixm1sHEYHBXAPKuQXkDOZ73Yi4l8RyIhX6u+GVM6evC
+A60rJyLz7TZ5XJ1G26vucmlcPlhvz11dDknXiuvm4qF3HyiZXLzOVtnDsUTytRST
+HCH4DpvXSt6OWTUj8Gzq4erS7VLh+pdh2hUTlYCw86Pn9hKVK60uqq3GSedgH729
+uuBbfWNAfG4Dwrk8PbkG5hay3Sv2Aac5NK0iok3dLFHGdNYbrxeDozYmYraHSa6d
+9xwN+5EmAW6cnnyiJ6lbp1vcQrR9BGndvOZs+UVIolMk8u1gwyqWdg==
+-----END CERTIFICATE-----