Explorar o código

!660 优化订单 OrderHandler 实现
Merge pull request !660 from 芋道源码/feature/mall_product_tmp

芋道源码 hai 1 ano
pai
achega
708af92a0f
Modificáronse 14 ficheiros con 481 adicións e 232 borrados
  1. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillProductMapper.java
  2. 31 196
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  3. 22 9
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainOrderHandler.java
  4. 118 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java
  5. 23 9
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationOrderHandler.java
  6. 42 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java
  7. 120 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeMemberPointOrderHandler.java
  8. 32 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java
  9. 46 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeProductSkuOrderHandler.java
  10. 20 6
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillOrderHandler.java
  11. 10 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApi.java
  12. 3 2
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/MemberExperienceBizTypeEnum.java
  13. 8 5
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java
  14. 5 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApiImpl.java

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/seckill/seckillactivity/SeckillProductMapper.java

@@ -41,7 +41,7 @@ public interface SeckillProductMapper extends BaseMapperX<SeckillProductDO> {
         Assert.isTrue(count > 0);
         return update(null, new LambdaUpdateWrapper<SeckillProductDO>()
                 .eq(SeckillProductDO::getId, id)
-                .gt(SeckillProductDO::getStock, count)
+                .ge(SeckillProductDO::getStock, count)
                 .setSql("stock = stock - " + count));
     }
 

+ 31 - 196
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java

@@ -14,23 +14,12 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
 import cn.iocoder.yudao.module.member.api.address.AddressApi;
 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
-import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
-import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
-import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
-import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
-import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
-import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
 import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
 import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
 import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
-import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
-import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
-import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
-import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
-import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
@@ -40,7 +29,6 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettle
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
 import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
-import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
@@ -48,15 +36,11 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
 import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
 import cn.iocoder.yudao.module.trade.dal.redis.no.TradeNoRedisDAO;
-import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.order.*;
 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
 import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog;
 import cn.iocoder.yudao.module.trade.framework.order.core.utils.TradeOrderLogUtils;
-import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
-import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
-import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
 import cn.iocoder.yudao.module.trade.service.cart.CartService;
 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
 import cn.iocoder.yudao.module.trade.service.message.TradeMessageService;
@@ -68,13 +52,15 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
 import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper;
 import lombok.extern.slf4j.Slf4j;
 import org.jetbrains.annotations.NotNull;
-import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@@ -109,30 +95,12 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     private DeliveryExpressService deliveryExpressService;
     @Resource
     private TradeMessageService tradeMessageService;
-    @Resource
-    private BrokerageUserService brokerageUserService;
-    @Resource
-    private BrokerageRecordService brokerageRecordService;
 
-    @Resource
-    private ProductSpuApi productSpuApi;
-    @Resource
-    private ProductSkuApi productSkuApi;
     @Resource
     private PayOrderApi payOrderApi;
     @Resource
     private AddressApi addressApi;
     @Resource
-    private CouponApi couponApi;
-    @Resource
-    private CombinationRecordApi combinationRecordApi;
-    @Resource
-    private MemberUserApi memberUserApi;
-    @Resource
-    private MemberLevelApi memberLevelApi;
-    @Resource
-    private MemberPointApi memberPointApi;
-    @Resource
     private ProductCommentApi productCommentApi;
 
     @Resource
@@ -199,7 +167,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         List<TradeOrderItemDO> orderItems = buildTradeOrderItems(order, calculateRespBO);
 
         // 2. 订单创建前的逻辑
-        beforeCreateTradeOrder(order, orderItems);
+        tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(order, orderItems));
 
         // 3. 保存订单
         tradeOrderMapper.insert(order);
@@ -234,11 +202,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
             order.setReceiverName(createReqVO.getReceiverName()).setReceiverMobile(createReqVO.getReceiverMobile());
             order.setPickUpVerifyCode(RandomUtil.randomNumbers(8)); // 随机一个核销码,长度为 8 位
         }
-        // 设置订单推广人
-        BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(order.getUserId());
-        if (brokerageUser != null && brokerageUser.getBindUserId() != null) {
-            order.setBrokerageUserId(brokerageUser.getBindUserId());
-        }
         return order;
     }
 
@@ -247,21 +210,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         return TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, calculateRespBO);
     }
 
