瀏覽代碼

fix: 优惠核销加状态机——券限有效未使用状态、条件更新防并发一券多用、满减封顶服务费防实付为负;移除两处吞异常try/catch使异常传播回滚,补权益缺失防护

skyline 3 周之前
父節點
當前提交
00642e7f36

+ 85 - 52
service/src/main/java/com/kym/service/miniapp/impl/CouponDiscountHandle.java

@@ -34,70 +34,103 @@ public class CouponDiscountHandle implements DiscountService {
 
 
     @Override
-    @Transactional
+    @Transactional(rollbackFor = Exception.class)
     public void computeDiscount(ChargeOrder chargeOrder, Account account) {
-        int discountAmount = 0;
+        // 获取订单优惠券
+        var orderCoupon = orderCouponService.lambdaQuery()
+                .eq(OrderCoupon::getStartChargeSeq, chargeOrder.getStartChargeSeq())
+                .eq(OrderCoupon::getUserId, chargeOrder.getUserId())
+                .one();
+        if (orderCoupon == null) {
+            log.error("订单{}未绑定优惠券,跳过优惠处理", chargeOrder.getStartChargeSeq());
+            return;
+        }
 
-        try {
-            // 获取订单优惠券
-            var orderCoupon = orderCouponService.lambdaQuery()
+        // 查询用户优惠券信息(仅有效且未使用的券,防止一券多用)
+        var userCoupon = userCouponService.lambdaQuery()
+                .eq(UserCoupon::getId, orderCoupon.getUserCouponId())
+                .eq(UserCoupon::getUserId, chargeOrder.getUserId())
+                .eq(UserCoupon::getStatus, UserCoupon.STATUS_有效)
+                .eq(UserCoupon::getUsageStatus, UserCoupon.USAGE_STATUS_未使用)
+                .one();
+        if (userCoupon == null) {
+            // 券不存在或已使用/失效,标记订单优惠券使用失败
+            orderCouponService.lambdaUpdate()
+                    .set(OrderCoupon::getStatus, OrderCoupon.STATUS_使用失败)
                     .eq(OrderCoupon::getStartChargeSeq, chargeOrder.getStartChargeSeq())
-                    .eq(OrderCoupon::getUserId, chargeOrder.getUserId())
-                    .one();
-
-            // 查询用户优惠券信息
-            var userCoupon = userCouponService.lambdaQuery()
-                    .eq(UserCoupon::getId, orderCoupon.getUserCouponId())
-                    .eq(UserCoupon::getUserId, chargeOrder.getUserId())
-                    .one();
-
-            // 检查是否达到使用门槛
-            if (chargeOrder.getServiceMoney() >= userCoupon.getMinServiceMoney()) {
-                // 根据优惠券类型处理
-                switch (userCoupon.getCouponType()) {
-                    case Coupon.COUPON_TYPE_满减券:
-                        discountAmount = userCoupon.getDiscount();
-                        break;
-                    case Coupon.COUPON_TYPE_折扣券:
-                        discountAmount = chargeOrder.getServiceMoney() * (100 - userCoupon.getDiscount()) / 100;
-                        break;
-                    default:
-                        break;
-                }
+                    .eq(OrderCoupon::getUserCouponId, orderCoupon.getUserCouponId())
+                    .update();
+            log.error("订单使用优惠券失败:订单号:{},用户优惠券{}不存在或已使用/失效", chargeOrder.getStartChargeSeq(), orderCoupon.getUserCouponId());
+            return;
+        }
 
-                // 订单金额、优惠金额更新
-                chargeOrder.setDiscountAmount(discountAmount);
-                chargeOrder.setServiceMoneyDiscount(discountAmount);
-                chargeOrder.setPayAmount(chargeOrder.getTotalMoney() - discountAmount);
+        // 检查是否达到使用门槛
+        if (chargeOrder.getServiceMoney() >= userCoupon.getMinServiceMoney()) {
+            // 根据优惠券类型处理
+            int discountAmount;
+            switch (userCoupon.getCouponType()) {
+                case Coupon.COUPON_TYPE_满减券:
+                    // 满减金额不能超过服务费金额,防止payAmount为负
+                    discountAmount = Math.min(userCoupon.getDiscount(), chargeOrder.getServiceMoney());
+                    break;
+                case Coupon.COUPON_TYPE_折扣券:
+                    discountAmount = Math.min(chargeOrder.getServiceMoney() * (100 - userCoupon.getDiscount()) / 100, chargeOrder.getServiceMoney());
+                    break;
+                default:
+                    // 未知的优惠券类型,标记使用失败
+                    orderCouponService.lambdaUpdate()
+                            .set(OrderCoupon::getStatus, OrderCoupon.STATUS_使用失败)
+                            .eq(OrderCoupon::getStartChargeSeq, chargeOrder.getStartChargeSeq())
+                            .eq(OrderCoupon::getUserCouponId, userCoupon.getId())
+                            .update();
+                    log.error("订单使用优惠券失败:订单号:{},优惠券id{}:未知优惠券类型{}", chargeOrder.getStartChargeSeq(), userCoupon.getCouponId(), userCoupon.getCouponType());
+                    return;
+            }
 
-                // 更新用户优惠券信息
-                userCouponService.lambdaUpdate()
-                        .set(UserCoupon::getStatus, UserCoupon.STATUS_无效)
-                        .set(UserCoupon::getUsageStatus, UserCoupon.USAGE_STATUS_已使用)
-                        .eq(UserCoupon::getId, userCoupon.getId())
-                        .update();
+            // 订单金额、优惠金额更新
+            chargeOrder.setDiscountAmount(discountAmount);
+            chargeOrder.setServiceMoneyDiscount(discountAmount);
+            chargeOrder.setPayAmount(Math.max(0, chargeOrder.getTotalMoney() - discountAmount));
 
-                // 更新订单优惠券关联信息
-                orderCouponService.lambdaUpdate()
-                        .set(OrderCoupon::getDiscountAmount, discountAmount)
-                        .set(OrderCoupon::getStatus, OrderCoupon.STATUS_使用成功)
-                        .eq(OrderCoupon::getStartChargeSeq, chargeOrder.getStartChargeSeq())
-                        .eq(OrderCoupon::getUserCouponId, userCoupon.getId())
-                        .update();
-
-                // 优惠券使用情况数据
-                couponService.usedQuantifyCount(userCoupon.getCouponId());
-            } else {
+            // 条件核销优惠券:仅未使用状态可核销,防止并发一券多用
+            var redeemed = userCouponService.lambdaUpdate()
+                    .set(UserCoupon::getStatus, UserCoupon.STATUS_无效)
+                    .set(UserCoupon::getUsageStatus, UserCoupon.USAGE_STATUS_已使用)
+                    .eq(UserCoupon::getId, userCoupon.getId())
+                    .eq(UserCoupon::getUsageStatus, UserCoupon.USAGE_STATUS_未使用)
+                    .update();
+            if (!redeemed) {
+                // 核销失败(已被并发使用),优惠回退为0并标记订单优惠券使用失败
+                chargeOrder.setDiscountAmount(0);
+                chargeOrder.setServiceMoneyDiscount(0);
+                chargeOrder.setPayAmount(chargeOrder.getTotalMoney());
                 orderCouponService.lambdaUpdate()
+                        .set(OrderCoupon::getDiscountAmount, 0)
                         .set(OrderCoupon::getStatus, OrderCoupon.STATUS_使用失败)
                         .eq(OrderCoupon::getStartChargeSeq, chargeOrder.getStartChargeSeq())
                         .eq(OrderCoupon::getUserCouponId, userCoupon.getId())
                         .update();
-                log.error("订单使用优惠券失败:订单号:{},优惠券id{}:服务费未达到优惠券使用门槛", chargeOrder.getStartChargeSeq(), userCoupon.getCouponId());
+                log.error("订单使用优惠券失败:订单号:{},优惠券id{}:核销时券已被使用", chargeOrder.getStartChargeSeq(), userCoupon.getCouponId());
+                return;
             }
-        } catch (Exception e) {
-            log.error("处理优惠券失败", e);
-            // 可以添加一些错误处理逻辑,比如重试机制、发送告警等
+
+            // 更新订单优惠券关联信息
+            orderCouponService.lambdaUpdate()
+                    .set(OrderCoupon::getDiscountAmount, discountAmount)
+                    .set(OrderCoupon::getStatus, OrderCoupon.STATUS_使用成功)
+                    .eq(OrderCoupon::getStartChargeSeq, chargeOrder.getStartChargeSeq())
+                    .eq(OrderCoupon::getUserCouponId, userCoupon.getId())
+                    .update();
+
+            // 优惠券使用情况数据
+            couponService.usedQuantifyCount(userCoupon.getCouponId());
+        } else {
+            orderCouponService.lambdaUpdate()
+                    .set(OrderCoupon::getStatus, OrderCoupon.STATUS_使用失败)
+                    .eq(OrderCoupon::getStartChargeSeq, chargeOrder.getStartChargeSeq())
+                    .eq(OrderCoupon::getUserCouponId, userCoupon.getId())
+                    .update();
+            log.error("订单使用优惠券失败:订单号:{},优惠券id{}:服务费未达到优惠券使用门槛", chargeOrder.getStartChargeSeq(), userCoupon.getCouponId());
         }
     }
 

+ 74 - 77
service/src/main/java/com/kym/service/miniapp/impl/RechargeRightsDiscountHandle.java

@@ -37,88 +37,85 @@ public class RechargeRightsDiscountHandle implements DiscountService {
     public void computeDiscount(ChargeOrder chargeOrder, Account account) {
         int discountAmount;
 
-        try {
-            // 获取订单权益,计算订单优惠金额,实付金额,服务费优惠金额
-            var orderRechargeRights = orderRechargeRightsService.lambdaQuery()
-                    .eq(OrderRechargeRights::getStartChargeSeq, chargeOrder.getStartChargeSeq())
-                    .eq(OrderRechargeRights::getUserId, chargeOrder.getUserId())
+        // 获取订单权益,计算订单优惠金额,实付金额,服务费优惠金额
+        var orderRechargeRights = orderRechargeRightsService.lambdaQuery()
+                .eq(OrderRechargeRights::getStartChargeSeq, chargeOrder.getStartChargeSeq())
+                .eq(OrderRechargeRights::getUserId, chargeOrder.getUserId())
+                .one();
+        if (orderRechargeRights != null) {
+            // 获取折扣,计算优惠
+            var discount = orderRechargeRights.getDiscount();
+            // 服务费优惠金额只保留到分,分以下不进行四舍五入
+            var serviceMoneyDiscount = (int) (chargeOrder.getServiceMoney() * (BigDecimal.valueOf(1).subtract(BigDecimal.valueOf(discount).divide(BigDecimal.valueOf(100)))).doubleValue());
+            chargeOrder
+                    .setPayAmount(chargeOrder.getTotalMoney() - serviceMoneyDiscount)
+                    .setDiscountAmount(serviceMoneyDiscount)
+                    .setServiceMoneyDiscount(serviceMoneyDiscount);
+
+            orderRechargeRights.setDiscountAmount(serviceMoneyDiscount);
+
+            // 用户充值权益
+            var userRechargeRights = userRechargeRightsService.lambdaQuery()
+                    .eq(UserRechargeRights::getId, orderRechargeRights.getUserRightsId())
+                    .eq(UserRechargeRights::getUserId, chargeOrder.getUserId())
+                    .eq(UserRechargeRights::getRightsId, orderRechargeRights.getRightsId())
                     .one();
-            if (orderRechargeRights != null) {
-                // 获取折扣,计算优惠
-                var discount = orderRechargeRights.getDiscount();
-                // 服务费优惠金额只保留到分,分以下不进行四舍五入
-                var serviceMoneyDiscount = (int) (chargeOrder.getServiceMoney() * (BigDecimal.valueOf(1).subtract(BigDecimal.valueOf(discount).divide(BigDecimal.valueOf(100)))).doubleValue());
-                chargeOrder
-                        .setPayAmount(chargeOrder.getTotalMoney() - serviceMoneyDiscount)
-                        .setDiscountAmount(serviceMoneyDiscount)
-                        .setServiceMoneyDiscount(serviceMoneyDiscount);
-
-                orderRechargeRights.setDiscountAmount(serviceMoneyDiscount);
-
-                // 用户充值权益
-                var userRechargeRights = userRechargeRightsService.lambdaQuery()
-                        .eq(UserRechargeRights::getId, orderRechargeRights.getUserRightsId())
-                        .eq(UserRechargeRights::getUserId, chargeOrder.getUserId())
-                        .eq(UserRechargeRights::getRightsId, orderRechargeRights.getRightsId())
-                        .one();
-
-                // 用户当前权益金余额是否足够支付本次充电费用
-                if (userRechargeRights.getStatus() == UserRechargeRights.STATUS_有效) {
-                    if (chargeOrder.getPayAmount() <= userRechargeRights.getRightsBalance()) {
-                        userRechargeRights
-                                // 用户权益包余额扣减当前订单实付金额
-                                .setRightsBalance(userRechargeRights.getRightsBalance() - chargeOrder.getPayAmount())
-                                .setDiscountAmount(userRechargeRights.getDiscountAmount() + serviceMoneyDiscount);
-
-                        // 账户设置优惠不可退金额
-                        discountAmount = account.getDiscountAmount() + serviceMoneyDiscount;
-                        account.setDiscountAmount(discountAmount);
-                        // 权益失效时更新状态
-                        if (chargeOrder.getPayAmount().intValue() == userRechargeRights.getRightsBalance().intValue()) {
-                            userRechargeRights.setStatus(UserRechargeRights.STATUS_无效);
-                        }
-                    } else {
-                        // 不足(用户当前权益金余额是否足够支付本次充电费用)
-                        // 如果优惠后的实付金额大于权益金余额,那么实际优惠金额需要通过权益金余额反推
-                        // 通过权益金余额,反推最多扣减多少服务费
-                        // rightBalance = elecMoney + serviceMoney * (1-discount)
-                        // 订单中elecMoney与serviceMoney的比值是一定的
-                        var ratio = BigDecimal.valueOf(chargeOrder.getElecMoney()).divide(BigDecimal.valueOf(chargeOrder.getServiceMoney()), 2, RoundingMode.HALF_UP);
-                        // (ratio * realServiceMoney) + (realServiceMoney * (100 - discount)/100) = rightBalance
-                        var discountRate = BigDecimal.valueOf(discount).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
-                        var userRightBalance = BigDecimal.valueOf(userRechargeRights.getRightsBalance());
-                        // 真实可以覆盖的服务费金额
-                        var realServiceMoney = userRightBalance.divide(ratio.add(BigDecimal.valueOf(1)).subtract(discountRate), 2, RoundingMode.HALF_UP);
-                        // 实际优惠金额 = 实际可以覆盖的服务费金额 * 折扣率
-                        var realDiscountAmount = realServiceMoney.multiply(BigDecimal.valueOf(1).subtract(discountRate)).intValue();
-                        chargeOrder
-                                // 实付金额
-                                .setPayAmount(chargeOrder.getTotalMoney() - realDiscountAmount)
-                                .setDiscountAmount(realDiscountAmount)
-                                .setServiceMoneyDiscount(realDiscountAmount);
-                        // 余额扣完,总优惠金额加上最后一次真实优惠金额,此条用户权益结束,状态设置为无效
-                        userRechargeRights
-                                .setRightsBalance(0)
-                                .setDiscountAmount(userRechargeRights.getDiscountAmount() + realDiscountAmount)
-                                .setStatus(UserRechargeRights.STATUS_无效);
-
-                        orderRechargeRights.setDiscountAmount(realDiscountAmount);
-                        // 此权益包消耗完毕,账户设置优惠不可退金额
-                        discountAmount = account.getDiscountAmount() + realDiscountAmount;
-                        account.setDiscountAmount(discountAmount);
+            if (userRechargeRights == null) {
+                log.error("订单{}的用户充值权益{}不存在,跳过优惠处理", chargeOrder.getStartChargeSeq(), orderRechargeRights.getUserRightsId());
+                return;
+            }
+
+            // 用户当前权益金余额是否足够支付本次充电费用
+            if (userRechargeRights.getStatus() == UserRechargeRights.STATUS_有效) {
+                if (chargeOrder.getPayAmount() <= userRechargeRights.getRightsBalance()) {
+                    userRechargeRights
+                            // 用户权益包余额扣减当前订单实付金额
+                            .setRightsBalance(userRechargeRights.getRightsBalance() - chargeOrder.getPayAmount())
+                            .setDiscountAmount(userRechargeRights.getDiscountAmount() + serviceMoneyDiscount);
+
+                    // 账户设置优惠不可退金额
+                    discountAmount = account.getDiscountAmount() + serviceMoneyDiscount;
+                    account.setDiscountAmount(discountAmount);
+                    // 权益失效时更新状态
+                    if (chargeOrder.getPayAmount().intValue() == userRechargeRights.getRightsBalance().intValue()) {
+                        userRechargeRights.setStatus(UserRechargeRights.STATUS_无效);
                     }
+                } else {
+                    // 不足(用户当前权益金余额是否足够支付本次充电费用)
+                    // 如果优惠后的实付金额大于权益金余额,那么实际优惠金额需要通过权益金余额反推
+                    // 通过权益金余额,反推最多扣减多少服务费
+                    // rightBalance = elecMoney + serviceMoney * (1-discount)
+                    // 订单中elecMoney与serviceMoney的比值是一定的
+                    var ratio = BigDecimal.valueOf(chargeOrder.getElecMoney()).divide(BigDecimal.valueOf(chargeOrder.getServiceMoney()), 2, RoundingMode.HALF_UP);
+                    // (ratio * realServiceMoney) + (realServiceMoney * (100 - discount)/100) = rightBalance
+                    var discountRate = BigDecimal.valueOf(discount).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
+                    var userRightBalance = BigDecimal.valueOf(userRechargeRights.getRightsBalance());
+                    // 真实可以覆盖的服务费金额
+                    var realServiceMoney = userRightBalance.divide(ratio.add(BigDecimal.valueOf(1)).subtract(discountRate), 2, RoundingMode.HALF_UP);
+                    // 实际优惠金额 = 实际可以覆盖的服务费金额 * 折扣率
+                    var realDiscountAmount = realServiceMoney.multiply(BigDecimal.valueOf(1).subtract(discountRate)).intValue();
+                    chargeOrder
+                            // 实付金额
+                            .setPayAmount(chargeOrder.getTotalMoney() - realDiscountAmount)
+                            .setDiscountAmount(realDiscountAmount)
+                            .setServiceMoneyDiscount(realDiscountAmount);
+                    // 余额扣完,总优惠金额加上最后一次真实优惠金额,此条用户权益结束,状态设置为无效
+                    userRechargeRights
+                            .setRightsBalance(0)
+                            .setDiscountAmount(userRechargeRights.getDiscountAmount() + realDiscountAmount)
+                            .setStatus(UserRechargeRights.STATUS_无效);
+
+                    orderRechargeRights.setDiscountAmount(realDiscountAmount);
+                    // 此权益包消耗完毕,账户设置优惠不可退金额
+                    discountAmount = account.getDiscountAmount() + realDiscountAmount;
+                    account.setDiscountAmount(discountAmount);
                 }
-
-                // 更新订单权益的最终优惠金额
-                orderRechargeRightsService.updateById(orderRechargeRights);
-                // 更新用户权益金等
-                userRechargeRightsService.updateById(userRechargeRights);
             }
 
-
-        } catch (Exception e) {
-            log.error("处理充值优惠失败", e);
-            // 可以添加一些错误处理逻辑,比如重试机制、发送告警等
+            // 更新订单权益的最终优惠金额
+            orderRechargeRightsService.updateById(orderRechargeRights);
+            // 更新用户权益金等
+            userRechargeRightsService.updateById(userRechargeRights);
         }
     }