Sfoglia il codice sorgente

代码调整,添加积分支付扣减顺序和商品退款,积分返回逻辑

wangzhijun 12 ore fa
parent
commit
a8626d6a9f

+ 27 - 1
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/AfterSaleOrderServiceImpl.java

@@ -41,6 +41,8 @@ public class AfterSaleOrderServiceImpl extends ServiceImpl<AfterSaleOrderMapper,
     private ProductOrderPaymentMapper productOrderPaymentMapper;
     @Resource
     private TRechargeService rechargeService;
+    @Resource
+    private com.ylx.point.service.IPointAccountService pointAccountService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -348,7 +350,31 @@ public class AfterSaleOrderServiceImpl extends ServiceImpl<AfterSaleOrderMapper,
             throw new ServiceException("未找到支付记录,无法退款");
         }
 
-        // 6. 调用微信退款接口
+        // 6. 处理积分退款(如果订单使用了积分)
+        if (productOrderInfo.getPointsUsed() != null && productOrderInfo.getPointsUsed() > 0) {
+            // 判断支付类型
+            Integer payType = productOrderInfo.getPayType();
+            if (payType == 1 || payType == 3) {
+                // 1-纯积分支付 或 3-积分+金额支付,需要退回积分
+                String refundOrderNo = "REFUND_POINT_" + productOrderInfo.getOrderNo();
+                Integer pointsToRefund = productOrderInfo.getPointsUsed();
+
+                try {
+                    Integer newBalance = pointAccountService.refundPoints(
+                        productOrderInfo.getOpenId(),
+                        pointsToRefund,
+                        refundOrderNo,
+                        "积分商品退款"
+                    );
+                    log.info("积分退款成功,退款积分:{},退款后余额:{}", pointsToRefund, newBalance);
+                } catch (Exception e) {
+                    log.error("积分退款失败", e);
+                    throw new ServiceException("积分退款失败:" + e.getMessage());
+                }
+            }
+        }
+
+        // 7. 调用微信退款接口
         // 生成退款单号
         String outRefundNo = generateRefundNo();
         // 退款金额

+ 3 - 1
nightFragrance-massage/src/main/java/com/ylx/point/enums/ActivityNameEnum.java