-    /**
-     * 订单创建前,执行前置逻辑
-     *
-     * @param order      订单
-     * @param orderItems 订单项
-     */
-    private void beforeCreateTradeOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-        // 1. 执行订单创建前置处理器
-        // TODO @puhui999:这里有个纠结点;handler 的定义是只处理指定类型的订单的拓展逻辑;还是通用的 handler,类似可以处理优惠劵等等
-        tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(order, orderItems));
-
-        // 2. 下单时扣减商品库存
-        productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems));
-    }
-
     /**
      * 订单创建后,执行后置逻辑
      * <p>
@@ -276,27 +224,16 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         // 1. 执行订单创建后置处理器
         tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(order, orderItems));
 
-        // 2. 有使用优惠券时更新
-        // 不在前置扣减的原因,是因为优惠劵要记录使用的订单号
-        if (order.getCouponId() != null) {
-            couponApi.useCoupon(new CouponUseReqDTO().setId(order.getCouponId()).setUserId(order.getUserId())
-                    .setOrderId(order.getId()));
-        }
-
-        // 3. 扣减积分(抵扣)
-        // 不在前置扣减的原因,是因为积分扣减时,需要记录关联业务
-        reduceUserPoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_USE, order.getId());
-
-        // 4. 删除购物车商品
+        // 2. 删除购物车商品
         Set<Long> cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId);
         if (CollUtil.isNotEmpty(cartIds)) {
             cartService.deleteCart(order.getUserId(), cartIds);
         }
 
-        // 5. 生成预支付
+        // 3. 生成预支付
         createPayOrder(order, orderItems);
 
-        // 6. 插入订单日志
+        // 4. 插入订单日志
         TradeOrderLogUtils.setOrderInfo(order.getId(), null, order.getStatus());
 
         // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
@@ -330,18 +267,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
             throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
         }
 
-        // 3、订单支付成功后
+        // 3. 执行 TradeOrderHandler 的后置处理
         List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
         tradeOrderHandlers.forEach(handler -> handler.afterPayOrder(order, orderItems));
 
-        // 4.1 增加用户积分(赠送)
-        addUserPoint(order.getUserId(), order.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE, order.getId());
-        // 4.2 增加用户经验
-        getSelf().addUserExperienceAsync(order.getUserId(), order.getPayPrice(), order.getId());
-        // 4.3 增加用户佣金
-        getSelf().addBrokerageAsync(order.getUserId(), order.getId());
-
-        // 5. 记录订单日志
+        // 4. 记录订单日志
         TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.UNDELIVERED.getStatus());
         TradeOrderLogUtils.setUserInfo(order.getUserId(), UserTypeEnum.MEMBER.getValue());
     }
@@ -434,8 +364,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
                         .put("logisticsNo", express != null ? deliveryReqVO.getLogisticsNo() : "").build());
 
         // 4. 发送站内信
-        tradeMessageService.sendMessageWhenDeliveryOrder(new TradeOrderMessageWhenDeliveryOrderReqBO().setOrderId(order.getId())
-                .setUserId(order.getUserId()).setMessage(null));
+        tradeMessageService.sendMessageWhenDeliveryOrder(new TradeOrderMessageWhenDeliveryOrderReqBO()
+                .setOrderId(order.getId()).setUserId(order.getUserId()).setMessage(null));
     }
 
     /**
@@ -448,18 +378,13 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
      */
     private TradeOrderDO validateOrderDeliverable(Long id) {
         TradeOrderDO order = validateOrderExists(id);
-        // 校验订单是否退款
+        // 1. 校验订单是否未发货
         if (ObjectUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
             throw exception(ORDER_DELIVERY_FAIL_REFUND_STATUS_NOT_NONE);
         }
-        // 订单类型:拼团
-        if (Objects.equals(TradeOrderTypeEnum.COMBINATION.getType(), order.getType())) {
-            // 校验订单拼团是否成功
-            if (!combinationRecordApi.isCombinationRecordSuccess(order.getUserId(), order.getId())) {
-                throw exception(ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS);
-            }
-        }
 
+        // 2. 执行 TradeOrderHandler 前置处理
+        tradeOrderHandlers.forEach(handler -> handler.beforeDeliveryOrder(order));
         return order;
     }
 
@@ -616,30 +541,19 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
      * @param cancelType 取消类型
      */
     private void cancelOrder0(TradeOrderDO order, TradeOrderCancelTypeEnum cancelType) {
-        Long id = order.getId();
         // 1. 更新 TradeOrderDO 状态为已取消
-        int updateCount = tradeOrderMapper.updateByIdAndStatus(id, order.getStatus(),
+        int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(),
                 new TradeOrderDO().setStatus(TradeOrderStatusEnum.CANCELED.getStatus())
                         .setCancelType(cancelType.getType()).setCancelTime(LocalDateTime.now()));
         if (updateCount == 0) {
             throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
         }
 
-        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
-        // 3. 回滚库存
-        productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems));
-        // 3.1、 活动相关的回滚
-        tradeOrderHandlers.forEach(handler -> handler.cancelOrder(order, orderItems));
-
-        // 4. 回滚优惠券
-        if (order.getCouponId() != null && order.getCouponId() > 0) {
-            couponApi.returnUsedCoupon(order.getCouponId());
-        }
-
-        // 5. 回滚积分(抵扣的)
-        addUserPoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_CANCEL, order.getId());
+        // 2. 执行 TradeOrderHandler 的后置处理
+        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
+        tradeOrderHandlers.forEach(handler -> handler.afterCancelOrder(order, orderItems));
 
