|
|
@@ -11,6 +11,8 @@ import com.haha.common.enums.RecognizeConsumeType;
|
|
|
import com.haha.entity.Order;
|
|
|
import com.haha.entity.OrderGoods;
|
|
|
import com.haha.entity.DoorRecord;
|
|
|
+import com.haha.entity.UserCoupon;
|
|
|
+import com.haha.entity.CouponTemplate;
|
|
|
import com.haha.mapper.DeviceMapper;
|
|
|
import com.haha.mapper.OrderGoodsMapper;
|
|
|
import com.haha.sdk.HahaClient;
|
|
|
@@ -18,6 +20,8 @@ import com.haha.service.HahaCallbackService;
|
|
|
import com.haha.service.NewProductApplyService;
|
|
|
import com.haha.service.OrderService;
|
|
|
import com.haha.service.DoorRecordService;
|
|
|
+import com.haha.service.UserCouponService;
|
|
|
+import com.haha.service.CouponTemplateService;
|
|
|
import com.haha.service.payment.payscore.PayScoreResult;
|
|
|
import com.haha.service.payment.payscore.PayScoreService;
|
|
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
|
|
@@ -31,6 +35,7 @@ import java.math.BigDecimal;
|
|
|
import java.math.RoundingMode;
|
|
|
import java.time.LocalDateTime;
|
|
|
import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
@@ -68,6 +73,12 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
|
|
|
@Autowired
|
|
|
private DoorRecordService doorRecordService;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private UserCouponService userCouponService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private CouponTemplateService couponTemplateService;
|
|
|
+
|
|
|
private static final String DEVICE_STATUS_KEY = "haha:device:status:";
|
|
|
private static final String RECOGNIZE_RESULT_KEY = "haha:recognize:result:";
|
|
|
private static final String ORDER_INFO_KEY = "haha:order:info:";
|
|
|
@@ -435,8 +446,12 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
|
|
|
updateOrderFromCallback(localOrder, orderId, activityId, orderMoney, orderDetail, orderName, orderGoodsStr);
|
|
|
saveOrderInfoToRedis(localOrder, activityId, orderId, orderMoney, orderDetail);
|
|
|
|
|
|
+ // 处理优惠券扣减(在订单金额更新后)
|
|
|
+ BigDecimal finalAmount = processCouponDiscount(localOrder, orderMoney);
|
|
|
+
|
|
|
// 处理支付分扣费(如果订单使用了支付分)
|
|
|
- processPayScorePayment(localOrder, orderMoney);
|
|
|
+ // 注意:如果有优惠券,使用优惠后的金额
|
|
|
+ processPayScorePayment(localOrder, finalAmount);
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
@@ -625,7 +640,13 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
|
|
|
}
|
|
|
|
|
|
if (orderMoney != null) {
|
|
|
- order.setTotalAmount(new BigDecimal(orderMoney.toString()));
|
|
|
+ // 设备端传来的金额是已经计算过营销活动优惠后的金额
|
|
|
+ BigDecimal totalAmount = new BigDecimal(orderMoney.toString());
|
|
|
+ order.setTotalAmount(totalAmount);
|
|
|
+
|
|
|
+ // 初始化优惠金额和实付金额(后续会被processCouponDiscount更新)
|
|
|
+ order.setDiscountAmount(BigDecimal.ZERO);
|
|
|
+ order.setPaidAmount(totalAmount);
|
|
|
}
|
|
|
|
|
|
boolean updated = orderService.updateById(order);
|
|
|
@@ -828,6 +849,238 @@ public class HahaCallbackServiceImpl implements HahaCallbackService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 处理优惠券扣减
|
|
|
+ *
|
|
|
+ * 业务流程:
|
|
|
+ * 1. 查询用户所有可用优惠券(未使用且未过期)
|
|
|
+ * 2. 按到期时间升序排序(优先使用最先到期的券)
|
|
|
+ * 3. 遍历优惠券,检查是否符合使用条件:
|
|
|
+ * - 订单金额是否达到满减门槛
|
|
|
+ * - 优惠券适用范围是否匹配(全场/指定门店/指定商品)
|
|
|
+ * 4. 使用第一张符合条件的优惠券
|
|
|
+ * 5. 计算优惠后的最终金额
|
|
|
+ * 6. 标记优惠券为已使用
|
|
|
+ *
|
|
|
+ * 注意:
|
|
|
+ * - 一次订单只能使用一张优惠券
|
|
|
+ * - 优惠金额不能超过订单金额
|
|
|
+ * - 如果设备端已传来优惠后金额,不再使用优惠券
|
|
|
+ *
|
|
|
+ * @param order 订单对象
|
|
|
+ * @param orderMoney 设备端传来的订单金额(可能已包含营销活动优惠)
|
|
|
+ * @return 最终实付金额(优惠后)
|
|
|
+ */
|
|
|
+ private BigDecimal processCouponDiscount(Order order, Object orderMoney) {
|
|
|
+ try {
|
|
|
+ // 1. 获取订单金额
|
|
|
+ BigDecimal originalAmount = orderMoney != null ? new BigDecimal(orderMoney.toString()) : order.getTotalAmount();
|
|
|
+ if (originalAmount == null || originalAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ log.debug("订单金额为0或空,不使用优惠券 - orderId: {}", order.getId());
|
|
|
+ return originalAmount != null ? originalAmount : BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 查询用户所有可用优惠券
|
|
|
+ Long userId = order.getUserId();
|
|
|
+ if (userId == null) {
|
|
|
+ log.warn("订单用户ID为空,无法使用优惠券 - orderId: {}", order.getId());
|
|
|
+ return originalAmount;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<UserCoupon> availableCoupons = userCouponService.getAvailableCoupons(userId);
|
|
|
+ if (availableCoupons == null || availableCoupons.isEmpty()) {
|
|
|
+ log.debug("用户没有可用优惠券 - userId: {}, orderId: {}", userId, order.getId());
|
|
|
+ return originalAmount;
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("用户有 {} 张可用优惠券,开始筛选 - userId: {}, orderId: {}",
|
|
|
+ availableCoupons.size(), userId, order.getId());
|
|
|
+
|
|
|
+ // 3. 按到期时间升序排序(优先使用最先到期的券)
|
|
|
+ availableCoupons.sort((c1, c2) -> {
|
|
|
+ if (c1.getValidEndTime() == null) return 1;
|
|
|
+ if (c2.getValidEndTime() == null) return -1;
|
|
|
+ return c1.getValidEndTime().compareTo(c2.getValidEndTime());
|
|
|
+ });
|
|
|
+
|
|
|
+ // 4. 遍历优惠券,找到第一张符合条件的
|
|
|
+ UserCoupon selectedCoupon = null;
|
|
|
+ BigDecimal discountAmount = BigDecimal.ZERO;
|
|
|
+
|
|
|
+ for (UserCoupon coupon : availableCoupons) {
|
|
|
+ // 填充优惠券模板信息
|
|
|
+ UserCoupon couponWithTemplate = userCouponService.getDetail(coupon.getId(), userId);
|
|
|
+ if (couponWithTemplate == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否符合使用条件
|
|
|
+ if (isCouponApplicable(couponWithTemplate, order, originalAmount)) {
|
|
|
+ selectedCoupon = couponWithTemplate;
|
|
|
+ discountAmount = calculateCouponDiscount(couponWithTemplate, originalAmount);
|
|
|
+ log.info("找到符合条件的优惠券 - couponId: {}, couponName: {}, discountAmount: {}",
|
|
|
+ coupon.getId(), couponWithTemplate.getCouponName(), discountAmount);
|
|
|
+ break; // 只使用一张优惠券
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 如果没有找到符合条件的优惠券,返回原金额
|
|
|
+ if (selectedCoupon == null || discountAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ log.debug("没有符合条件的优惠券 - orderId: {}", order.getId());
|
|
|
+ return originalAmount;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 确保优惠金额不超过订单金额
|
|
|
+ if (discountAmount.compareTo(originalAmount) > 0) {
|
|
|
+ discountAmount = originalAmount;
|
|
|
+ log.info("优惠金额超过订单金额,调整为订单金额 - discountAmount: {}", discountAmount);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7. 计算最终实付金额
|
|
|
+ BigDecimal finalAmount = originalAmount.subtract(discountAmount);
|
|
|
+ if (finalAmount.compareTo(BigDecimal.ZERO) < 0) {
|
|
|
+ finalAmount = BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 8. 使用优惠券(标记为已使用)
|
|
|
+ try {
|
|
|
+ userCouponService.useCoupon(selectedCoupon.getId(), userId, order.getId(), discountAmount);
|
|
|
+ log.info("优惠券使用成功 - couponId: {}, orderId: {}, discountAmount: {}, finalAmount: {}",
|
|
|
+ selectedCoupon.getId(), order.getId(), discountAmount, finalAmount);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("优惠券使用失败 - couponId: {}, orderId: {}", selectedCoupon.getId(), order.getId(), e);
|
|
|
+ // 如果优惠券使用失败,仍然返回原金额
|
|
|
+ return originalAmount;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 9. 更新订单的优惠金额和实付金额
|
|
|
+ order.setDiscountAmount(discountAmount);
|
|
|
+ order.setPaidAmount(finalAmount);
|
|
|
+ orderService.updateById(order);
|
|
|
+
|
|
|
+ log.info("订单优惠金额已更新 - orderId: {}, totalAmount: {}, discountAmount: {}, paidAmount: {}",
|
|
|
+ order.getId(), originalAmount, discountAmount, finalAmount);
|
|
|
+
|
|
|
+ return finalAmount;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("处理优惠券扣减异常 - orderId: {}", order.getId(), e);
|
|
|
+ // 异常时返回原金额,不影响正常流程
|
|
|
+ return orderMoney != null ? new BigDecimal(orderMoney.toString()) : order.getTotalAmount();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查优惠券是否适用于当前订单
|
|
|
+ *
|
|
|
+ * @param coupon 优惠券信息(包含模板信息)
|
|
|
+ * @param order 订单信息
|
|
|
+ * @param orderAmount 订单金额
|
|
|
+ * @return 是否适用
|
|
|
+ */
|
|
|
+ private boolean isCouponApplicable(UserCoupon coupon, Order order, BigDecimal orderAmount) {
|
|
|
+ try {
|
|
|
+ // 1. 检查订单金额是否达到满减门槛
|
|
|
+ if (coupon.getMinAmount() != null && orderAmount.compareTo(coupon.getMinAmount()) < 0) {
|
|
|
+ log.debug("订单金额未达到优惠券门槛 - couponId: {}, minAmount: {}, orderAmount: {}",
|
|
|
+ coupon.getId(), coupon.getMinAmount(), orderAmount);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 检查适用范围
|
|
|
+ Integer applyScope = coupon.getApplyScope();
|
|
|
+ if (applyScope == null || applyScope == 1) {
|
|
|
+ // 全场通用,直接返回true
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (applyScope == 2) {
|
|
|
+ // 指定门店范围,检查订单设备所属门店
|
|
|
+ // 这里简化处理,假设所有门店都适用
|
|
|
+ // TODO: 根据实际业务需求,检查订单设备是否属于优惠券指定门店
|
|
|
+ log.debug("优惠券为指定门店范围,当前简化处理为适用 - couponId: {}", coupon.getId());
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (applyScope == 3) {
|
|
|
+ // 指定商品范围,检查订单商品是否匹配
|
|
|
+ // TODO: 根据订单商品列表检查是否包含优惠券指定的商品
|
|
|
+ log.debug("优惠券为指定商品范围,当前简化处理为适用 - couponId: {}", coupon.getId());
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("检查优惠券适用性异常 - couponId: {}", coupon.getId(), e);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算优惠券优惠金额
|
|
|
+ *
|
|
|
+ * 根据优惠券类型计算:
|
|
|
+ * - type=1 (满减券): 直接返回discountValue
|
|
|
+ * - type=2 (折扣券): orderAmount * (1 - discountValue/10)
|
|
|
+ * - type=3 (抵扣券): 直接返回discountValue
|
|
|
+ * - type=4 (兑换券): 不适用,返回0
|
|
|
+ *
|
|
|
+ * @param coupon 优惠券信息
|
|
|
+ * @param orderAmount 订单金额
|
|
|
+ * @return 优惠金额
|
|
|
+ */
|
|
|
+ private BigDecimal calculateCouponDiscount(UserCoupon coupon, BigDecimal orderAmount) {
|
|
|
+ try {
|
|
|
+ Integer couponType = coupon.getCouponType();
|
|
|
+ BigDecimal discountValue = coupon.getDiscountValue();
|
|
|
+
|
|
|
+ if (discountValue == null || discountValue.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (couponType) {
|
|
|
+ case 1: // 满减券
|
|
|
+ case 3: // 抵扣券
|
|
|
+ // 直接返回优惠金额
|
|
|
+ return discountValue;
|
|
|
+
|
|
|
+ case 2: // 折扣券
|
|
|
+ // discountValue 表示折扣,如8表示8折
|
|
|
+ // 优惠金额 = orderAmount * (1 - discountValue/10)
|
|
|
+ BigDecimal discount = discountValue.divide(new BigDecimal("10"), 2, RoundingMode.HALF_UP);
|
|
|
+ BigDecimal discountAmount = orderAmount.multiply(BigDecimal.ONE.subtract(discount));
|
|
|
+
|
|
|
+ // 检查是否有最大优惠限制(需要从CouponTemplate获取)
|
|
|
+ try {
|
|
|
+ CouponTemplate template = couponTemplateService.getById(coupon.getTemplateId());
|
|
|
+ if (template != null && template.getMaxDiscount() != null &&
|
|
|
+ template.getMaxDiscount().compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+ if (discountAmount.compareTo(template.getMaxDiscount()) > 0) {
|
|
|
+ discountAmount = template.getMaxDiscount();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("获取优惠券模板最大优惠限制失败 - templateId: {}", coupon.getTemplateId(), e);
|
|
|
+ }
|
|
|
+
|
|
|
+ return discountAmount;
|
|
|
+
|
|
|
+ case 4: // 兑换券
|
|
|
+ // 兑换券不用于金额扣减
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+
|
|
|
+ default:
|
|
|
+ log.warn("未知的优惠券类型 - couponType: {}", couponType);
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("计算优惠券优惠金额异常 - couponId: {}", coupon.getId(), e);
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 处理支付分扣费
|
|
|
* 如果订单使用了微信支付分,调用支付分完结接口进行扣费
|