You've already forked FrameTour-BE
fix(pricing): 修复优惠券计算逻辑,防止优惠金额溢出
- 重构优惠券折扣计算方法,确保固定金额优惠券不超过适用商品总价 - 修改百分比优惠券计算逻辑,基于适用商品总价而非购物车总价 - 新增适用商品总价计算方法,支持按商品类型过滤 - 添加防止优惠金额超出适用商品总价的保护逻辑 - 完善无商品类型限制时的全商品适用逻辑 - 增加多种边界情况和多SKU场景的单元测试 - 修复百分比优惠券最大折扣限制的计算顺序问题
This commit is contained in:
@@ -70,17 +70,27 @@ public class CouponServiceImpl implements ICouponService {
|
||||
if (!isCouponApplicable(coupon, products, totalAmount)) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
|
||||
// 计算适用商品的总价
|
||||
BigDecimal applicableProductsTotal = calculateApplicableProductsTotal(coupon, products);
|
||||
|
||||
BigDecimal discount;
|
||||
if (coupon.getCouponType() == CouponType.PERCENTAGE) {
|
||||
discount = totalAmount.multiply(coupon.getDiscountValue().divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP));
|
||||
// 百分比优惠券基于适用商品总价计算,而非购物车总价
|
||||
discount = applicableProductsTotal.multiply(coupon.getDiscountValue().divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP));
|
||||
if (coupon.getMaxDiscount() != null && discount.compareTo(coupon.getMaxDiscount()) > 0) {
|
||||
discount = coupon.getMaxDiscount();
|
||||
}
|
||||
} else {
|
||||
// 固定金额优惠券
|
||||
discount = coupon.getDiscountValue();
|
||||
}
|
||||
|
||||
|
||||
// 限制优惠金额不超过适用商品总价,防止优惠溢出到其他SKU
|
||||
if (discount.compareTo(applicableProductsTotal) > 0) {
|
||||
discount = applicableProductsTotal;
|
||||
}
|
||||
|
||||
return discount.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
@@ -128,7 +138,37 @@ public class CouponServiceImpl implements ICouponService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 计算适用商品的总价
|
||||
* @param coupon 优惠券配置
|
||||
* @param products 商品列表
|
||||
* @return 适用商品的总价
|
||||
*/
|
||||
private BigDecimal calculateApplicableProductsTotal(PriceCouponConfig coupon, List<ProductItem> products) {
|
||||
// 如果优惠券没有商品类型限制,返回所有商品总价
|
||||
if (coupon.getApplicableProducts() == null || coupon.getApplicableProducts().isEmpty()) {
|
||||
return products.stream()
|
||||
.map(ProductItem::getSubtotal)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
|
||||
try {
|
||||
// 解析适用商品类型列表
|
||||
List<String> applicableProductTypes = objectMapper.readValue(
|
||||
coupon.getApplicableProducts(), new TypeReference<List<String>>() {});
|
||||
|
||||
// 计算适用商品的总价
|
||||
return products.stream()
|
||||
.filter(product -> applicableProductTypes.contains(product.getProductType().getCode()))
|
||||
.map(ProductItem::getSubtotal)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
} catch (Exception e) {
|
||||
log.error("解析适用商品类型失败", e);
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CouponUseResult useCoupon(CouponUseRequest request) {
|
||||
|
||||
Reference in New Issue
Block a user