-        // 6. 增加订单日志
+        // 3. 增加订单日志
         TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.CANCELED.getStatus());
     }
 
@@ -660,8 +574,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
                 .setStatus(TradeOrderStatusEnum.CANCELED.getStatus())
                 .setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now()));
 
-        // 2. 退还优惠券
-        couponApi.returnUsedCoupon(order.getCouponId());
+        // 2. 执行 TradeOrderHandler 的后置处理
+        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId());
+        tradeOrderHandlers.forEach(handler -> handler.afterCancelOrder(order, orderItems));
     }
 
     @Override
@@ -789,13 +704,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void updateOrderItemWhenAfterSaleSuccess(Long id, Integer refundPrice) {
-        // 1. 更新订单项
+        // 1.1 更新订单项
         updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
                 TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(), null);
-
-        // 2.1 更新订单的退款金额、积分
+        // 1.2 执行 TradeOrderHandler 的后置处理
         TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(id);
         TradeOrderDO order = tradeOrderMapper.selectById(orderItem.getOrderId());
+        tradeOrderHandlers.forEach(handler -> handler.afterCancelOrderItem(order, orderItem));
+
+        // 2.1 更新订单的退款金额、积分
         Integer orderRefundPrice = order.getRefundPrice() + refundPrice;
         Integer orderRefundPoint = order.getRefundPoint() + orderItem.getUsePoint();
         Integer refundStatus = isAllOrderItemAfterSaleSuccess(order.getId()) ?
@@ -806,23 +723,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
                 .setRefundPrice(orderRefundPrice).setRefundPoint(orderRefundPoint));
         // 2.2 如果全部退款,则进行取消订单
         getSelf().cancelOrderByAfterSale(order, orderRefundPrice);
-
-
-        // 3. 回滚库存
-        productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(Collections.singletonList(orderItem)));
-        // 3.1、 活动相关的回滚
-        tradeOrderHandlers.forEach(handler -> handler.cancelOrder(order, Collections.singletonList(orderItem)));
-
-        // 4.1 回滚积分:扣减用户积分(赠送的)
-        reduceUserPoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.AFTER_SALE_DEDUCT_GIVE, orderItem.getAfterSaleId());
-        // 4.2 回滚积分:增加用户积分(返还抵扣)
-        addUserPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.AFTER_SALE_REFUND_USED, orderItem.getAfterSaleId());
-
-        // 5. 回滚经验:扣减用户经验
-        getSelf().reduceUserExperienceAsync(order.getUserId(), refundPrice, orderItem.getAfterSaleId());
-
-        // 6. 回滚佣金:更新分佣记录为已失效
-        getSelf().cancelBrokerageAsync(order.getUserId(), id);
     }
 
     @Override
@@ -839,6 +739,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         if (updateCount <= 0) {
             throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL);
         }
+
     }
 
     /**
@@ -939,7 +840,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_COMMENT)
     public void createOrderItemCommentBySystemBySystem(TradeOrderDO order) {
         // 1. 查询未评论的订单项
-        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderIdAndCommentStatus(order.getId(), Boolean.FALSE);
+        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderIdAndCommentStatus(
+                order.getId(), Boolean.FALSE);
         if (CollUtil.isEmpty(orderItems)) {
             return;
         }
@@ -982,73 +884,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
 
     // =================== 营销相关的操作 ===================
 
-    @Async
-    protected void addUserExperienceAsync(Long userId, Integer payPrice, Long orderId) {
-        int bizType = MemberExperienceBizTypeEnum.ORDER.getType();
-        memberLevelApi.addExperience(userId, payPrice, bizType, String.valueOf(orderId));
-    }
-
-    @Async
-    protected void reduceUserExperienceAsync(Long userId, Integer refundPrice, Long afterSaleId) {
-        int bizType = MemberExperienceBizTypeEnum.REFUND.getType();
-        memberLevelApi.addExperience(userId, -refundPrice, bizType, String.valueOf(afterSaleId));
-    }
-
-    /**
-     * 添加用户积分
-     * <p>
-     * 目前是支付成功后,就会创建积分记录。
-     * <p>
-     * 业内还有两种做法,可以根据自己的业务调整:
-     * 1. 确认收货后,才创建积分记录
-     * 2. 支付 or 下单成功时,创建积分记录(冻结),确认收货解冻或者 n 天后解冻
-     *
-     * @param userId  用户编号
-     * @param point   增加积分数量
-     * @param bizType 业务编号
-     * @param bizId   业务编号
-     */
-    protected void addUserPoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) {
-        if (point != null && point > 0) {
-            memberPointApi.addPoint(userId, point, bizType.getType(), String.valueOf(bizId));
-        }
-    }
-
-    protected void reduceUserPoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) {
-        if (point != null && point > 0) {
-            memberPointApi.reducePoint(userId, point, bizType.getType(), String.valueOf(bizId));
-        }
-    }
-
-    /**
-     * 创建分销记录
-     * <p>
-     * 目前是支付成功后,就会创建分销记录。
-     * <p>
-     * 业内还有两种做法,可以根据自己的业务调整:
-     * 1. 确认收货后,才创建分销记录
-     * 2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻
-     *
-     * @param userId  用户编号
-     * @param orderId 订单编号
-     */
-    @Async
-    protected void addBrokerageAsync(Long userId, Long orderId) {
-        MemberUserRespDTO user = memberUserApi.getUser(userId);
-        Assert.notNull(user);
-        // 每一个订单项,都会去生成分销记录
-        List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(orderId);
-        List<BrokerageAddReqBO> addList = convertList(orderItems,
-                item -> TradeOrderConvert.INSTANCE.convert(user, item,
-                        productSpuApi.getSpu(item.getSpuId()), productSkuApi.getSku(item.getSkuId())));
-        brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, addList);
-    }
-
-    @Async
-    protected void cancelBrokerageAsync(Long userId, Long orderItemId) {
-        brokerageRecordService.cancelBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, String.valueOf(orderItemId));
-    }
-
     /**
      * 获得自身的代理对象,解决 AOP 生效问题
      *

+ 22 - 9
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainHandler.java → yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBargainOrderHandler.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.trade.service.order.handler;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Assert;
 import cn.iocoder.yudao.module.promotion.api.bargain.BargainActivityApi;
 import cn.iocoder.yudao.module.promotion.api.bargain.BargainRecordApi;
@@ -12,12 +13,12 @@ import javax.annotation.Resource;
 import java.util.List;
 
 /**
- * 砍价订单 handler 实现类
+ * 砍价订单的 {@link TradeOrderHandler} 实现类
  *
  * @author HUIHUI
  */
 @Component
