Browse Source

feat(order): 添加分账功能及优化退款逻辑

- 在 AppOrderController 中新增分账接口 profitSharing
- 引入 WeChatProfitSharingService 并注入到相关 Controller 和 JobService- 新增 ProfitSharingJonService 定时任务类,实现每日分账和分账结果查询
-优化 RefundJobService 中的过期订单自动退款逻辑- 修复 OrderController 中退款成功和失败回调的状态更新问题
- 移除分账结果查询中的冗余字段设置
- 增强日志记录,包括异常堆栈信息和关键业务节点日志
wzq 1 month ago
parent
commit
82a863542e

+ 4 - 5
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/app/controller/OrderController.java

@@ -268,7 +268,7 @@ public class OrderController {
             @Override
             public void success(WechatCallbackRefundData refundData) {
                 log.info("---------------------------------------------------微信退款成功回调");
-                // TODO 退款成功的业务逻辑,例如更改订单状态为退款成功等
+                //  退款成功的业务逻辑,例如更改订单状态为退款成功等
                 AppOrder appOrder = appOrderService.getOne(Wrappers.lambdaQuery(AppOrder.class).eq(AppOrder::getOrderCode, refundData.getOrderNo()));
                 appOrder.setOrderStatus(CommonConstant.ORDER_STATUS_6).setAfterSaleStatus(CommonConstant.NUMBER_2);
                 appOrderService.updateById(appOrder);
@@ -276,7 +276,7 @@ public class OrderController {
                         appOrder.getId()));
 
                 for (AppOrderProInfo appOrderProInfo : orderProInfoList) {
-                    appOrderProInfo.setOrderStatus(CommonConstant.ORDER_PRO_INFO_TYPE_6).setAfterSaleStatus(CommonConstant.NUMBER_2);
+                    appOrderProInfo.setOrderStatus(CommonConstant.ORDER_STATUS_6).setAfterSaleStatus(CommonConstant.NUMBER_2);
                     appOrderProInfoService.updateById(appOrderProInfo);
                 }
                 AppOrderRefundsInfo appOrderRefundsInfo = appOrderRefundsInfoService.getOne(Wrappers.<AppOrderRefundsInfo>lambdaQuery().eq(AppOrderRefundsInfo::getOrderId,
@@ -289,7 +289,7 @@ public class OrderController {
             @Override
             public void fail(WechatCallbackRefundData refundData) {
                 log.info("---------------------------------------------------微信退款失败回调");
-                // TODO 特殊情况下退款失败业务处理,例如银行卡冻结需要人工退款,此时可以邮件或短信提醒管理员,并携带退款单号等关键信息
+                //  特殊情况下退款失败业务处理,例如银行卡冻结需要人工退款,此时可以邮件或短信提醒管理员,并携带退款单号等关键信息
                 AppOrder appOrder = appOrderService.getOne(Wrappers.lambdaQuery(AppOrder.class).eq(AppOrder::getOrderCode, refundData.getOrderNo()));
                 appOrder.setOrderStatus(CommonConstant.ORDER_STATUS_6).setAfterSaleStatus(CommonConstant.NUMBER_2);
                 appOrderService.updateById(appOrder);
@@ -298,14 +298,13 @@ public class OrderController {
                         appOrder.getId()));
 
                 for (AppOrderProInfo appOrderProInfo : orderProInfoList) {
-                    appOrderProInfo.setOrderStatus(CommonConstant.ORDER_PRO_INFO_TYPE_6).setAfterSaleStatus(CommonConstant.NUMBER_2);
+                    appOrderProInfo.setOrderStatus(CommonConstant.ORDER_STATUS_6).setAfterSaleStatus(CommonConstant.NUMBER_2);
                     appOrderProInfoService.updateById(appOrderProInfo);
                 }
                 AppOrderRefundsInfo appOrderRefundsInfo =
                         appOrderRefundsInfoService.getOne(Wrappers.<AppOrderRefundsInfo>lambdaQuery().eq(AppOrderRefundsInfo::getOrderId,
                                 appOrder.getId()));
                 appOrderRefundsInfo.setStatus(refundData.getStatus());
-                appOrderRefundsInfo.setSuccessTime(refundData.getSuccessTime());
                 appOrderRefundsInfoService.updateById(appOrderRefundsInfo);
             }
         });

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

@@ -188,6 +188,7 @@ public class WeChatProfitSharingService {
                 }
             }
         } catch (Exception e) {
+            log.error("分账操作发生异常,订单号:{}", orderCode, e);
             e.printStackTrace();
         }
         return null;
