Forráskód Böngészése

Merge remote-tracking branch 'origin/dev' into dev

# Conflicts:
#	nightFragrance-massage/src/main/java/com/ylx/order/controller/OrderController.java
郭子栋 2 napja
szülő
commit
8d21fddf85

+ 4 - 3
nightFragrance-massage/src/main/java/com/ylx/order/controller/OrderController.java

@@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
+import java.util.Map;
 
 @RestController
 @RequestMapping("/order")
@@ -31,9 +32,9 @@ public class OrderController {
     @PreAuthorize("@customerAuth.isCustomer()")
     @ApiOperation("客户端用户提交订单")
     @PostMapping("/submit")
-    public R<?> submitOrder(@Validated @RequestBody OrderSubmitDTO dto) {
-        this.orderService.submitOrder(dto);
-        return R.ok();
+    public R<Map<String, Object>> submitOrder(@Validated @RequestBody OrderSubmitDTO dto) {
+        Map<String, Object> data = this.orderService.submitOrder(dto);
+        return R.ok(data);
     }
 
     @PreAuthorize("@customerAuth.isCustomer()")

+ 1 - 1
nightFragrance-massage/src/main/java/com/ylx/order/service/TOrderService.java

@@ -153,7 +153,7 @@ public interface TOrderService extends IService<TOrder> {
      */
     public List<Map<String, Object>> myIncomeDetail(TWxUser user, Integer wStatus);
 
-    void submitOrder(OrderSubmitDTO dto);
+    Map<String, Object> submitOrder(OrderSubmitDTO dto);
 
     /**
      * 用户端订单列表

+ 151 - 112
nightFragrance-massage/src/main/java/com/ylx/order/service/impl/TOrderServiceImpl.java

@@ -11,6 +11,9 @@ import com.ylx.common.core.domain.model.WxLoginUser;
 import com.ylx.common.exception.ServiceException;
 import com.ylx.common.utils.DateUtils;
 import com.ylx.common.utils.SecurityUtils;
+import com.ylx.common.weixinPay.enums.WxPayTypeEnum;
+import com.ylx.common.weixinPay.service.WxPayV3Service;
+import com.ylx.giftCard.domain.GiftCardOrder;
 import com.ylx.massage.domain.MaTechnician;
 import com.ylx.massage.domain.TAddress;
 import com.ylx.massage.domain.TWxUser;
@@ -42,6 +45,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
@@ -72,6 +76,8 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
     private ShoppingFundsDetailService shoppingFundsDetailService;
     @Resource
     private CouponService couponService;
+    @Resource
+    private WxPayV3Service wxPayV3Service;
 
     @Override
     public TOrder addOrder(TOrder order) {
@@ -205,156 +211,83 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void submitOrder(OrderSubmitDTO dto) {
-
-        TOrder order = new TOrder();
-        String orderNo = orderNumberGenerator.generateNextOrderNumber("");
-        order.setOrderNo(orderNo);
+    public Map<String, Object> submitOrder(OrderSubmitDTO dto) {
+        Map<String, Object> map = new HashMap<>();
 
-        // 1. 获取当前用户
+        // 1. 获取并校验当前用户
         WxLoginUser wxLoginUser = SecurityUtils.getWxLoginUser();
         if (ObjectUtil.isNull(wxLoginUser)) {
             throw new ServiceException("用户未登录");
         }
-
         Long userId = Long.parseLong(wxLoginUser.getId());
+        String openId = wxLoginUser.getCOpenid();
 
-        order.setUserId(userId);
-
-        // 2. 获取项目信息
+        // 2. 获取并校验项目信息
         Project project = this.projectService.getById(dto.getProjectId());
         if (ObjectUtil.isNull(project)) {
             throw new ServiceException("项目不存在");
         }
 
-        order.setProjectId(dto.getProjectId());
-        order.setProjectType(project.getType());
-        order.setProjectName(project.getTitle());
-        order.setProjectCover(project.getCover());
-        order.setHighlight(project.getHighlight());
-        order.setAppointmentStartTime(dto.getAppointmentStartTime());
-        order.setProjectDuration(project.getStandardDuration());
-
-        // 3. 获取商户信息
+        // 3. 获取并校验商户信息
         MaTechnician maTechnician = this.maTechnicianService.getById(dto.getMerchantId());
         if (ObjectUtil.isNull(maTechnician)) {
             throw new ServiceException("商户不存在");
         }
 
-        List<TAddress> merchantAddresslist = this.addressService.list(new LambdaQueryWrapper<TAddress>()
-                .eq(TAddress::getMerchantId, dto.getMerchantId())
-                .eq(TAddress::getUserType, 2)
-                .eq(TAddress::getIsDelete, 0));
-        if (CollUtil.isEmpty(merchantAddresslist)) {
-            throw new ServiceException("商户地址不存在");
-        }
-
-        order.setMerchantId(dto.getMerchantId());
-        order.setMerchantType(maTechnician.getTechType());
-        order.setMerchantNickName(maTechnician.getTeNickName());
-        order.setMerchantAvatar(maTechnician.getTeAvatar());
-
         // 4. 获取联系人信息
         TAddress address = this.addressService.getById(dto.getAddressId());
         if (ObjectUtil.isNull(address)) {
             throw new ServiceException("客户联系地址不存在");
         }
 
-        order.setContactPersonName(address.getUserName());
-        order.setContactPhoneNumber(address.getPhone());
-        order.setContactAddressInfo(address.getDetailAddress());
-
-        // 5. 设置价格信息
-        LocalDateTime appointmentStartTime = dto.getAppointmentStartTime();
+        // 5. 获取商户地址信息
+        List<TAddress> merchantAddressList = this.addressService.list(new LambdaQueryWrapper<TAddress>()
+                .eq(TAddress::getMerchantId, dto.getMerchantId())
+                .eq(TAddress::getUserType, 2)
+                .eq(TAddress::getIsDelete, 0));
+        if (CollUtil.isEmpty(merchantAddressList)) {
+            throw new ServiceException("商户地址不存在");
+        }
 
-        // 优惠券优惠
+        // 6. 计算价格与优惠券核销 (核心优化:先算钱、扣券,再落库)
+        BigDecimal basePrice = Optional.ofNullable(project.getPrice()).orElse(BigDecimal.ZERO);
+        BigDecimal trafficFee = Optional.ofNullable(dto.getTrafficFee()).orElse(BigDecimal.ZERO);
         BigDecimal couponDiscount = BigDecimal.ZERO;
-        order.setCouponId(dto.getCouponId());
+
         if (ObjectUtil.isNotNull(dto.getCouponId())) {
-            couponDiscount = this.couponService.calculateDiscountAmount(dto.getCouponId(), wxLoginUser.getCOpenid(), project.getPrice());
+            // 计算抵扣金额
+            couponDiscount = this.couponService.calculateDiscountAmount(dto.getCouponId(), openId, basePrice);
+            // 立即核销优惠券,保证与订单在同一事务中
+            this.couponService.useCoupon(dto.getCouponId(), openId, null, 1);
         }
-        // 车费
-        BigDecimal trafficFee = Optional.ofNullable(dto.getTrafficFee()).orElse(BigDecimal.ZERO);
-        // 商品原价
-        BigDecimal price = Optional.ofNullable(project.getPrice()).orElse(BigDecimal.ZERO);
 
         // 实付金额 = 商品原价 - 优惠券优惠 + 车费
-        BigDecimal finalAmount = price.subtract(couponDiscount).add(trafficFee);
-
-        order.setBasePrice(price);
-        order.setCouponDiscount(couponDiscount);
-        order.setTrafficFee(trafficFee);
-        order.setFinalAmount(finalAmount);
+        BigDecimal finalAmount = basePrice.subtract(couponDiscount).add(trafficFee)
+                .setScale(2, RoundingMode.HALF_UP);
 
-        order.setCreateTime(DateUtils.getNowDate());
-        order.setAppointmentStartTime(appointmentStartTime);
-        order.setStatus(0);
-        order.setExecStatus(0);
-        order.setDispatchedStatus(0);
-
-        // 6.经纬度信息
-        order.setUserLatitude(new BigDecimal(address.getLatitude()));
-        order.setUserLongitude(new BigDecimal(address.getLongitude()));
-        //取默认真实地址(type=1)
-        Optional<TAddress> realAddrOpt = merchantAddresslist.stream()
-                .filter(a -> a.getType() == 1)
-                .findFirst();
-        //取默认虚拟地址(type=2)
-        Optional<TAddress> virtualAddrOpt = merchantAddresslist.stream()
-                .filter(a -> a.getType() == 2)
-                .findFirst();
-
-        realAddrOpt.ifPresent(addr -> {
-            order.setMerchantLongitude(new BigDecimal(addr.getLongitude()));
-            order.setMerchantLatitude(new BigDecimal(addr.getLatitude()));
-        });
-        virtualAddrOpt.ifPresent(addr -> {
-            order.setVirtualLongitude(new BigDecimal(addr.getLongitude()));
-            order.setVirtualLatitude(new BigDecimal(addr.getLatitude()));
-        });
-
-        // 7. 判断支付方式
-        Integer paymentMethod = dto.getPaymentMethod();
-        order.setPaymentMethod(paymentMethod);
-        // 余额支付
-        if (ObjectUtil.equals(PaymentMethodEnum.BALANCE.getCode(), paymentMethod)) {
-            order.setStatus(OrderStatusEnum.PENDING_DISPATCH.getCode());
-            order.setPaidTime(LocalDateTime.now());
-        } else {
-            order.setStatus(OrderStatusEnum.PENDING_PAYMENT.getCode());
-        }
+        // 7. 组装订单对象
+        TOrder order = buildOrder(dto, project, maTechnician, address, merchantAddressList,
+                basePrice, trafficFee, couponDiscount, finalAmount, userId);
 
+        // 8. 保存订单
         boolean saveResult = this.save(order);
         if (!saveResult) {
             throw new ServiceException("添加订单失败");
         }
 
-        // 8. 处理用户余额数据
-        if (ObjectUtil.equals(PaymentMethodEnum.BALANCE.getCode(), paymentMethod)) {
-
-            // 处理用户余额
-            TWxUser user = this.wxUserService.getById(userId);
-            BigDecimal newBalance = user.getdBalance().add(finalAmount);
-            this.wxUserService.lambdaUpdate()
-                    .set(TWxUser::getdBalance, newBalance)
-                    .eq(TWxUser::getId, user.getId())
-                    .update();
-
-            // 记录购物金明细
-            ShoppingFundsDetailAddDto shoppingFundsDetailAddDto = new ShoppingFundsDetailAddDto();
-            shoppingFundsDetailAddDto.setUserId(userId.toString());
-            shoppingFundsDetailAddDto.setAmount(finalAmount);
-            shoppingFundsDetailAddDto.setOrderNo(orderNo);
-            shoppingFundsDetailAddDto.setExpenseType(ShoppingFundsExpenseTypeEnum.CONSUMPTION.getCode());
-            shoppingFundsDetailAddDto.setBalance(newBalance);
-            shoppingFundsDetailService.addShoppingFundsDetail(shoppingFundsDetailAddDto);
-
-        }
-        // 9. 判断有没有使用优惠券
-        if (couponDiscount.compareTo(BigDecimal.ZERO) > 0) {
-            this.couponService.useCoupon(dto.getCouponId(), wxLoginUser.getCOpenid(), order.getId(), 1);
+        // 9. 更新优惠券关联的真实订单ID (因为刚才核销时订单还没生成,这里补上)
+        if (ObjectUtil.isNotNull(dto.getCouponId())) {
+            this.couponService.useCoupon(dto.getCouponId(), openId, order.getId(), 1);
         }
 
+        // 10. 处理余额支付逻辑
+        if (ObjectUtil.equals(PaymentMethodEnum.BALANCE.getCode(), dto.getPaymentMethod())) {
+            handleBalancePayment(userId, finalAmount, order.getOrderNo());
+            map.put("orderId", order.getId());
+            return map;
+        } else {
+            return createWxPayOrder(order, wxLoginUser);
+        }
     }
 
     /**
@@ -470,4 +403,110 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
         String end = completedTime.format(timeFormatter);
         return date + " " + start + "-" + end;
     }
+
+    /**
+     * 组装订单对象
+     */
+    private TOrder buildOrder(OrderSubmitDTO dto, Project project, MaTechnician maTechnician,
+                              TAddress address, List<TAddress> merchantAddressList,
+                              BigDecimal basePrice, BigDecimal trafficFee, BigDecimal couponDiscount,
+                              BigDecimal finalAmount, Long userId) {
+        TOrder order = new TOrder();
+        order.setOrderNo(orderNumberGenerator.generateNextOrderNumber(""));
+        order.setUserId(userId);
+        order.setProjectId(dto.getProjectId());
+        order.setProjectType(project.getType());
+        order.setProjectName(project.getTitle());
+        order.setProjectCover(project.getCover());
+        order.setHighlight(project.getHighlight());
+        order.setAppointmentStartTime(dto.getAppointmentStartTime());
+        order.setProjectDuration(project.getStandardDuration());
+        order.setMerchantId(dto.getMerchantId());
+        order.setMerchantType(maTechnician.getTechType());
+        order.setMerchantNickName(maTechnician.getTeNickName());
+        order.setMerchantAvatar(maTechnician.getTeAvatar());
+        order.setContactPersonName(address.getUserName());
+        order.setContactPhoneNumber(address.getPhone());
+        order.setContactAddressInfo(address.getDetailAddress());
+        order.setBasePrice(basePrice);
+        order.setCouponDiscount(couponDiscount);
+        order.setTrafficFee(trafficFee);
+        order.setFinalAmount(finalAmount);
+        order.setCouponId(dto.getCouponId());
+        order.setCreateTime(DateUtils.getNowDate());
+        order.setAppointmentStartTime(dto.getAppointmentStartTime());
+        order.setStatus(0);
+        order.setExecStatus(0);
+        order.setDispatchedStatus(0);
+        order.setPaymentMethod(dto.getPaymentMethod());
+
+        // 经纬度安全赋值
+        order.setUserLatitude(new BigDecimal(address.getLatitude()));
+        order.setUserLongitude(new BigDecimal(address.getLongitude()));
+
+        merchantAddressList.stream().filter(a -> a.getType() == 1).findFirst().ifPresent(addr -> {
+            order.setMerchantLongitude(new BigDecimal(addr.getLongitude()));
+            order.setMerchantLatitude(new BigDecimal(addr.getLatitude()));
+        });
+        merchantAddressList.stream().filter(a -> a.getType() == 2).findFirst().ifPresent(addr -> {
+            order.setVirtualLongitude(new BigDecimal(addr.getLongitude()));
+            order.setVirtualLatitude(new BigDecimal(addr.getLatitude()));
+        });
+
+        // 设置支付状态
+        if (ObjectUtil.equals(PaymentMethodEnum.BALANCE.getCode(), dto.getPaymentMethod())) {
+            order.setStatus(OrderStatusEnum.PENDING_DISPATCH.getCode());
+            order.setPaidTime(LocalDateTime.now());
+        } else {
+            order.setStatus(OrderStatusEnum.PENDING_PAYMENT.getCode());
+        }
+        return order;
+    }
+
+    /**
+     * 处理余额支付 (核心优化:数据库原子操作防并发)
+     */
+    private void handleBalancePayment(Long userId, BigDecimal finalAmount, String orderNo) {
+        // 1. 数据库层面原子扣减余额,并校验余额是否充足
+        boolean deductSuccess = this.wxUserService.lambdaUpdate()
+                .setSql("d_balance = d_balance - " + finalAmount)
+                .eq(TWxUser::getId, userId)
+                // 防止余额超扣的核心:当前余额必须大于等于扣减金额
+                .apply("d_balance >= {0}", finalAmount)
+                .update();
+
+        if (!deductSuccess) {
+            throw new ServiceException("余额不足,扣款失败");
+        }
+
+        // 2. 重新查询最新余额用于记录流水
+        TWxUser user = this.wxUserService.getById(userId);
+
+        // 3. 记录购物金明细
+        ShoppingFundsDetailAddDto detailDto = new ShoppingFundsDetailAddDto();
+        detailDto.setUserId(userId.toString());
+        detailDto.setAmount(finalAmount);
+        detailDto.setOrderNo(orderNo);
+        detailDto.setExpenseType(ShoppingFundsExpenseTypeEnum.CONSUMPTION.getCode());
+        detailDto.setBalance(user.getdBalance());
+        this.shoppingFundsDetailService.addShoppingFundsDetail(detailDto);
+    }
+
+    /**
+     * 创建微信支付订单(事务外执行,减少事务时长)
+     */
+    private Map<String, Object> createWxPayOrder(TOrder order, WxLoginUser wxLoginUser) {
+        try {
+            return wxPayV3Service.createV3JsapiOrder(
+                    order.getOrderNo(),
+                    order.getFinalAmount(),
+                    "购买情绪价值商品",
+                    wxLoginUser.getCOpenid(),
+                    WxPayTypeEnum.EMOTION_GOODS.getCode()
+            );
+        } catch (Exception e) {
+            log.error("微信支付下单失败,订单号: {}", order.getOrderNo(), e);
+            throw new ServiceException("支付服务异常,请稍后重试");
+        }
+    }
 }