-public class TradeBargainHandler implements TradeOrderHandler {
+public class TradeBargainOrderHandler implements TradeOrderHandler {
 
     @Resource
     private BargainActivityApi bargainActivityApi;
@@ -26,7 +27,7 @@ public class TradeBargainHandler implements TradeOrderHandler {
 
     @Override
     public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-        if (TradeOrderTypeEnum.isBargain(order.getType())) {
+        if (!TradeOrderTypeEnum.isBargain(order.getType())) {
             return;
         }
         // 明确校验一下
@@ -39,7 +40,7 @@ public class TradeBargainHandler implements TradeOrderHandler {
 
     @Override
     public void afterOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-        if (TradeOrderTypeEnum.isBargain(order.getType())) {
+        if (!TradeOrderTypeEnum.isBargain(order.getType())) {
             return;
         }
         // 明确校验一下
@@ -50,16 +51,28 @@ public class TradeBargainHandler implements TradeOrderHandler {
     }
 
     @Override
-    public void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-        if (TradeOrderTypeEnum.isBargain(order.getType())) {
+    public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        if (!TradeOrderTypeEnum.isBargain(order.getType())) {
             return;
         }
         // 明确校验一下
         Assert.isTrue(orderItems.size() == 1, "砍价时,只允许选择一个商品");
 
-        // 恢复砍价活动的库存
-        bargainActivityApi.updateBargainActivityStock(order.getBargainActivityId(),
-                orderItems.get(0).getCount());
+        // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚
+        orderItems = filterOrderItemListByNoneAfterSale(orderItems);
+        if (CollUtil.isEmpty(orderItems)) {
+            return;
+        }
+        afterCancelOrderItem(order, orderItems.get(0));
+    }
+
+    @Override
+    public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
+        if (!TradeOrderTypeEnum.isBargain(order.getType())) {
+            return;
+        }
+        // 恢复(增加)砍价活动的库存
+        bargainActivityApi.updateBargainActivityStock(order.getBargainActivityId(), orderItem.getCount());
     }
 
 }

+ 118 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java

@@ -0,0 +1,118 @@
+package cn.iocoder.yudao.module.trade.service.order.handler;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
+import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
+import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
+import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
+import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
+import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+
+/**
+ * 订单分销的 {@link TradeOrderHandler} 实现类
+ *
+ * @author 芋道源码
+ */
+@Component
+public class TradeBrokerageOrderHandler implements TradeOrderHandler {
+
+    @Resource
+    private MemberUserApi memberUserApi;
+    @Resource
+    private ProductSpuApi productSpuApi;
+    @Resource
+    private ProductSkuApi productSkuApi;
+
+    @Resource
+    private BrokerageRecordService brokerageRecordService;
+    @Resource
+    private BrokerageUserService brokerageUserService;
+
+    @Override
+    public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        // 设置订单推广人
+        BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(order.getUserId());
+        if (brokerageUser != null && brokerageUser.getBindUserId() != null) {
+            order.setBrokerageUserId(brokerageUser.getBindUserId());
+        }
+    }
+
+    @Override
+    public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        if (order.getBrokerageUserId() == null) {
+            return;
+        }
+        addBrokerage(order.getUserId(), orderItems);
+    }
+
+    @Override
+    public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        // 如果是未支付的订单,不会产生分销结果,所以直接 return
+        if (!order.getPayStatus()) {
+            return;
+        }
+        if (order.getBrokerageUserId() == null) {
+            return;
+        }
+
+        // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚
+        orderItems = filterOrderItemListByNoneAfterSale(orderItems);
+        if (CollUtil.isEmpty(orderItems)) {
+            return;
+        }
+        orderItems.forEach(orderItem -> afterCancelOrderItem(order, orderItem));
+    }
+
+    @Override
+    public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
+        if (order.getBrokerageUserId() == null) {
+            return;
+        }
+        cancelBrokerage(order.getId(), orderItem.getOrderId());
+    }
+
+    /**
+     * 创建分销记录
+     * <p>
+     * 目前是支付成功后,就会创建分销记录。
+     * <p>
+     * 业内还有两种做法,可以根据自己的业务调整:
+     * 1. 确认收货后,才创建分销记录
+     * 2. 支付 or 下单成功时,创建分销记录(冻结),确认收货解冻或者 n 天后解冻
+     *
+     * @param userId  用户编号
+     * @param orderItems 订单项
+     */
+    protected void addBrokerage(Long userId, List<TradeOrderItemDO> orderItems) {
+        MemberUserRespDTO user = memberUserApi.getUser(userId);
+        Assert.notNull(user);
+        ProductSpuRespDTO spu = productSpuApi.getSpu(orderItems.get(0).getSpuId());
+        Assert.notNull(spu);
+        ProductSkuRespDTO sku = productSkuApi.getSku(orderItems.get(0).getSkuId());
+
+        // 每一个订单项,都会去生成分销记录
+        List<BrokerageAddReqBO> addList = convertList(orderItems,
+                item -> TradeOrderConvert.INSTANCE.convert(user, item, spu, sku));
+        brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, addList);
+    }
+
+    protected void cancelBrokerage(Long userId, Long orderItemId) {
+        brokerageRecordService.cancelBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, String.valueOf(orderItemId));
+    }
+
+}