@@ -257,6 +258,7 @@ public class WeChatProfitSharingService {
             }
             return res;
         } catch (Exception e) {
+            log.error("分账结果查询发生异常,订单号:{}", orderCode, e);
             e.printStackTrace();
         }
         return null;

+ 4 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/pay/unionPay/SM3Util.java

@@ -7,6 +7,10 @@ import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
 import java.util.Arrays;
 
 public class SM3Util {

+ 119 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/quartz/job/ProfitSharingJonService.java

@@ -0,0 +1,119 @@
+package org.jeecg.modules.quartz.job;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.modules.pay.config.WeChatProfitSharingService;
+import org.jeecg.modules.system.app.entity.AppOrder;
+import org.jeecg.modules.system.app.mapper.AppOrderMapper;
+import org.jeecg.modules.system.app.service.IAppOrderService;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+import java.util.List;
+
+import static java.lang.Thread.sleep;
+
+/**
+ * @Description 分账定时任务(包场和无固定场)
+ */
+@Slf4j
+@AllArgsConstructor
+public class ProfitSharingJonService {
+
+    private final WeChatProfitSharingService weChatProfitSharingService;
+    private final AppOrderMapper appOrderMapper;
+
+
+    /**
+     * 每日2点执行
+     */
+    @Scheduled(cron = "0 0 2 * * ?")
+    public void profitSharingExecute() {
+        log.info("开始执行分账定时任务");
+        try {
+            //包场和无固定场
+            List<AppOrder> appOrders = appOrderMapper.selectList(Wrappers.lambdaQuery(AppOrder.class)
+                    .eq(AppOrder::getPayStatus, CommonConstant.NUMBER_1)
+                    .in(AppOrder::getOrderType, CommonConstant.ORDER_TYPE_1, CommonConstant.ORDER_TYPE_2)
+            );
+            for (AppOrder appOrder : appOrders) {
+                //判断当前订单是否到期,是否进行分账
+                Date date = appOrder.getCreateTime();
+                // 将Date转换为Instant时间戳
+                Instant instant = date.toInstant();
+                // 结合系统默认时区生成ZonedDateTime
+                ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
+                // 提取日期部分
+                LocalDate localDate = zonedDateTime.toLocalDate();
+                //校验时间
+                boolean validRange = isValidRange(localDate, 5);
+                if (validRange) {
+                    //分账
+                    weChatProfitSharingService.profitSharing(appOrder.getOrderCode());
+                }
+            }
+        } catch (Exception e) {
+            log.error("分账定时任务异常", e);
+        }
+    }
+
+    /**
+     * 每日23点执行
+     */
+    @Scheduled(cron = "0 0 23 * * ?")
+    public void getProfitSharingResultExecute() {
+        log.info("开始执行分账结果查询定时任务");
+        try {
+            List<AppOrder> appOrders = appOrderMapper.selectList(Wrappers.lambdaQuery(AppOrder.class)
+                    .eq(AppOrder::getProfitSharingStatus, CommonConstant.NUMBER_1)
+            );
+            for (AppOrder appOrder : appOrders) {
+                sleep(1000);
+                //查询分账结果
+                weChatProfitSharingService.getProfitSharingResult(appOrder.getOrderCode());
+            }
+        } catch (Exception e) {
+            log.error("分账结果查询定时任务异常", e);
+        }
+    }
+
+    /**
+     * 验证当前时间是否满足:
+     * 1. 当前时间早于目标日期的30天后
+     * 2. 当前时间与目标日期的30天后间隔不超过N天
+     *
+     * @param targetDate 目标日期(null会抛出异常)
+     * @return 满足条件返回true,否则返回false
+     * @throws IllegalArgumentException 当输入日期为null时抛出
+     */
+    public static boolean isValidRange(LocalDate targetDate, int days) {
+        if (targetDate == null) {
+            throw new IllegalArgumentException("目标日期不能为null");
+        }
+
+        // 获取当前日期(带时区信息)
+        LocalDate currentDate = LocalDate.now();
+
+        // 计算目标日期的30天后日期
+        LocalDate deadlineDate = targetDate.plusDays(30);
+
+        // 验证当前时间必须早于30天后的日期
+        if (currentDate.isAfter(deadlineDate)) {
+            return false;
+        }
+
+        // 计算日期间隔(精确到天)
+        long daysBetween = ChronoUnit.DAYS.between(currentDate, deadlineDate);
+
+        // 验证间隔是否在N天内
+        return daysBetween <= days;
+    }
+}

+ 53 - 21
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/quartz/job/RefundJobService.java

@@ -1,19 +1,29 @@
 package org.jeecg.modules.quartz.job;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.modules.app.form.RefundOrderForm;
+import org.jeecg.modules.app.service.IOrderService;
+import org.jeecg.modules.pay.config.WeChatProfitSharingService;
 import org.jeecg.modules.system.app.entity.AppOrder;
 import org.jeecg.modules.system.app.entity.AppOrderProInfo;
+import org.jeecg.modules.system.app.entity.AppSitePlace;
 import org.jeecg.modules.system.app.service.IAppOrderProInfoService;
 import org.jeecg.modules.system.app.service.IAppOrderService;
+import org.jeecg.modules.system.app.service.IAppSitePlaceService;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.StringJoiner;
 
 /**
  * @author wzq
@@ -25,41 +35,63 @@ import java.util.List;
 //@Component
 public class RefundJobService {
 
-    private final IAppOrderService appOrderService;
+    private final IOrderService appOrderService;
+
+    private final IAppSitePlaceService appSitePlaceService;
 
     private final IAppOrderProInfoService appOrderProInfoService;
 
     /**
-     *  @Description 检查已过期未使用的订单进行退款
+     *  @Description 检查无固定场已过期未使用的订单进行退款
      */
-    @Scheduled(cron = "")
+    @Scheduled(cron = "0 0 3 * * ?")
     @Transactional(rollbackFor = Exception.class)
     public void execute(){
         log.info("开始执行检查已过期未使用的订单退款定时任务");
         try {
-            List<AppOrder> refundOrderList = new ArrayList<>();
-            List<AppOrder> list = appOrderService.list(Wrappers.<AppOrder>lambdaQuery().eq(AppOrder::getPayStatus, 1).eq(AppOrder::getOrderStatus, CommonConstant.ORDER_STATUS_1));
+            List<AppOrder> list = appOrderService.list(Wrappers.<AppOrder>lambdaQuery()
+                    .eq(AppOrder::getPayStatus, CommonConstant.NUMBER_1)
+                    .eq(AppOrder::getOrderStatus, CommonConstant.ORDER_STATUS_1)
+                    .eq(AppOrder::getOrderType, CommonConstant.ORDER_TYPE_2)
+            );
             for (AppOrder appOrder : list) {
-                List<AppOrderProInfo> orderProInfoList = appOrderProInfoService.list(Wrappers.lambdaQuery(AppOrderProInfo.class)
-                        .eq(AppOrderProInfo::getOrderId, appOrder.getId())
-                        .ne(AppOrderProInfo::getType, CommonConstant.ORDER_PRO_INFO_TYPE_6)
-                );
-                for (AppOrderProInfo appOrderProInfo : orderProInfoList) {
-                    if (appOrderProInfo.getOrderStatus() == 1) {
-                        // 订单未使用,判断是否已过期
-                        LocalDate expireTime = LocalDate.parse((appOrderProInfo.getExpireTime()));
-                        if (expireTime.isBefore(LocalDate.now())) {
-                            // 订单已过期
-                            refundOrderList.add(appOrder);
-                            continue;
+                //判断订单是否支持过期自动退款
+                String orgCode = appOrder.getOrgCode();
+                AppSitePlace sitePlace = appSitePlaceService.getOne(Wrappers.lambdaQuery(AppSitePlace.class).eq(AppSitePlace::getOrgCode, orgCode));
+                if (ObjectUtil.isNotEmpty(sitePlace)){
+                    if (sitePlace.getRefundType() != null && sitePlace.getRefundType() == 0){
+                        List<AppOrderProInfo> orderProInfoList = appOrderProInfoService.list(Wrappers.lambdaQuery(AppOrderProInfo.class)
+                                .eq(AppOrderProInfo::getOrderId, appOrder.getId())
+                                .eq(AppOrderProInfo::getType, CommonConstant.ORDER_PRO_INFO_TYPE_2)
+                        );
+                        List<AppOrderProInfo> refundOrderList = new ArrayList<>();
+                        for (AppOrderProInfo appOrderProInfo : orderProInfoList) {
+
+                            if (appOrderProInfo.getOrderStatus() == 1) {
+                                // 订单未使用,判断是否已过期
+                                LocalDateTime expireTime = LocalDateTime.parse(appOrderProInfo.getExpireTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
+                                if (expireTime.isBefore(LocalDateTime.now())) {
+                                    // 订单已过期
+                                    refundOrderList.add(appOrderProInfo);
+                                }
+                            }
+                        }
+                        //批量退款
+                        if (CollUtil.isNotEmpty(refundOrderList)){
+                            RefundOrderForm refundOrderForm = new RefundOrderForm();
+                            refundOrderForm.setOrderCode(appOrder.getOrderCode());
+
+                            StringJoiner sj = new StringJoiner(",");
+                            refundOrderList.forEach(appOrderProInfo -> sj.add(appOrderProInfo.getId()));
+
+                            refundOrderForm.setOrderProInfoIds(sj.toString());
+                            refundOrderForm.setReason("过期自动退款");
+
+                            appOrderService.refundOrder(refundOrderForm);
                         }
                     }
                 }
             }
-            //批量退款
-            for (AppOrder appOrder : refundOrderList) {
-                //todo 退款(分账回退)
-            }
         }catch (Exception e){
             log.error("检查已过期未使用的订单退款定时任务异常",e);
         }

+ 21 - 0
national-motion-module-system/national-motion-system-biz/src/main/java/org/jeecg/modules/system/app/controller/AppOrderController.java

@@ -1,16 +1,19 @@
 package org.jeecg.modules.system.app.controller;
 
+import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.formula.functions.T;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.aspect.annotation.AutoLog;
 import org.jeecg.common.system.base.controller.JeecgController;
 import org.jeecg.common.system.query.QueryGenerator;
+import org.jeecg.modules.pay.config.WeChatProfitSharingService;
 import org.jeecg.modules.system.app.dto.ExportConditionDTO;
 import org.jeecg.modules.system.app.entity.AppOrder;
 import org.jeecg.modules.system.app.form.AppOrderPageForm;
@@ -47,6 +50,8 @@ import java.util.List;
 public class AppOrderController extends JeecgController<AppOrder, IAppOrderService> {
 	@Autowired
 	private IAppOrderService appOrderService;
+    @Autowired
+    private WeChatProfitSharingService weChatProfitSharingService;
 	
 	/**
 	 * 分页列表查询
@@ -140,6 +145,22 @@ public class AppOrderController extends JeecgController<AppOrder, IAppOrderServi
 		appOrderService.removeById(id);
 		return Result.OK("删除成功!");
 	}
+
+    /**
+     * 分账
+     *
+     * @param orderCode
+     * @return
+     * @throws Exception
+     */
+    @AutoLog(value = "分账")
+    @Operation(summary = "分账")
+//    @RequiresPermissions("org.jeecg.modules.app:nm_order:profitSharing")
+    @DeleteMapping(value = "/profitSharing")
+    public Result<JSONObject> profitSharing(@RequestParam(name = "orderCode", required = true) String orderCode) throws Exception {
+        JSONObject jsonObject = weChatProfitSharingService.profitSharing(orderCode);
+        return Result.OK(jsonObject);
+    }
 	
 	/**
 	 *  批量删除