@@ -15,7 +15,9 @@ public enum ActivityNameEnum {
 
     BROWSE_MERCHANT(5, "浏览商户"),
 
-    USER_SIGN_IN(6, "每日签到");
+    USER_SIGN_IN(6, "每日签到"),
+
+    GOODS_REFUND(7, "商品退款");
 
     private final Integer code;
     private final String info;

+ 33 - 0
nightFragrance-massage/src/main/java/com/ylx/point/service/IPointAccountService.java

@@ -2,5 +2,38 @@ package com.ylx.point.service;
 
 public interface IPointAccountService {
 
+    /**
+     * 增加用户积分
+     * @param openId 用户openId
+     * @param points 积分数量(正数)
+     * @param activityName 活动名称
+     * @param bizOrderNo 业务订单号
+     * @param activityId 活动ID
+     * @param taskId 任务ID
+     * @param taskType 任务类型
+     * @return 变动后的余额
+     */
     Integer addPoints(String openId, int points, String activityName, String bizOrderNo, Long activityId, Long taskId, Integer taskType);
+
+    /**
+     * 扣减用户积分(用于积分商品下单)
+     * 扣减顺序:先获取的先扣减(FIFO原则)
+     * @param openId 用户openId
+     * @param pointsToDeduct 需要扣减的积分数量(正数)
+     * @param bizOrderNo 业务订单号
+     * @param activityName 活动名称(如:积分商品兑换)
+     * @return 变动后的余额
+     */
+    Integer deductPoints(String openId, int pointsToDeduct, String bizOrderNo, String activityName);
+
+    /**
+     * 退款积分(用于积分商品退款)
+     * 退回的积分没有过期时间
+     * @param openId 用户openId
+     * @param pointsToRefund 需要退回的积分数量(正数)
+     * @param bizOrderNo 业务订单号
+     * @param activityName 活动名称(如:积分商品退款)
+     * @return 变动后的余额
+     */
+    Integer refundPoints(String openId, int pointsToRefund, String bizOrderNo, String activityName);
 }

+ 149 - 21
nightFragrance-massage/src/main/java/com/ylx/point/service/impl/PointAccountServiceImpl.java

@@ -1,13 +1,13 @@
 package com.ylx.point.service.impl;
 
 import cn.hutool.core.date.DateUtil;
-import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ylx.common.exception.ServiceException;
 import com.ylx.point.domain.PointActivity;
 import com.ylx.point.domain.PointActivityExpirePolicy;
 import com.ylx.point.domain.PointUserLog;
+import com.ylx.point.enums.ActivityNameEnum;
 import com.ylx.point.enums.PointActivityExpirePolicyEnum;
 import com.ylx.point.service.IPointAccountService;
 import com.ylx.point.service.IPointActivityExpirePolicyService;
@@ -15,11 +15,12 @@ import com.ylx.point.service.IPointActivityService;
 import com.ylx.point.service.IPointUserLogService;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import springfox.documentation.annotations.Cacheable;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
-import java.util.*;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
 
 @Service
 public class PointAccountServiceImpl implements IPointAccountService {
@@ -68,37 +69,164 @@ public class PointAccountServiceImpl implements IPointAccountService {
 
         boolean saveSuccess = pointUserLogService.save(log);
         if (!saveSuccess) {
-            throw new RuntimeException("积分流水记录失败");
+            throw new ServiceException("积分流水记录失败");
         }
 
         return newBalance;
     }
 
     /**
-     * 获取用户当前可用积分余额
-     * 逻辑:SUM(所有收入记录) - SUM(所有支出记录)
-     * 或者更简单的:SUM(points),因为支出记录的 points 是负数
+     * 扣减用户积分(用于积分商品下单)
+     * 扣减顺序:先获取的先扣减(FIFO原则)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Integer deductPoints(String openId, int pointsToDeduct, String bizOrderNo, String activityName) {
+
+        // 1. 获取当前余额
+        Integer currentBalance = getBalance(openId);
+        if (currentBalance < pointsToDeduct) {
+            throw new ServiceException("积分余额不足,当前余额:" + currentBalance + ",需要扣减:" + pointsToDeduct);
+        }
+
+        // 2. 查询用户所有未过期的收入记录,按获取时间升序排序(FIFO原则)
+        LambdaQueryWrapper<PointUserLog> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(PointUserLog::getOpenId, openId)
+                .eq(PointUserLog::getOpType, 1)  // 1-收入
+                .eq(PointUserLog::getIsExpired, 0)  // 0-未过期
+                .and(wrapper -> wrapper.isNull(PointUserLog::getExpireTime)
+                        .or()
+                        .gt(PointUserLog::getExpireTime, new Date()))  // 未过期或无过期时间
+                .orderByAsc(PointUserLog::getCreateTime);  // 按获取时间升序
+
+        List<PointUserLog> availablePoints = pointUserLogService.list(queryWrapper);
+
+        // 3. 按FIFO原则扣减积分
+        int remainingToDeduct = pointsToDeduct;
+        for (PointUserLog sourceLog : availablePoints) {
+            if (remainingToDeduct <= 0) {
+                break;
+            }
+
+            // 计算这条记录可以扣减的积分
+            // 需要查询这条记录已经被扣减了多少
+            LambdaQueryWrapper<PointUserLog> deductQuery = new LambdaQueryWrapper<>();
+            deductQuery.eq(PointUserLog::getOpenId, openId)
+                    .eq(PointUserLog::getOpType, 2)  // 2-支出
+                    .eq(PointUserLog::getSourceLogId, sourceLog.getId());
+            List<PointUserLog> deductLogs = pointUserLogService.list(deductQuery);
+
+            int alreadyDeducted = deductLogs.stream()
+                    .mapToInt(PointUserLog::getPoints)
+                    .map(Math::abs)
+                    .sum();
+
+            int availableInThisRecord = sourceLog.getPoints() - alreadyDeducted;
+
+            if (availableInThisRecord <= 0) {
+                continue;  // 这条记录已经完全扣减,跳过
+            }
+
+            // 计算本次需要从这条记录扣减的积分
+            int deductFromThisRecord = Math.min(remainingToDeduct, availableInThisRecord);
+
+            // 4. 创建扣减记录
+            PointUserLog deductLog = new PointUserLog();
+            deductLog.setOpenId(openId);
+            deductLog.setPoints(-deductFromThisRecord);  // 负数表示扣减
+            deductLog.setOpType(2);  // 2-支出
+            deductLog.setBizOrderId(bizOrderNo);
+            deductLog.setSourceLogId(sourceLog.getId());  // 关联到收入记录
+            deductLog.setActivityName(activityName);
+            deductLog.setMonth(DateUtil.format(new Date(), "yyyyMM"));
+
+            // 计算扣减后的余额
+            Integer newBalance = currentBalance - deductFromThisRecord;
+            deductLog.setBalanceAfter(newBalance);
+
+            boolean saveSuccess = pointUserLogService.save(deductLog);
+            if (!saveSuccess) {
+                throw new ServiceException("积分扣减记录失败");
+            }
+
+            // 更新剩余需要扣减的积分
+            remainingToDeduct -= deductFromThisRecord;
+            currentBalance = newBalance;
+
+            // 5. 检查源记录是否完全扣减,如果是则标记为已过期
+            if (availableInThisRecord == deductFromThisRecord) {
+                sourceLog.setIsExpired(1);  // 1-已完全核销
+                pointUserLogService.updateById(sourceLog);
+            }
+        }
+
+        return currentBalance;
+    }
+
+    /**
+     * 退款积分(用于积分商品退款)
+     * 退回的积分没有过期时间
      */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Integer refundPoints(String openId, int pointsToRefund, String bizOrderNo, String activityName) {
+        if (pointsToRefund <= 0) {
+            return getBalance(openId);
+        }
+
+        // 1. 获取当前余额 (变动前)
+        Integer currentBalance = getBalance(openId);
+
+        // 2. 写入积分流水表 (point_user_log)
+        PointUserLog log = new PointUserLog();
+        log.setOpenId(openId);
+        log.setPoints(pointsToRefund);
+        log.setOpType(1);  // 1-收入
+        log.setBizOrderId(bizOrderNo);
+        log.setSourceLogId(null);
+        log.setIsExpired(0);  // 0-未过期
+        log.setActivityName(ActivityNameEnum.GOODS_REFUND.getInfo());
+        log.setExpireTime(null);  // 退款积分没有过期时间
+        log.setMonth(DateUtil.format(new Date(), "yyyyMM"));
+
+        // 计算变动后余额 (快照)
+        Integer newBalance = currentBalance + pointsToRefund;
+        log.setBalanceAfter(newBalance);
+
+        boolean saveSuccess = pointUserLogService.save(log);
+        if (!saveSuccess) {
+            throw new RuntimeException("积分退款记录失败");
+        }
+
+        return newBalance;
+    }
+
     /**
      * 获取用户当前可用积分余额
-     * 逻辑:SUM(points),支出记录的 points 为负数
+     * 逻辑:SUM(所有未过期的收入记录的points) - SUM(所有支出记录的points绝对值)
      */
     private Integer getBalance(String openId) {
-        // 使用 QueryWrapper (非 Lambda) 来执行原生 SQL 聚合函数
-        QueryWrapper<PointUserLog> queryWrapper = new QueryWrapper<>();
-        queryWrapper.select("SUM(points)");
-        queryWrapper.eq("open_id", openId);
+        // 1. 查询所有未过期的收入记录总和
+        QueryWrapper<PointUserLog> incomeWrapper = new QueryWrapper<>();
+        incomeWrapper.select("SUM(points)");
+        incomeWrapper.eq("open_id", openId);
+        incomeWrapper.eq("op_type", 1);  // 1-收入
+        incomeWrapper.ne("is_expired", 1);  // 排除已过期的记录
 
-        // 执行查询,获取单个 Object 结果
-        PointUserLog pointUserLog = pointUserLogService.getOne(queryWrapper);
+        PointUserLog incomeLog = pointUserLogService.getOne(incomeWrapper);
+        int totalIncome = (incomeLog != null && incomeLog.getPoints() != null) ? incomeLog.getPoints() : 0;
 
-        // 判空处理
-        if (ObjectUtil.isNull(pointUserLog)) {
-            return 0;
-        }
+        // 2. 查询所有支出记录总和(支出记录的points为负数,需要取绝对值)
+        QueryWrapper<PointUserLog> expenseWrapper = new QueryWrapper<>();
+        expenseWrapper.select("SUM(ABS(points))");
+        expenseWrapper.eq("open_id", openId);
+        expenseWrapper.in("op_type", 2, 3);  // 2-支出 3-过期
+
+        PointUserLog expenseLog = pointUserLogService.getOne(expenseWrapper);
+        int totalExpense = (expenseLog != null && expenseLog.getPoints() != null) ? expenseLog.getPoints() : 0;
 
-        // 类型转换并返回
-        return pointUserLog.getPoints();
+        // 3. 返回可用余额
+        return totalIncome - totalExpense;
     }