+ 23 - 9
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationHandler.java → yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationOrderHandler.java

@@ -18,14 +18,15 @@ import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_CREATE_FAIL_EXIST_UNPAID;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS;
 
 /**
- * 拼团订单 handler 接口实现类
+ * 拼团订单的 {@link TradeOrderHandler} 实现类
  *
  * @author HUIHUI
  */
 @Component
-public class TradeCombinationHandler implements TradeOrderHandler {
+public class TradeCombinationOrderHandler implements TradeOrderHandler {
 
     @Resource
     @Lazy
@@ -41,16 +42,16 @@ public class TradeCombinationHandler implements TradeOrderHandler {
     @Override
     public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
         // 如果不是拼团订单则结束
-        if (TradeOrderTypeEnum.isCombination(order.getType())) {
+        if (!TradeOrderTypeEnum.isCombination(order.getType())) {
             return;
         }
         Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
 
-        // 校验是否满足拼团活动相关限制
+        // 1. 校验是否满足拼团活动相关限制
         TradeOrderItemDO item = orderItems.get(0);
         combinationRecordApi.validateCombinationRecord(order.getUserId(), order.getCombinationActivityId(),
                 order.getCombinationHeadId(), item.getSkuId(), item.getCount());
-        // 校验该用户是否存在未支付的拼团活动订单;就是还没支付的时候,重复下单了;需要校验下;不然的话,一个拼团可以下多个单子了;
+        // 2. 校验该用户是否存在未支付的拼团活动订单;就是还没支付的时候,重复下单了;需要校验下;不然的话,一个拼团可以下多个单子了;
         TradeOrderDO activityOrder = orderQueryService.getActivityOrderByUserIdAndActivityIdAndStatus(
                 order.getUserId(), order.getCombinationActivityId(), TradeOrderStatusEnum.UNPAID.getStatus());
         if (activityOrder != null) {
@@ -61,19 +62,32 @@ public class TradeCombinationHandler implements TradeOrderHandler {
     @Override
     public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
         // 1.如果不是拼团订单则结束
-        if (TradeOrderTypeEnum.isCombination(order.getType())) {
+        if (!TradeOrderTypeEnum.isCombination(order.getType())) {
             return;
         }
         Assert.isTrue(orderItems.size() == 1, "拼团时,只允许选择一个商品");
 
-        // 2.获取商品信息
+        // 2. 创建拼团记录
         TradeOrderItemDO item = orderItems.get(0);
-        // 2.1.创建拼团记录
         KeyValue<Long, Long> recordIdAndHeadId = combinationRecordApi.createCombinationRecord(
                 TradeOrderConvert.INSTANCE.convert(order, item));
-        // 3.更新拼团相关信息到订单
+
+        // 3. 更新拼团相关信息到订单
+        // TODO 芋艿,只需要更新 record
         orderUpdateService.updateOrderCombinationInfo(order.getId(), order.getCombinationActivityId(),
                 recordIdAndHeadId.getKey(), recordIdAndHeadId.getValue());
     }
 
+    @Override
+    public void beforeDeliveryOrder(TradeOrderDO order) {
+        if (!TradeOrderTypeEnum.isCombination(order.getType())) {
+            return;
+        }
+        // 校验订单拼团是否成功
+        if (!combinationRecordApi.isCombinationRecordSuccess(order.getUserId(), order.getId())) {
+            throw exception(ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS);
+        }
+    }
+
 }
+

+ 42 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCouponOrderHandler.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.trade.service.order.handler;
+
+import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
+import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 优惠劵的 {@link TradeOrderHandler} 实现类
+ *
+ * @author 芋道源码
+ */
+@Component
+public class TradeCouponOrderHandler implements TradeOrderHandler {
+
+    @Resource
+    private CouponApi couponApi;
+
+    @Override
+    public void afterOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        if (order.getCouponId() == null || order.getCouponId() <= 0) {
+            return;
+        }
+        // 不在前置扣减的原因,是因为优惠劵要记录使用的订单号
+        couponApi.useCoupon(new CouponUseReqDTO().setId(order.getCouponId()).setUserId(order.getUserId())
+                .setOrderId(order.getId()));
+    }
+
+    @Override
+    public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        if (order.getCouponId() == null || order.getCouponId() <= 0) {
+            return;
+        }
+        // 退回优惠劵
+        couponApi.returnUsedCoupon(order.getCouponId());
+    }
+
+}

+ 120 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeMemberPointOrderHandler.java

@@ -0,0 +1,120 @@
+package cn.iocoder.yudao.module.trade.service.order.handler;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
+import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
+import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
+import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
+import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getSumValue;
+
+/**
+ * 会员积分、等级的 {@link TradeOrderHandler} 实现类
+ *
+ * @author owen
+ */
+@Component
+public class TradeMemberPointOrderHandler implements TradeOrderHandler {
+
+    @Resource
+    private MemberPointApi memberPointApi;
+    @Resource
+    private MemberLevelApi memberLevelApi;
+
+    @Resource
+    private AfterSaleService afterSaleService;
+
+    @Override
+    public void afterOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        // 扣减用户积分(订单抵扣)。不在前置扣减的原因,是因为积分扣减时,需要记录关联业务
+        reducePoint(order.getUserId(), order.getUsePoint(), MemberPointBizTypeEnum.ORDER_USE, order.getId());
+    }
+
+    @Override
+    public void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        // 增加用户积分(订单赠送)
+        addPoint(order.getUserId(), order.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE,
+                order.getId());
+
+        // 增加用户经验
+        memberLevelApi.addExperience(order.getUserId(), order.getPayPrice(),
+                MemberExperienceBizTypeEnum.ORDER_GIVE.getType(), String.valueOf(order.getId()));
+    }
+
+    @Override
+    public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚
+        orderItems = filterOrderItemListByNoneAfterSale(orderItems);
+        if (CollUtil.isEmpty(orderItems)) {
+            return;
+        }
+
+        // 增加(回滚)用户积分(订单抵扣)
+        Integer usePoint = getSumValue(orderItems, TradeOrderItemDO::getUsePoint, Integer::sum);
+        addPoint(order.getUserId(), usePoint, MemberPointBizTypeEnum.ORDER_USE_CANCEL,
+                order.getId());
+
+        // 如下的返还,需要经过支持,也就是经历 afterPayOrder 流程
+        if (!order.getPayStatus()) {
+            return;
+        }
+        // 扣减(回滚)积分(订单赠送)
+        Integer givePoint = getSumValue(orderItems, TradeOrderItemDO::getGivePoint, Integer::sum);
+        reducePoint(order.getUserId(), givePoint, MemberPointBizTypeEnum.ORDER_GIVE_CANCEL,
+                order.getId());
+        // 扣减(回滚)用户经验
+        int payPrice = order.getPayPrice() - order.getRefundPrice();
+        memberLevelApi.addExperience(order.getUserId(), payPrice,
+                MemberExperienceBizTypeEnum.ORDER_GIVE_CANCEL.getType(), String.valueOf(order.getId()));
+    }
+
+    @Override
+    public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
+        // 扣减(回滚)积分(订单赠送)
+        reducePoint(order.getUserId(), orderItem.getGivePoint(), MemberPointBizTypeEnum.ORDER_GIVE_CANCEL_ITEM,
+                orderItem.getId());
+        // 增加(回滚)积分(订单抵扣)
+        addPoint(order.getUserId(), orderItem.getUsePoint(), MemberPointBizTypeEnum.ORDER_USE_CANCEL_ITEM,
+                orderItem.getId());
+
+        // 扣减(回滚)用户经验
+        AfterSaleDO afterSale = afterSaleService.getAfterSale(orderItem.getAfterSaleId());
+        memberLevelApi.reduceExperience(order.getUserId(), afterSale.getRefundPrice(),
+                MemberExperienceBizTypeEnum.ORDER_GIVE_CANCEL_ITEM.getType(), String.valueOf(orderItem.getId()));
+    }
+
+    /**
+     * 添加用户积分
+     * <p>
+     * 目前是支付成功后,就会创建积分记录。
+     * <p>
+     * 业内还有两种做法,可以根据自己的业务调整:
+     * 1. 确认收货后,才创建积分记录
+     * 2. 支付 or 下单成功时,创建积分记录(冻结),确认收货解冻或者 n 天后解冻
+     *
+     * @param userId  用户编号
+     * @param point   增加积分数量
+     * @param bizType 业务编号
+     * @param bizId   业务编号
+     */
+    protected void addPoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) {
+        if (point != null && point > 0) {
+            memberPointApi.addPoint(userId, point, bizType.getType(), String.valueOf(bizId));
+        }
+    }
+
+    protected void reducePoint(Long userId, Integer point, MemberPointBizTypeEnum bizType, Long bizId) {
+        if (point != null && point > 0) {
+            memberPointApi.reducePoint(userId, point, bizType.getType(), String.valueOf(bizId));
+        }
+    }
+
+}

