Merge branch 'develop' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-jdk17
# Conflicts: # yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
This commit is contained in:
@@ -62,3 +62,8 @@ tenant-id: {{appTenentId}}
|
||||
GET {{appApi}}/trade/order/get-express-track-list?id=70
|
||||
Authorization: Bearer {{appToken}}
|
||||
tenant-id: {{appTenentId}}
|
||||
|
||||
### /trade-order/settlement-product 获得商品结算信息
|
||||
GET {{appApi}}/trade/order/settlement-product?spuIds=633
|
||||
Authorization: Bearer {{appToken}}
|
||||
tenant-id: {{appTenentId}}
|
||||
@@ -17,9 +17,11 @@ import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
|
||||
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
|
||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
|
||||
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
|
||||
import cn.iocoder.yudao.module.trade.service.price.TradePriceService;
|
||||
import com.google.common.collect.Maps;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
@@ -47,9 +49,10 @@ public class AppTradeOrderController {
|
||||
private TradeOrderQueryService tradeOrderQueryService;
|
||||
@Resource
|
||||
private DeliveryExpressService deliveryExpressService;
|
||||
|
||||
@Resource
|
||||
private AfterSaleService afterSaleService;
|
||||
@Resource
|
||||
private TradePriceService priceService;
|
||||
|
||||
@Resource
|
||||
private TradeOrderProperties tradeOrderProperties;
|
||||
@@ -61,6 +64,13 @@ public class AppTradeOrderController {
|
||||
return success(tradeOrderUpdateService.settlementOrder(getLoginUserId(), settlementReqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/settlement-product")
|
||||
@Operation(summary = "获得商品结算信息", description = "用于商品列表、商品详情,获得参与活动后的价格信息")
|
||||
@Parameter(name = "spuIds", description = "商品 SPU 编号数组")
|
||||
public CommonResult<List<AppTradeProductSettlementRespVO>> settlementProduct(@RequestParam("spuIds") List<Long> spuIds) {
|
||||
return success(priceService.calculateProductPrice(getLoginUserId(), spuIds));
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建订单")
|
||||
@PreAuthenticated
|
||||
@@ -79,21 +89,32 @@ public class AppTradeOrderController {
|
||||
|
||||
@GetMapping("/get-detail")
|
||||
@Operation(summary = "获得交易订单")
|
||||
@Parameter(name = "id", description = "交易订单编号")
|
||||
@Parameters({
|
||||
@Parameter(name = "id", description = "交易订单编号"),
|
||||
@Parameter(name = "sync", description = "是否同步支付状态", example = "true")
|
||||
})
|
||||
@PreAuthenticated
|
||||
public CommonResult<AppTradeOrderDetailRespVO> getOrder(@RequestParam("id") Long id) {
|
||||
// 查询订单
|
||||
public CommonResult<AppTradeOrderDetailRespVO> getOrderDetail(@RequestParam("id") Long id,
|
||||
@RequestParam(value = "sync", required = false) Boolean sync) {
|
||||
// 1.1 查询订单
|
||||
TradeOrderDO order = tradeOrderQueryService.getOrder(getLoginUserId(), id);
|
||||
if (order == null) {
|
||||
return success(null);
|
||||
}
|
||||
// 1.2 sync 仅在等待支付
|
||||
if (Boolean.TRUE.equals(sync)
|
||||
&& TradeOrderStatusEnum.isUnpaid(order.getStatus()) && !order.getPayStatus()) {
|
||||
tradeOrderUpdateService.syncOrderPayStatusQuietly(order.getId(), order.getPayOrderId());
|
||||
// 重新查询,因为同步后,可能会有变化
|
||||
order = tradeOrderQueryService.getOrder(id);
|
||||
}
|
||||
|
||||
// 查询订单项
|
||||
// 2.1 查询订单项
|
||||
List<TradeOrderItemDO> orderItems = tradeOrderQueryService.getOrderItemListByOrderId(order.getId());
|
||||
// 查询物流公司
|
||||
// 2.2 查询物流公司
|
||||
DeliveryExpressDO express = order.getLogisticsId() != null && order.getLogisticsId() > 0 ?
|
||||
deliveryExpressService.getDeliveryExpress(order.getLogisticsId()) : null;
|
||||
// 最终组合
|
||||
// 2.3 最终组合
|
||||
return success(TradeOrderConvert.INSTANCE.convert02(order, orderItems, tradeOrderProperties, express));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
|
||||
|
||||
import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -34,6 +35,13 @@ public class AppTradeOrderSettlementRespVO {
|
||||
@Schema(description = "总积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private Integer totalPoint;
|
||||
|
||||
/**
|
||||
* 营销活动数组
|
||||
*
|
||||
* 只对应 {@link TradePriceCalculateRespBO.Price#items} 商品匹配的活动
|
||||
*/
|
||||
private List<TradePriceCalculateRespBO.Promotion> promotions;
|
||||
|
||||
@Schema(description = "购物项")
|
||||
@Data
|
||||
public static class Item {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package cn.iocoder.yudao.module.trade.controller.app.order.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Schema(description = "用户 App - 商品结算信息 Response VO")
|
||||
@Data
|
||||
public class AppTradeProductSettlementRespVO {
|
||||
|
||||
@Schema(description = "SPU 商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long spuId;
|
||||
|
||||
@Schema(description = "SKU 价格信息数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private List<Sku> skus;
|
||||
|
||||
@Schema(description = "满减送活动信息", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private RewardActivity rewardActivity;
|
||||
|
||||
@Schema(description = "SKU 价格信息")
|
||||
@Data
|
||||
public static class Sku implements Serializable {
|
||||
|
||||
@Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "优惠后价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
|
||||
private Integer promotionPrice;
|
||||
|
||||
@Schema(description = "营销类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "4")
|
||||
private Integer promotionType; // 对应 PromotionTypeEnum 枚举,目前只有 4 和 6 两种
|
||||
|
||||
@Schema(description = "营销编号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Long promotionId; // 目前只有限时折扣活动的编号
|
||||
|
||||
@Schema(description = "活动结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime promotionEndTime;
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "满减送活动信息")
|
||||
@Data
|
||||
public static class RewardActivity {
|
||||
|
||||
@Schema(description = "满减活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "条件类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer conditionType;
|
||||
|
||||
@Schema(description = "优惠规则的数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private List<RewardActivityRule> rules;
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "优惠规则")
|
||||
@Data
|
||||
public static class RewardActivityRule {
|
||||
|
||||
@Schema(description = "优惠门槛", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") // 1. 满 N 元,单位:分; 2. 满 N 件
|
||||
private Integer limit;
|
||||
|
||||
@Schema(description = "优惠价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
|
||||
private Integer discountPrice;
|
||||
|
||||
@Schema(description = "是否包邮", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
private Boolean freeDelivery;
|
||||
|
||||
@Schema(description = "赠送的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
|
||||
private Integer point;
|
||||
|
||||
@Schema(description = "赠送的优惠劵编号的数组")
|
||||
private Map<Long, Integer> giveCouponTemplateCounts;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -46,8 +46,7 @@ public interface AfterSaleConvert {
|
||||
@Mapping(source = "afterSale.refundPrice", target = "price"),
|
||||
@Mapping(source = "orderProperties.payAppKey", target = "appKey")
|
||||
})
|
||||
PayRefundCreateReqDTO convert(String userIp, AfterSaleDO afterSale,
|
||||
TradeOrderProperties orderProperties);
|
||||
PayRefundCreateReqDTO convert(String userIp, AfterSaleDO afterSale, TradeOrderProperties orderProperties);
|
||||
|
||||
MemberUserRespVO convert(MemberUserRespDTO bean);
|
||||
|
||||
|
||||
@@ -386,7 +386,7 @@ public class AfterSaleServiceImpl implements AfterSaleService {
|
||||
public void afterCommit() {
|
||||
// 创建退款单
|
||||
PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties)
|
||||
.setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));
|
||||
.setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));;
|
||||
Long payRefundId = payRefundApi.createRefund(createReqDTO);
|
||||
// 更新售后单的退款单号
|
||||
tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));
|
||||
|
||||
@@ -49,6 +49,17 @@ public interface TradeOrderUpdateService {
|
||||
*/
|
||||
void updateOrderPaid(Long id, Long payOrderId);
|
||||
|
||||
/**
|
||||
* 同步订单的支付状态
|
||||
*
|
||||
* 1. Quietly 表示,即使同步失败,也不会抛出异常
|
||||
* 2. 什么时候回出现异常?因为是主动同步,可能和支付模块的回调通知 {@link #updateOrderPaid(Long, Long)} 存在并发冲突,导致抛出异常
|
||||
*
|
||||
* @param id 订单编号
|
||||
* @param payOrderId 支付订单编号
|
||||
*/
|
||||
void syncOrderPayStatusQuietly(Long id, Long payOrderId);
|
||||
|
||||
/**
|
||||
* 【管理员】发货交易订单
|
||||
*
|
||||
|
||||
@@ -9,7 +9,6 @@ import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
|
||||
@@ -166,7 +165,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
TradePriceCalculateReqBO calculateReqBO = TradeOrderConvert.INSTANCE.convert(userId, settlementReqVO, cartList);
|
||||
calculateReqBO.getItems().forEach(item -> Assert.isTrue(item.getSelected(), // 防御性编程,保证都是选中的
|
||||
"商品({}) 未设置为选中", item.getSkuId()));
|
||||
return tradePriceService.calculatePrice(calculateReqBO);
|
||||
return tradePriceService.calculateOrderPrice(calculateReqBO);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -269,12 +268,24 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_PAY)
|
||||
public void updateOrderPaid(Long id, Long payOrderId) {
|
||||
// 1. 校验并获得交易订单(可支付)
|
||||
KeyValue<TradeOrderDO, PayOrderRespDTO> orderResult = validateOrderPayable(id, payOrderId);
|
||||
TradeOrderDO order = orderResult.getKey();
|
||||
PayOrderRespDTO payOrder = orderResult.getValue();
|
||||
// 1.1 校验订单是否存在
|
||||
TradeOrderDO order = validateOrderExists(id);
|
||||
// 1.2 校验订单已支付
|
||||
if (!TradeOrderStatusEnum.isUnpaid(order.getStatus()) || order.getPayStatus()) {
|
||||
// 特殊:如果订单已支付,且支付单号相同,直接返回,说明重复回调
|
||||
if (ObjectUtil.equals(order.getPayOrderId(), payOrderId)) {
|
||||
log.warn("[updateOrderPaid][order({}) 已支付,且支付单号相同({}),直接返回]", order, payOrderId);
|
||||
return;
|
||||
}
|
||||
log.error("[updateOrderPaid][order({}) 支付单不匹配({}),请进行处理!order 数据是:{}]",
|
||||
id, payOrderId, JsonUtils.toJsonString(order));
|
||||
throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
|
||||
}
|
||||
|
||||
// 2. 更新 TradeOrderDO 状态为已支付,等待发货
|
||||
// 2. 校验支付订单的合法性
|
||||
PayOrderRespDTO payOrder = validatePayOrderPaid(order, payOrderId);
|
||||
|
||||
// 3. 更新 TradeOrderDO 状态为已支付,等待发货
|
||||
int updateCount = tradeOrderMapper.updateByIdAndStatus(id, order.getStatus(),
|
||||
new TradeOrderDO().setStatus(TradeOrderStatusEnum.UNDELIVERED.getStatus()).setPayStatus(true)
|
||||
.setPayTime(LocalDateTime.now()).setPayChannelCode(payOrder.getChannelCode()));
|
||||
@@ -282,66 +293,65 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
||||
throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
|
||||
}
|
||||
|
||||
// 3. 执行 TradeOrderHandler 的后置处理
|
||||
// 4. 执行 TradeOrderHandler 的后置处理
|
||||
List<TradeOrderItemDO> orderItems = tradeOrderItemMapper.selectListByOrderId(id);
|
||||
tradeOrderHandlers.forEach(handler -> handler.afterPayOrder(order, orderItems));
|
||||
|
||||
// 4. 记录订单日志
|
||||
// 5. 记录订单日志
|
||||
TradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.UNDELIVERED.getStatus());
|
||||
TradeOrderLogUtils.setUserInfo(order.getUserId(), UserTypeEnum.MEMBER.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验交易订单满足被支付的条件
|
||||
* <p>
|
||||
* 1. 交易订单未支付
|
||||
* 2. 支付单已支付
|
||||
*
|
||||
* @param id 交易订单编号
|
||||
* @param payOrderId 支付订单编号
|
||||
* @return 交易订单
|
||||
*/
|
||||
private KeyValue<TradeOrderDO, PayOrderRespDTO> validateOrderPayable(Long id, Long payOrderId) {
|
||||
// 校验订单是否存在
|
||||
TradeOrderDO order = validateOrderExists(id);
|
||||
// 校验订单未支付
|
||||
if (!TradeOrderStatusEnum.isUnpaid(order.getStatus()) || order.getPayStatus()) {
|
||||
log.error("[validateOrderPaid][order({}) 不处于待支付状态,请进行处理!order 数据是:{}]",
|
||||
id, JsonUtils.toJsonString(order));
|
||||
throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
|
||||
}
|
||||
// 校验支付订单匹配
|
||||
if (ObjectUtil.notEqual(order.getPayOrderId(), payOrderId)) { // 支付单号
|
||||
log.error("[validateOrderPaid][order({}) 支付单不匹配({}),请进行处理!order 数据是:{}]",
|
||||
id, payOrderId, JsonUtils.toJsonString(order));
|
||||
throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
|
||||
}
|
||||
|
||||
// 校验支付单是否存在
|
||||
@Override
|
||||
public void syncOrderPayStatusQuietly(Long id, Long payOrderId) {
|
||||
PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId);
|
||||
if (payOrder == null) {
|
||||
log.error("[validateOrderPaid][order({}) payOrder({}) 不存在,请进行处理!]", id, payOrderId);
|
||||
return;
|
||||
}
|
||||
if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
getSelf().updateOrderPaid(id, payOrderId);
|
||||
} catch (Throwable e) {
|
||||
log.warn("[syncOrderPayStatusQuietly][id({}) payOrderId({}) 同步支付状态失败]", id, payOrderId, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验支付订单的合法性
|
||||
*
|
||||
* @param order 交易订单
|
||||
* @param payOrderId 支付订单编号
|
||||
* @return 支付订单
|
||||
*/
|
||||
private PayOrderRespDTO validatePayOrderPaid(TradeOrderDO order, Long payOrderId) {
|
||||
// 1. 校验支付单是否存在
|
||||
PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId);
|
||||
if (payOrder == null) {
|
||||
log.error("[validatePayOrderPaid][order({}) payOrder({}) 不存在,请进行处理!]", order.getId(), payOrderId);
|
||||
throw exception(ORDER_NOT_FOUND);
|
||||
}
|
||||
// 校验支付单已支付
|
||||
|
||||
// 2.1 校验支付单已支付
|
||||
if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
|
||||
log.error("[validateOrderPaid][order({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]",
|
||||
id, payOrderId, JsonUtils.toJsonString(payOrder));
|
||||
log.error("[validatePayOrderPaid][order({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]",
|
||||
order.getId(), payOrderId, JsonUtils.toJsonString(payOrder));
|
||||
throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS);
|
||||
}
|
||||
// 校验支付金额一致
|
||||
// 2.2 校验支付金额一致
|
||||
if (ObjectUtil.notEqual(payOrder.getPrice(), order.getPayPrice())) {
|
||||
log.error("[validateOrderPaid][order({}) payOrder({}) 支付金额不匹配,请进行处理!order 数据是:{},payOrder 数据是:{}]",
|
||||
id, payOrderId, JsonUtils.toJsonString(order), JsonUtils.toJsonString(payOrder));
|
||||
log.error("[validatePayOrderPaid][order({}) payOrder({}) 支付金额不匹配,请进行处理!order 数据是:{},payOrder 数据是:{}]",
|
||||
order.getId(), payOrderId, JsonUtils.toJsonString(order), JsonUtils.toJsonString(payOrder));
|
||||
throw exception(ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH);
|
||||
}
|
||||
// 校验支付订单匹配(二次)
|
||||
if (ObjectUtil.notEqual(payOrder.getMerchantOrderId(), id.toString())) {
|
||||
log.error("[validateOrderPaid][order({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]",
|
||||
id, payOrderId, JsonUtils.toJsonString(payOrder));
|
||||
// 2.2 校验支付订单匹配(二次)
|
||||
if (ObjectUtil.notEqual(payOrder.getMerchantOrderId(), order.getId().toString())) {
|
||||
log.error("[validatePayOrderPaid][order({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]",
|
||||
order.getId(), payOrderId, JsonUtils.toJsonString(payOrder));
|
||||
throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
|
||||
}
|
||||
return new KeyValue<>(order, payOrder);
|
||||
return payOrder;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price;
|
||||
|
||||
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeProductSettlementRespVO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 价格计算 Service 接口
|
||||
*
|
||||
@@ -13,11 +15,20 @@ import jakarta.validation.Valid;
|
||||
public interface TradePriceService {
|
||||
|
||||
/**
|
||||
* 价格计算
|
||||
* 【订单】价格计算
|
||||
*
|
||||
* @param calculateReqDTO 计算信息
|
||||
* @return 计算结果
|
||||
*/
|
||||
TradePriceCalculateRespBO calculatePrice(@Valid TradePriceCalculateReqBO calculateReqDTO);
|
||||
TradePriceCalculateRespBO calculateOrderPrice(@Valid TradePriceCalculateReqBO calculateReqDTO);
|
||||
|
||||
/**
|
||||
* 【商品】价格计算,用于商品列表、商品详情
|
||||
*
|
||||
* @param userId 用户编号,允许为空
|
||||
* @param spuIds 商品 SPU 编号数组
|
||||
* @return 计算结果
|
||||
*/
|
||||
List<AppTradeProductSettlementRespVO> calculateProductPrice(Long userId, List<Long> spuIds);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
|
||||
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.promotion.api.discount.DiscountActivityApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeProductSettlementRespVO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.calculator.TradeDiscountActivityPriceCalculator;
|
||||
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator;
|
||||
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
|
||||
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
|
||||
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
|
||||
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;
|
||||
@@ -37,12 +46,19 @@ public class TradePriceServiceImpl implements TradePriceService {
|
||||
private ProductSkuApi productSkuApi;
|
||||
@Resource
|
||||
private ProductSpuApi productSpuApi;
|
||||
@Resource
|
||||
private DiscountActivityApi discountActivityApi;
|
||||
@Resource
|
||||
private RewardActivityApi rewardActivityApi;
|
||||
|
||||
@Resource
|
||||
private List<TradePriceCalculator> priceCalculators;
|
||||
|
||||
@Resource
|
||||
private TradeDiscountActivityPriceCalculator discountActivityPriceCalculator;
|
||||
|
||||
@Override
|
||||
public TradePriceCalculateRespBO calculatePrice(TradePriceCalculateReqBO calculateReqBO) {
|
||||
public TradePriceCalculateRespBO calculateOrderPrice(TradePriceCalculateReqBO calculateReqBO) {
|
||||
// 1.1 获得商品 SKU 数组
|
||||
List<ProductSkuRespDTO> skuList = checkSkuList(calculateReqBO);
|
||||
// 1.2 获得商品 SPU 数组
|
||||
@@ -85,4 +101,55 @@ public class TradePriceServiceImpl implements TradePriceService {
|
||||
return productSpuApi.validateSpuList(convertSet(skuList, ProductSkuRespDTO::getSpuId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AppTradeProductSettlementRespVO> calculateProductPrice(Long userId, List<Long> spuIds) {
|
||||
// 1.1 获得 SPU 与 SKU 的映射
|
||||
List<ProductSkuRespDTO> allSkuList = productSkuApi.getSkuListBySpuId(spuIds);
|
||||
Map<Long, List<ProductSkuRespDTO>> spuIdAndSkuListMap = convertMultiMap(allSkuList, ProductSkuRespDTO::getSpuId);
|
||||
// 1.2 获得会员等级
|
||||
MemberLevelRespDTO level = discountActivityPriceCalculator.getMemberLevel(userId);
|
||||
// 1.3 获得限时折扣活动
|
||||
Map<Long, DiscountProductRespDTO> skuIdAndDiscountMap = convertMap(
|
||||
discountActivityApi.getMatchDiscountProductListBySkuIds(convertSet(allSkuList, ProductSkuRespDTO::getId)),
|
||||
DiscountProductRespDTO::getSkuId);
|
||||
// 1.4 获得满减送活动
|
||||
List<RewardActivityMatchRespDTO> rewardActivityMap = rewardActivityApi.getMatchRewardActivityListBySpuIds(spuIds);
|
||||
|
||||
// 2. 价格计算
|
||||
return convertList(spuIds, spuId -> {
|
||||
AppTradeProductSettlementRespVO spuVO = new AppTradeProductSettlementRespVO().setSpuId(spuId);
|
||||
// 2.1 优惠价格
|
||||
List<ProductSkuRespDTO> skuList = spuIdAndSkuListMap.get(spuId);
|
||||
List<AppTradeProductSettlementRespVO.Sku> skuVOList = convertList(skuList, sku -> {
|
||||
AppTradeProductSettlementRespVO.Sku skuVO = new AppTradeProductSettlementRespVO.Sku()
|
||||
.setId(sku.getId()).setPromotionPrice(sku.getPrice());
|
||||
TradePriceCalculateRespBO.OrderItem orderItem = new TradePriceCalculateRespBO.OrderItem()
|
||||
.setPayPrice(sku.getPrice()).setCount(1);
|
||||
// 计算限时折扣的优惠价格
|
||||
DiscountProductRespDTO discountProduct = skuIdAndDiscountMap.get(sku.getId());
|
||||
Integer discountPrice = discountActivityPriceCalculator.calculateActivityPrice(discountProduct, orderItem);
|
||||
// 计算 VIP 优惠金额
|
||||
Integer vipPrice = discountActivityPriceCalculator.calculateVipPrice(level, orderItem);
|
||||
if (discountPrice <= 0 && vipPrice <= 0) {
|
||||
return skuVO;
|
||||
}
|
||||
// 选择一个大的优惠
|
||||
if (discountPrice > vipPrice) {
|
||||
return skuVO.setPromotionPrice(sku.getPrice() - discountPrice)
|
||||
.setPromotionType(PromotionTypeEnum.DISCOUNT_ACTIVITY.getType())
|
||||
.setPromotionId(discountProduct.getId()).setPromotionEndTime(discountProduct.getActivityEndTime());
|
||||
} else {
|
||||
return skuVO.setPromotionPrice(sku.getPrice() - vipPrice)
|
||||
.setPromotionType(PromotionTypeEnum.MEMBER_LEVEL.getType());
|
||||
}
|
||||
});
|
||||
spuVO.setSkus(skuVOList);
|
||||
// 2.2 满减送活动
|
||||
RewardActivityMatchRespDTO rewardActivity = CollUtil.findOne(rewardActivityMap,
|
||||
activity -> CollUtil.contains(activity.getSpuIds(), spuId));
|
||||
spuVO.setRewardActivity(BeanUtils.toBean(rewardActivity, AppTradeProductSettlementRespVO.RewardActivity.class));
|
||||
return spuVO;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -122,9 +122,9 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
|
||||
*/
|
||||
private boolean isGlobalExpressFree(TradePriceCalculateRespBO result) {
|
||||
TradeConfigDO config = tradeConfigService.getTradeConfig();
|
||||
return config != null
|
||||
&& Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮
|
||||
&& result.getPrice().getPayPrice() >= config.getDeliveryExpressFreePrice(); // 满足包邮的价格
|
||||
return config == null
|
||||
|| Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮
|
||||
|| result.getPrice().getPayPrice() >= config.getDeliveryExpressFreePrice(); // 满足包邮的价格
|
||||
}
|
||||
|
||||
private void calculateDeliveryPrice(List<OrderItem> selectedSkus,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
|
||||
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.api.discount.DiscountActivityApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
|
||||
@@ -10,20 +13,23 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.framework.common.util.number.MoneyUtils.calculateRatePrice;
|
||||
import static cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper.formatPrice;
|
||||
|
||||
/**
|
||||
* 限时折扣的 {@link TradePriceCalculator} 实现类
|
||||
*
|
||||
* 由于“会员折扣”和“限时折扣”是冲突,需要选择优惠金额多的,所以也放在这里计算
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@@ -32,6 +38,10 @@ public class TradeDiscountActivityPriceCalculator implements TradePriceCalculato
|
||||
|
||||
@Resource
|
||||
private DiscountActivityApi discountActivityApi;
|
||||
@Resource
|
||||
private MemberLevelApi memberLevelApi;
|
||||
@Resource
|
||||
private MemberUserApi memberUserApi;
|
||||
|
||||
@Override
|
||||
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
||||
@@ -39,51 +49,103 @@ public class TradeDiscountActivityPriceCalculator implements TradePriceCalculato
|
||||
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
|
||||
return;
|
||||
}
|
||||
// 获得 SKU 对应的限时折扣活动
|
||||
List<DiscountProductRespDTO> discountProducts = discountActivityApi.getMatchDiscountProductList(
|
||||
convertSet(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSkuId));
|
||||
if (CollUtil.isEmpty(discountProducts)) {
|
||||
return;
|
||||
}
|
||||
Map<Long, DiscountProductRespDTO> discountProductMap = convertMap(discountProducts, DiscountProductRespDTO::getSkuId);
|
||||
|
||||
// 处理每个 SKU 的限时折扣
|
||||
// 1.1 获得 SKU 对应的限时折扣活动
|
||||
List<DiscountProductRespDTO> discountProducts = discountActivityApi.getMatchDiscountProductListBySkuIds(
|
||||
convertSet(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSkuId));
|
||||
Map<Long, DiscountProductRespDTO> discountProductMap = convertMap(discountProducts, DiscountProductRespDTO::getSkuId);
|
||||
// 1.2 获得会员等级
|
||||
MemberLevelRespDTO level = getMemberLevel(param.getUserId());
|
||||
|
||||
// 2. 计算每个 SKU 的优惠金额
|
||||
result.getItems().forEach(orderItem -> {
|
||||
// 1. 获取该 SKU 的优惠信息
|
||||
DiscountProductRespDTO discountProduct = discountProductMap.get(orderItem.getSkuId());
|
||||
if (discountProduct == null) {
|
||||
if (!orderItem.getSelected()) {
|
||||
return;
|
||||
}
|
||||
// 2.1 计算限时折扣的优惠金额
|
||||
DiscountProductRespDTO discountProduct = discountProductMap.get(orderItem.getSkuId());
|
||||
Integer discountPrice = calculateActivityPrice(discountProduct, orderItem);
|
||||
// 2.2 计算 VIP 优惠金额
|
||||
Integer vipPrice = calculateVipPrice(level, orderItem);
|
||||
if (discountPrice <= 0 && vipPrice <= 0) {
|
||||
return;
|
||||
}
|
||||
// 2. 计算优惠金额
|
||||
Integer newPayPrice = calculatePayPrice(discountProduct, orderItem);
|
||||
Integer newDiscountPrice = orderItem.getPayPrice() - newPayPrice;
|
||||
|
||||
// 3.1 记录优惠明细
|
||||
if (orderItem.getSelected()) {
|
||||
// 注意,只有在选中的情况下,才会记录到优惠明细。否则仅仅是更新 SKU 优惠金额,用于展示
|
||||
// 3. 选择优惠金额多的
|
||||
if (discountPrice > vipPrice) {
|
||||
TradePriceCalculatorHelper.addPromotion(result, orderItem,
|
||||
discountProduct.getActivityId(), discountProduct.getActivityName(), PromotionTypeEnum.DISCOUNT_ACTIVITY.getType(),
|
||||
StrUtil.format("限时折扣:省 {} 元", formatPrice(newDiscountPrice)),
|
||||
newDiscountPrice);
|
||||
StrUtil.format("限时折扣:省 {} 元", formatPrice(discountPrice)),
|
||||
discountPrice);
|
||||
// 更新 SKU 优惠金额
|
||||
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + discountPrice);
|
||||
} else {
|
||||
assert level != null;
|
||||
TradePriceCalculatorHelper.addPromotion(result, orderItem,
|
||||
level.getId(), level.getName(), PromotionTypeEnum.MEMBER_LEVEL.getType(),
|
||||
String.format("会员等级折扣:省 %s 元", formatPrice(vipPrice)),
|
||||
vipPrice);
|
||||
// 更新 SKU 的优惠金额
|
||||
orderItem.setVipPrice(vipPrice);
|
||||
}
|
||||
// 3.2 更新 SKU 优惠金额
|
||||
orderItem.setDiscountPrice(orderItem.getDiscountPrice() + newDiscountPrice);
|
||||
|
||||
// 4. 分摊优惠
|
||||
TradePriceCalculatorHelper.recountPayPrice(orderItem);
|
||||
TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
});
|
||||
TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
}
|
||||
|
||||
private Integer calculatePayPrice(DiscountProductRespDTO discountProduct,
|
||||
TradePriceCalculateRespBO.OrderItem orderItem) {
|
||||
Integer price = orderItem.getPayPrice();
|
||||
if (PromotionDiscountTypeEnum.PRICE.getType().equals(discountProduct.getDiscountType())) { // 减价
|
||||
price -= discountProduct.getDiscountPrice() * orderItem.getCount();
|
||||
} else if (PromotionDiscountTypeEnum.PERCENT.getType().equals(discountProduct.getDiscountType())) { // 打折
|
||||
price = price * discountProduct.getDiscountPercent() / 100;
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format("优惠活动的商品(%s) 的优惠类型不正确", discountProduct));
|
||||
/**
|
||||
* 获得用户的等级
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @return 用户等级
|
||||
*/
|
||||
public MemberLevelRespDTO getMemberLevel(Long userId) {
|
||||
MemberUserRespDTO user = memberUserApi.getUser(userId);
|
||||
if (user == null || user.getLevelId() == null || user.getLevelId() <= 0) {
|
||||
return null;
|
||||
}
|
||||
return price;
|
||||
return memberLevelApi.getMemberLevel(user.getLevelId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算优惠活动的价格
|
||||
*
|
||||
* @param discount 优惠活动
|
||||
* @param orderItem 交易项
|
||||
* @return 优惠价格
|
||||
*/
|
||||
public Integer calculateActivityPrice(DiscountProductRespDTO discount,
|
||||
TradePriceCalculateRespBO.OrderItem orderItem) {
|
||||
if (discount == null) {
|
||||
return 0;
|
||||
}
|
||||
Integer newPrice = orderItem.getPayPrice();
|
||||
if (PromotionDiscountTypeEnum.PRICE.getType().equals(discount.getDiscountType())) { // 减价
|
||||
newPrice -= discount.getDiscountPrice() * orderItem.getCount();
|
||||
} else if (PromotionDiscountTypeEnum.PERCENT.getType().equals(discount.getDiscountType())) { // 打折
|
||||
newPrice = calculateRatePrice(orderItem.getPayPrice(), discount.getDiscountPercent() / 100.0);
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format("优惠活动的商品(%s) 的优惠类型不正确", discount));
|
||||
}
|
||||
return orderItem.getPayPrice() - newPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算会员 VIP 的优惠价格
|
||||
*
|
||||
* @param level 会员等级
|
||||
* @param orderItem 交易项
|
||||
* @return 优惠价格
|
||||
*/
|
||||
public Integer calculateVipPrice(MemberLevelRespDTO level,
|
||||
TradePriceCalculateRespBO.OrderItem orderItem) {
|
||||
if (level == null || level.getDiscountPercent() == null) {
|
||||
return 0;
|
||||
}
|
||||
Integer newPrice = calculateRatePrice(orderItem.getPayPrice(), level.getDiscountPercent().doubleValue());
|
||||
return orderItem.getPayPrice() - newPrice;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.module.member.api.level.MemberLevelApi;
|
||||
import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper.formatPrice;
|
||||
|
||||
/**
|
||||
* 会员 VIP 折扣的 {@link TradePriceCalculator} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Order(TradePriceCalculator.ORDER_MEMBER_LEVEL)
|
||||
public class TradeMemberLevelPriceCalculator implements TradePriceCalculator {
|
||||
|
||||
@Resource
|
||||
private MemberLevelApi memberLevelApi;
|
||||
@Resource
|
||||
private MemberUserApi memberUserApi;
|
||||
|
||||
@Override
|
||||
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
|
||||
// 0. 只有【普通】订单,才计算该优惠
|
||||
if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
|
||||
return;
|
||||
}
|
||||
// 1. 获得用户的会员等级
|
||||
MemberUserRespDTO user = memberUserApi.getUser(param.getUserId());
|
||||
if (user.getLevelId() == null || user.getLevelId() <= 0) {
|
||||
return;
|
||||
}
|
||||
MemberLevelRespDTO level = memberLevelApi.getMemberLevel(user.getLevelId());
|
||||
if (level == null || level.getDiscountPercent() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 计算每个 SKU 的优惠金额
|
||||
result.getItems().forEach(orderItem -> {
|
||||
// 2.1 计算优惠金额
|
||||
Integer vipPrice = calculateVipPrice(orderItem.getPayPrice(), level.getDiscountPercent());
|
||||
if (vipPrice <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2.2 记录优惠明细
|
||||
if (orderItem.getSelected()) {
|
||||
// 注意,只有在选中的情况下,才会记录到优惠明细。否则仅仅是更新 SKU 优惠金额,用于展示
|
||||
TradePriceCalculatorHelper.addPromotion(result, orderItem,
|
||||
level.getId(), level.getName(), PromotionTypeEnum.MEMBER_LEVEL.getType(),
|
||||
String.format("会员等级折扣:省 %s 元", formatPrice(vipPrice)),
|
||||
vipPrice);
|
||||
}
|
||||
|
||||
// 2.3 更新 SKU 的优惠金额
|
||||
orderItem.setVipPrice(vipPrice);
|
||||
TradePriceCalculatorHelper.recountPayPrice(orderItem);
|
||||
});
|
||||
TradePriceCalculatorHelper.recountAllPrice(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算会员 VIP 优惠价格
|
||||
*
|
||||
* @param price 原价
|
||||
* @param discountPercent 折扣
|
||||
* @return 优惠价格
|
||||
*/
|
||||
public Integer calculateVipPrice(Integer price, Integer discountPercent) {
|
||||
if (discountPercent == null) {
|
||||
return 0;
|
||||
}
|
||||
Integer newPrice = price * discountPercent / 100;
|
||||
return price - newPrice;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,8 +13,6 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
|
||||
*/
|
||||
public interface TradePriceCalculator {
|
||||
|
||||
int ORDER_MEMBER_LEVEL = 5;
|
||||
|
||||
int ORDER_SECKILL_ACTIVITY = 8;
|
||||
int ORDER_BARGAIN_ACTIVITY = 8;
|
||||
int ORDER_COMBINATION_ACTIVITY = 8;
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
package cn.iocoder.yudao.module.trade.service.price.calculator;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.RewardActivityApi;
|
||||
import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionConditionTypeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
|
||||
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
|
||||
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
|
||||
@@ -17,8 +14,6 @@ import jakarta.annotation.Resource;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -47,14 +42,15 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
|
||||
return;
|
||||
}
|
||||
// 获得 SKU 对应的满减送活动
|
||||
List<RewardActivityMatchRespDTO> rewardActivities = rewardActivityApi.getMatchRewardActivityList(
|
||||
List<RewardActivityMatchRespDTO> rewardActivities = rewardActivityApi.getMatchRewardActivityListBySpuIds(
|
||||
convertSet(result.getItems(), TradePriceCalculateRespBO.OrderItem::getSpuId));
|
||||
if (CollUtil.isEmpty(rewardActivities)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理每个满减送活动
|
||||
rewardActivities.forEach(rewardActivity -> calculate(param, result, rewardActivity));
|
||||
// 处理最新的满减送活动
|
||||
if (!rewardActivities.isEmpty()) {
|
||||
calculate(param, result, rewardActivities.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
private void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result,
|
||||
@@ -69,7 +65,7 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
|
||||
if (rule == null) {
|
||||
TradePriceCalculatorHelper.addNotMatchPromotion(result, orderItems,
|
||||
rewardActivity.getId(), rewardActivity.getName(), PromotionTypeEnum.REWARD_ACTIVITY.getType(),
|
||||
getRewardActivityNotMeetTip(rewardActivity, orderItems));
|
||||
"满减送:" + rewardActivity.getRules().get(0).getDescription());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -77,6 +73,10 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
|
||||
Integer newDiscountPrice = rule.getDiscountPrice();
|
||||
// 2.2 计算分摊的优惠金额
|
||||
List<Integer> divideDiscountPrices = TradePriceCalculatorHelper.dividePrice(orderItems, newDiscountPrice);
|
||||
// 2.3 计算是否包邮
|
||||
if (Boolean.TRUE.equals(rule.getFreeDelivery())) {
|
||||
result.setFreeDelivery(true);
|
||||
}
|
||||
|
||||
// 3.1 记录使用的优惠劵
|
||||
result.setCouponId(param.getCouponId());
|
||||
@@ -110,16 +110,8 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
|
||||
// 4.3 记录赠送的优惠券
|
||||
if (CollUtil.isNotEmpty(rule.getGiveCouponTemplateCounts())) {
|
||||
for (Map.Entry<Long, Integer> entry : rule.getGiveCouponTemplateCounts().entrySet()) {
|
||||
Map<Long, Integer> giveCouponTemplateCounts = result.getGiveCouponTemplateCounts();
|
||||
// TODO @puhui999:是不是有一种可能性,这个 key 没有,别的 key 有哈。
|
||||
// TODO 这里还有一种简化的写法。就是下面,大概两行就可以啦
|
||||
// result.getGiveCouponTemplateCounts().put(entry.getKey(),
|
||||
// result.getGiveCouponTemplateCounts().getOrDefault(entry.getKey(), 0) + entry.getValue());
|
||||
if (giveCouponTemplateCounts.get(entry.getKey()) == null) { // 情况一:还没有赠送的优惠券
|
||||
result.setGiveCouponTemplateCounts(rule.getGiveCouponTemplateCounts());
|
||||
} else { // 情况二:别的满减活动送过同类优惠券,则直接增加数量
|
||||
giveCouponTemplateCounts.put(entry.getKey(), giveCouponTemplateCounts.get(entry.getKey()) + entry.getValue());
|
||||
}
|
||||
result.getGiveCouponTemplateCounts().put(entry.getKey(),
|
||||
result.getGiveCouponTemplateCounts().getOrDefault(entry.getKey(), 0) + entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,28 +125,14 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
|
||||
*/
|
||||
private List<TradePriceCalculateRespBO.OrderItem> filterMatchActivityOrderItems(TradePriceCalculateRespBO result,
|
||||
RewardActivityMatchRespDTO rewardActivity) {
|
||||
// 情况一:全部商品都可以参与
|
||||
if (PromotionProductScopeEnum.isAll(rewardActivity.getProductScope())) {
|
||||
return result.getItems();
|
||||
}
|
||||
// 情况二:指定商品参与
|
||||
if (PromotionProductScopeEnum.isSpu(rewardActivity.getProductScope())) {
|
||||
return filterList(result.getItems(),
|
||||
orderItem -> CollUtil.contains(rewardActivity.getProductScopeValues(), orderItem.getSpuId()));
|
||||
}
|
||||
// 情况三:指定商品类型参与
|
||||
if (PromotionProductScopeEnum.isCategory(rewardActivity.getProductScope())) {
|
||||
return filterList(result.getItems(),
|
||||
orderItem -> CollUtil.contains(rewardActivity.getProductScopeValues(), orderItem.getCategoryId()));
|
||||
}
|
||||
return ListUtil.of();
|
||||
return filterList(result.getItems(), orderItem -> CollUtil.contains(rewardActivity.getSpuIds(), orderItem.getSpuId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得最大匹配的满减送活动的规则
|
||||
*
|
||||
* @param rewardActivity 满减送活动
|
||||
* @param orderItems 商品项
|
||||
* @param orderItems 商品项
|
||||
* @return 匹配的活动规则
|
||||
*/
|
||||
private RewardActivityMatchRespDTO.Rule getMaxMatchRewardActivityRule(RewardActivityMatchRespDTO rewardActivity,
|
||||
@@ -179,31 +157,4 @@ public class TradeRewardActivityPriceCalculator implements TradePriceCalculator
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得满减送活动不匹配时的提示
|
||||
*
|
||||
* @param rewardActivity 满减送活动
|
||||
* @return 提示
|
||||
*/
|
||||
private String getRewardActivityNotMeetTip(RewardActivityMatchRespDTO rewardActivity,
|
||||
List<TradePriceCalculateRespBO.OrderItem> orderItems) {
|
||||
// 1. 计算数量和价格
|
||||
Integer count = TradePriceCalculatorHelper.calculateTotalCount(orderItems);
|
||||
Integer price = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
|
||||
assert count != null && price != null;
|
||||
|
||||
// 2. 构建不满足时的提示信息:按最低档规则算
|
||||
String meetTip = "满减送:购满 {} {},可以减 {} 元";
|
||||
List<RewardActivityMatchRespDTO.Rule> rules = new ArrayList<>(rewardActivity.getRules());
|
||||
rules.sort(Comparator.comparing(RewardActivityMatchRespDTO.Rule::getLimit)); // 按优惠门槛升序
|
||||
RewardActivityMatchRespDTO.Rule rule = rules.get(0);
|
||||
if (PromotionConditionTypeEnum.PRICE.getType().equals(rewardActivity.getConditionType())) {
|
||||
return StrUtil.format(meetTip, rule.getLimit(), "元", MoneyUtils.fenToYuanStr(rule.getDiscountPrice()));
|
||||
}
|
||||
if (PromotionConditionTypeEnum.COUNT.getType().equals(rewardActivity.getConditionType())) {
|
||||
return StrUtil.format(meetTip, rule.getLimit(), "件", MoneyUtils.fenToYuanStr(rule.getDiscountPrice()));
|
||||
}
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user