|
|
@@ -43,44 +43,6 @@ import java.util.stream.Collectors;
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOrder> implements StoreOrderService {
|
|
|
|
|
|
- /**
|
|
|
- * 金额验证误差阈值(0.01元)
|
|
|
- */
|
|
|
- private static final BigDecimal AMOUNT_TOLERANCE = new BigDecimal("0.01");
|
|
|
-
|
|
|
- /**
|
|
|
- * 订单金额信息封装类
|
|
|
- */
|
|
|
- private static class OrderAmountInfo {
|
|
|
- private final BigDecimal totalAmount; // 订单总金额(菜品总价)
|
|
|
- private final BigDecimal tablewareFee; // 餐具费
|
|
|
- private final BigDecimal discountAmount; // 优惠金额
|
|
|
- private final BigDecimal payAmount; // 实付金额
|
|
|
-
|
|
|
- public OrderAmountInfo(BigDecimal totalAmount, BigDecimal tablewareFee,
|
|
|
- BigDecimal discountAmount, BigDecimal payAmount) {
|
|
|
- this.totalAmount = totalAmount;
|
|
|
- this.tablewareFee = tablewareFee;
|
|
|
- this.discountAmount = discountAmount;
|
|
|
- this.payAmount = payAmount;
|
|
|
- }
|
|
|
-
|
|
|
- public BigDecimal getTotalAmount() {
|
|
|
- return totalAmount;
|
|
|
- }
|
|
|
-
|
|
|
- public BigDecimal getTablewareFee() {
|
|
|
- return tablewareFee;
|
|
|
- }
|
|
|
-
|
|
|
- public BigDecimal getDiscountAmount() {
|
|
|
- return discountAmount;
|
|
|
- }
|
|
|
-
|
|
|
- public BigDecimal getPayAmount() {
|
|
|
- return payAmount;
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
private final StoreOrderDetailMapper orderDetailMapper;
|
|
|
private final StoreTableMapper storeTableMapper;
|
|
|
@@ -155,25 +117,17 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
|
|
|
throw new RuntimeException("购物车中没有新增商品或商品数量未增加,无法创建订单");
|
|
|
}
|
|
|
|
|
|
- // 验证并纠正前端传入的金额(订单总金额、餐具费、优惠金额、实付金额)
|
|
|
- OrderAmountInfo amountInfo = validateAndCorrectOrderAmounts(dto, cart, storeInfo, table);
|
|
|
-
|
|
|
- // 使用验证后的金额
|
|
|
- BigDecimal totalAmount = amountInfo.getTotalAmount();
|
|
|
- BigDecimal tablewareFee = amountInfo.getTablewareFee();
|
|
|
- BigDecimal discountAmount = amountInfo.getDiscountAmount();
|
|
|
- BigDecimal payAmount = amountInfo.getPayAmount();
|
|
|
+ // 直接使用前端传入的值
|
|
|
+ BigDecimal totalAmount = dto.getTotalAmount() != null ? dto.getTotalAmount() : BigDecimal.ZERO;
|
|
|
+ BigDecimal tablewareFee = dto.getTablewareFee() != null ? dto.getTablewareFee() : BigDecimal.ZERO;
|
|
|
+ BigDecimal discountAmount = dto.getDiscountAmount() != null ? dto.getDiscountAmount() : BigDecimal.ZERO;
|
|
|
+ BigDecimal payAmount = dto.getPayAmount() != null ? dto.getPayAmount() : BigDecimal.ZERO;
|
|
|
|
|
|
// 验证优惠券(可选,couponId 可以为 null)
|
|
|
- // 注意:订单总金额、优惠金额、实付金额都由前端计算并传入,后端验证其正确性
|
|
|
- LifeDiscountCoupon coupon = null; // 缓存优惠券对象,避免重复查询
|
|
|
+ // 注意:订单总金额、优惠金额、实付金额都由前端计算并传入,后端不再验证
|
|
|
+ LifeDiscountCoupon coupon = null;
|
|
|
|
|
|
if (dto.getCouponId() != null) {
|
|
|
- // 如果使用了优惠券,必须传入优惠金额
|
|
|
- if (discountAmount == null || discountAmount.compareTo(BigDecimal.ZERO) < 0) {
|
|
|
- throw new RuntimeException("使用优惠券时必须传入优惠金额");
|
|
|
- }
|
|
|
-
|
|
|
// 检查桌号是否已使用优惠券,如果已使用则替换为新优惠券
|
|
|
if (cartService.hasUsedCoupon(dto.getTableId())) {
|
|
|
// 获取旧的优惠券使用记录
|
|
|
@@ -217,45 +171,13 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
|
|
|
throw new RuntimeException("优惠券不属于该门店");
|
|
|
}
|
|
|
|
|
|
- // 验证前端计算的优惠金额是否合理(防止前端计算错误)
|
|
|
- BigDecimal totalWithTableware = totalAmount.add(tablewareFee);
|
|
|
- BigDecimal expectedDiscountAmount = calculateDiscountAmount(coupon, totalWithTableware);
|
|
|
-
|
|
|
- // 保存前端传入的实付金额(优先使用前端传入的值)
|
|
|
- BigDecimal frontendPayAmount = payAmount;
|
|
|
-
|
|
|
- // 验证并纠正优惠金额
|
|
|
- discountAmount = validateAndCorrectAmount(
|
|
|
- discountAmount,
|
|
|
- expectedDiscountAmount,
|
|
|
- "优惠金额"
|
|
|
- );
|
|
|
-
|
|
|
- // 如果优惠金额被纠正了,重新计算实付金额用于验证
|
|
|
- // 但始终优先使用前端传入的实付金额
|
|
|
- BigDecimal recalculatedPayAmount = totalAmount.add(tablewareFee).subtract(discountAmount);
|
|
|
- if (frontendPayAmount != null) {
|
|
|
- // 如果前端传入了实付金额,验证其是否正确(仅用于记录日志)
|
|
|
- BigDecimal payAmountDifference = frontendPayAmount.subtract(recalculatedPayAmount).abs();
|
|
|
- if (payAmountDifference.compareTo(AMOUNT_TOLERANCE) > 0) {
|
|
|
- log.warn("优惠金额纠正后,前端传入的实付金额与重新计算的不一致, frontend={}, recalculated={}, difference={},将使用前端传入的值",
|
|
|
- frontendPayAmount, recalculatedPayAmount, payAmountDifference);
|
|
|
- }
|
|
|
- // 始终使用前端传入的值
|
|
|
- payAmount = frontendPayAmount;
|
|
|
- } else {
|
|
|
- // 如果前端没有传入实付金额,使用重新计算的值
|
|
|
- payAmount = recalculatedPayAmount;
|
|
|
- }
|
|
|
-
|
|
|
// 标记桌号已使用新优惠券
|
|
|
cartService.markCouponUsed(dto.getTableId(), dto.getCouponId());
|
|
|
} else {
|
|
|
- // 如果没有使用优惠券,优惠金额应该为0
|
|
|
- if (discountAmount != null && discountAmount.compareTo(BigDecimal.ZERO) != 0) {
|
|
|
- throw new RuntimeException("未使用优惠券时,优惠金额必须为0");
|
|
|
+ // 直接使用前端传入的值,如果为null则设为0
|
|
|
+ if (discountAmount == null) {
|
|
|
+ discountAmount = BigDecimal.ZERO;
|
|
|
}
|
|
|
- discountAmount = BigDecimal.ZERO;
|
|
|
}
|
|
|
|
|
|
Date now = new Date();
|
|
|
@@ -1860,40 +1782,6 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
|
|
|
return "ORD" + timestamp + String.format("%04d", Integer.parseInt(random));
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 计算餐具费(从门店信息获取)
|
|
|
- *
|
|
|
- * @param storeInfo 门店信息
|
|
|
- * @param dinerCount 就餐人数
|
|
|
- * @return 餐具费总额
|
|
|
- */
|
|
|
- private BigDecimal calculateTablewareFee(StoreInfo storeInfo, Integer dinerCount) {
|
|
|
- if (storeInfo == null) {
|
|
|
- log.warn("门店信息为空,返回默认餐具费 0.00");
|
|
|
- return BigDecimal.ZERO;
|
|
|
- }
|
|
|
-
|
|
|
- Integer tablewareFee = storeInfo.getTablewareFee();
|
|
|
- if (tablewareFee == null || tablewareFee < 0) {
|
|
|
- log.warn("门店餐具费未设置或无效, storeId={}, tablewareFee={},返回默认餐具费 0.00",
|
|
|
- storeInfo.getId(), tablewareFee);
|
|
|
- return BigDecimal.ZERO;
|
|
|
- }
|
|
|
-
|
|
|
- if (dinerCount == null || dinerCount <= 0) {
|
|
|
- log.warn("就餐人数无效, dinerCount={},返回默认餐具费 0.00", dinerCount);
|
|
|
- return BigDecimal.ZERO;
|
|
|
- }
|
|
|
-
|
|
|
- // tablewareFee 是单价(元),乘以就餐人数得到总费用
|
|
|
- BigDecimal unitPrice = BigDecimal.valueOf(tablewareFee);
|
|
|
- BigDecimal totalFee = unitPrice.multiply(BigDecimal.valueOf(dinerCount));
|
|
|
-
|
|
|
- log.info("计算餐具费, storeId={}, unitPrice={}, dinerCount={}, totalFee={}",
|
|
|
- storeInfo.getId(), unitPrice, dinerCount, totalFee);
|
|
|
-
|
|
|
- return totalFee;
|
|
|
- }
|
|
|
|
|
|
@Override
|
|
|
public void migrateTableData(Integer fromTableId, Integer toTableId, Integer userId) {
|
|
|
@@ -2111,94 +1999,4 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
|
|
|
return discountAmount;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 验证并纠正前端传入的订单金额
|
|
|
- * 包括:订单总金额、餐具费、优惠金额、实付金额
|
|
|
- *
|
|
|
- * @param dto 创建订单DTO
|
|
|
- * @param cart 购物车信息
|
|
|
- * @param storeInfo 门店信息
|
|
|
- * @param table 餐桌信息
|
|
|
- * @return 验证并纠正后的订单金额信息
|
|
|
- */
|
|
|
- private OrderAmountInfo validateAndCorrectOrderAmounts(
|
|
|
- CreateOrderDTO dto,
|
|
|
- CartDTO cart,
|
|
|
- StoreInfo storeInfo,
|
|
|
- StoreTable table) {
|
|
|
-
|
|
|
- // 1. 获取前端传入的金额(如果为null则使用默认值)
|
|
|
- BigDecimal frontendTotalAmount = dto.getTotalAmount() != null
|
|
|
- ? dto.getTotalAmount()
|
|
|
- : cart.getTotalAmount();
|
|
|
- BigDecimal frontendTablewareFee = dto.getTablewareFee() != null
|
|
|
- ? dto.getTablewareFee()
|
|
|
- : BigDecimal.ZERO;
|
|
|
- BigDecimal frontendDiscountAmount = dto.getDiscountAmount() != null
|
|
|
- ? dto.getDiscountAmount()
|
|
|
- : BigDecimal.ZERO;
|
|
|
- BigDecimal frontendPayAmount = dto.getPayAmount();
|
|
|
-
|
|
|
- // 2. 计算后端验证值
|
|
|
- BigDecimal backendTotalAmount = cart.getTotalAmount();
|
|
|
- BigDecimal backendTablewareFee = calculateTablewareFee(storeInfo, dto.getDinerCount());
|
|
|
-
|
|
|
- // 3. 验证并纠正订单总金额
|
|
|
- BigDecimal totalAmount = validateAndCorrectAmount(
|
|
|
- frontendTotalAmount,
|
|
|
- backendTotalAmount,
|
|
|
- "订单总金额"
|
|
|
- );
|
|
|
-
|
|
|
- // 4. 验证并纠正餐具费
|
|
|
- BigDecimal tablewareFee = validateAndCorrectAmount(
|
|
|
- frontendTablewareFee,
|
|
|
- backendTablewareFee,
|
|
|
- "餐具费"
|
|
|
- );
|
|
|
-
|
|
|
- // 5. 计算期望的实付金额(订单总金额 + 餐具费 - 优惠金额)
|
|
|
- BigDecimal expectedPayAmount = totalAmount.add(tablewareFee).subtract(frontendDiscountAmount);
|
|
|
-
|
|
|
- // 6. 验证并纠正实付金额
|
|
|
- BigDecimal payAmount = frontendPayAmount != null
|
|
|
- ? validateAndCorrectAmount(frontendPayAmount, expectedPayAmount, "实付金额")
|
|
|
- : expectedPayAmount;
|
|
|
-
|
|
|
- // 7. 返回验证后的金额信息
|
|
|
- return new OrderAmountInfo(totalAmount, tablewareFee, frontendDiscountAmount, payAmount);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 验证并纠正金额
|
|
|
- * 如果前端金额与后端计算金额的误差超过阈值,则使用后端计算的金额
|
|
|
- *
|
|
|
- * @param frontendAmount 前端传入的金额
|
|
|
- * @param backendAmount 后端计算的金额
|
|
|
- * @param amountName 金额名称(用于日志)
|
|
|
- * @return 验证并纠正后的金额
|
|
|
- */
|
|
|
- private BigDecimal validateAndCorrectAmount(
|
|
|
- BigDecimal frontendAmount,
|
|
|
- BigDecimal backendAmount,
|
|
|
- String amountName) {
|
|
|
-
|
|
|
- if (frontendAmount == null || backendAmount == null) {
|
|
|
- log.warn("{}验证失败:前端金额或后端金额为null, frontend={}, backend={}",
|
|
|
- amountName, frontendAmount, backendAmount);
|
|
|
- return backendAmount != null ? backendAmount : BigDecimal.ZERO;
|
|
|
- }
|
|
|
-
|
|
|
- BigDecimal difference = frontendAmount.subtract(backendAmount).abs();
|
|
|
-
|
|
|
- if (difference.compareTo(AMOUNT_TOLERANCE) > 0) {
|
|
|
- log.warn("前端计算的{}与后端计算不一致, frontend={}, backend={}, difference={}",
|
|
|
- amountName, frontendAmount, backendAmount, difference);
|
|
|
- // 使用后端计算的金额,确保数据准确性
|
|
|
- return backendAmount;
|
|
|
- }
|
|
|
-
|
|
|
- // 误差在允许范围内,使用前端金额
|
|
|
- return frontendAmount;
|
|
|
- }
|
|
|
}
|