+ 32 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeOrderHandler.java

@@ -1,7 +1,9 @@
 package cn.iocoder.yudao.module.trade.service.order.handler;
 
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
 
 import java.util.List;
 
@@ -35,16 +37,42 @@ public interface TradeOrderHandler {
      * @param order 订单
      * @param orderItems 订单项
      */
-    default void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-    }
+    default void afterPayOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {}
 
     /**
-     * 订单取消
+     * 订单取消
      *
      * @param order 订单
      * @param orderItems 订单项
      */
-    default void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+    default void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {}
+
+    /**
+     * 订单项取消后
+     *
+     * @param order 订单
+     * @param orderItem 订单项
+     */
+    default void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {}
+
+    /**
+     * 订单发货前
+     *
+     * @param order 订单
+     */
+    default void beforeDeliveryOrder(TradeOrderDO order) {}
+
+    // ========== 公用方法 ==========
+
+    /**
+     * 过滤“未售后”的订单项列表
+     *
+     * @param orderItems 订单项列表
+     * @return 过滤后的订单项列表
+     */
+    default List<TradeOrderItemDO> filterOrderItemListByNoneAfterSale(List<TradeOrderItemDO> orderItems) {
+        return CollectionUtils.filterList(orderItems,
+                item -> TradeOrderItemAfterSaleStatusEnum.isNone(item.getAfterSaleStatus()));
     }
 
 }

+ 46 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeProductSkuOrderHandler.java

@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.trade.service.order.handler;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
+import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
+import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static java.util.Collections.singletonList;
+
+/**
+ * 商品 SKU 库存的 {@link TradeOrderHandler} 实现类
+ *
+ * @author 芋道源码
+ */
+@Component
+public class TradeProductSkuOrderHandler implements TradeOrderHandler {
+
+    @Resource
+    private ProductSkuApi productSkuApi;
+
+    @Override
+    public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems));
+    }
+
+    @Override
+    public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚
+        orderItems = filterOrderItemListByNoneAfterSale(orderItems);
+        if (CollUtil.isEmpty(orderItems)) {
+            return;
+        }
+        productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems));
+    }
+
+    @Override
+    public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
+        productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(singletonList(orderItem)));
+    }
+
+}

+ 20 - 6
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillHandler.java → yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeSeckillOrderHandler.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.trade.service.order.handler;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Assert;
 import cn.iocoder.yudao.module.promotion.api.seckill.SeckillActivityApi;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
@@ -11,19 +12,19 @@ import javax.annotation.Resource;
 import java.util.List;
 
 /**
- * 秒杀订单 handler 实现类
+ * 秒杀订单的 {@link TradeOrderHandler} 实现类
  *
  * @author HUIHUI
  */
 @Component
-public class TradeSeckillHandler implements TradeOrderHandler {
+public class TradeSeckillOrderHandler implements TradeOrderHandler {
 
     @Resource
     private SeckillActivityApi seckillActivityApi;
 
     @Override
     public void beforeOrderCreate(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-        if (TradeOrderTypeEnum.isSeckill(order.getType())) {
+        if (!TradeOrderTypeEnum.isSeckill(order.getType())) {
             return;
         }
         // 明确校验一下
@@ -35,16 +36,29 @@ public class TradeSeckillHandler implements TradeOrderHandler {
     }
 
     @Override
-    public void cancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
-        if (TradeOrderTypeEnum.isSeckill(order.getType())) {
+    public void afterCancelOrder(TradeOrderDO order, List<TradeOrderItemDO> orderItems) {
+        if (!TradeOrderTypeEnum.isSeckill(order.getType())) {
             return;
         }
         // 明确校验一下
         Assert.isTrue(orderItems.size() == 1, "秒杀时,只允许选择一个商品");
 
+        // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚
+        orderItems = filterOrderItemListByNoneAfterSale(orderItems);
+        if (CollUtil.isEmpty(orderItems)) {
+            return;
+        }
+        afterCancelOrderItem(order, orderItems.get(0));
+    }
+
+    @Override
+    public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {
+        if (!TradeOrderTypeEnum.isSeckill(order.getType())) {
+            return;
+        }
         // 恢复秒杀活动的库存
         seckillActivityApi.updateSeckillStockIncr(order.getSeckillActivityId(),
-                orderItems.get(0).getSkuId(), orderItems.get(0).getCount());
+                orderItem.getSkuId(), orderItem.getCount());
     }
 
 }

+ 10 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApi.java

@@ -28,4 +28,14 @@ public interface MemberLevelApi {
      */
     void addExperience(Long userId, Integer experience, Integer bizType, String bizId);
 
+    /**
+     * 扣减会员经验
+     *
+     * @param userId     会员ID
+     * @param experience 经验
+     * @param bizType    业务类型 {@link MemberExperienceBizTypeEnum}
+     * @param bizId      业务编号
+     */
+    void reduceExperience(Long userId, Integer experience, Integer bizType, String bizId);
+
 }

+ 3 - 2
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/MemberExperienceBizTypeEnum.java

@@ -20,10 +20,11 @@ public enum MemberExperienceBizTypeEnum {
      */
     ADMIN(0, "管理员调整", "管理员调整获得 {} 经验", true),
     INVITE_REGISTER(1, "邀新奖励", "邀请好友获得 {} 经验", true),
-    ORDER(2, "下单奖励", "下单获得 {} 经验", true),
-    REFUND(3, "退单扣除", "退单获得 {} 经验", false),
     SIGN_IN(4, "签到奖励", "签到获得 {} 经验", true),
     LOTTERY(5, "抽奖奖励", "抽奖获得 {} 经验", true),
+    ORDER_GIVE(11, "下单奖励", "下单获得 {} 经验", true),
+    ORDER_GIVE_CANCEL(12, "下单奖励(整单取消)", "取消订单获得 {} 经验", false), // ORDER_GIVE 的取消
+    ORDER_GIVE_CANCEL_ITEM(13, "下单奖励(单个退款)", "退款订单获得 {} 经验", false), // ORDER_GIVE 的取消
     ;
 
     /**

+ 8 - 5
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java

@@ -18,11 +18,14 @@ public enum MemberPointBizTypeEnum implements IntArrayValuable {
 
     SIGN(1, "签到", "签到获得 {} 积分", true),
     ADMIN(2, "管理员修改", "管理员修改 {} 积分", true),
-    ORDER_GIVE(10, "订单奖励", "下单获得 {} 积分", true), // 支付订单时,赠送积分
-    ORDER_CANCEL(11, "订单取消", "订单取消,退还 {} 积分", true), // 取消订单时,退回积分
-    ORDER_USE(12, "订单使用", "下单使用 {} 积分", false), // 下单时,扣减积分
-    AFTER_SALE_REFUND_USED(13, "订单退款", "订单退款,退还 {} 积分", true), // 售后订单成功时,退回积分(对应 ORDER_USE 操作)
-    AFTER_SALE_DEDUCT_GIVE(14, "订单退款", "订单退款,扣除赠送的 {} 积分", false), // 售后订单成功时,扣减积分(对应 ORDER_GIVE 操作)
+
+    ORDER_USE(11, "订单积分抵扣", "下单使用 {} 积分", false), // 下单时,扣减积分
+    ORDER_USE_CANCEL(12, "订单积分抵扣(整单取消)", "订单取消,退还 {} 积分", true), // ORDER_USE 的取消
+    ORDER_USE_CANCEL_ITEM(13, "订单积分抵扣(单个退款)", "订单退款,退还 {} 积分", true), // ORDER_USE 的取消
+
+    ORDER_GIVE(21, "订单积分奖励", "下单获得 {} 积分", true), // 支付订单时,赠送积分
+    ORDER_GIVE_CANCEL(22, "订单积分奖励(整单取消)", "订单取消,退还 {} 积分", false), // ORDER_GIVE 的取消
+    ORDER_GIVE_CANCEL_ITEM(23, "订单积分奖励(单个退款)", "订单退款,扣除赠送的 {} 积分", false) // ORDER_GIVE 的取消
     ;
 
     /**

+ 5 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApiImpl.java

@@ -38,4 +38,9 @@ public class MemberLevelApiImpl implements MemberLevelApi {
         memberLevelService.addExperience(userId, experience, bizTypeEnum, bizId);
     }
 
+    @Override
+    public void reduceExperience(Long userId, Integer experience, Integer bizType, String bizId) {
+        addExperience(userId, -experience, bizType, bizId);
+    }
+
 }