|
|
@@ -2,37 +2,59 @@ package com.ylx.order.service.impl;
|
|
|
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
|
|
|
import com.ylx.common.core.domain.R;
|
|
|
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.massage.domain.MaTechnician;
|
|
|
import com.ylx.massage.domain.TAddress;
|
|
|
import com.ylx.massage.domain.TWxUser;
|
|
|
import com.ylx.massage.domain.vo.HomeBlock;
|
|
|
import com.ylx.massage.domain.vo.OrderVerificationVo;
|
|
|
import com.ylx.massage.domain.vo.TechnicianAvailabilityVo;
|
|
|
+import com.ylx.massage.service.CouponService;
|
|
|
import com.ylx.massage.service.IMaTechnicianService;
|
|
|
import com.ylx.massage.service.TAddressService;
|
|
|
+import com.ylx.massage.service.TWxUserService;
|
|
|
import com.ylx.massage.utils.OrderNumberGenerator;
|
|
|
+import com.ylx.order.domain.OrderStatusFlow;
|
|
|
import com.ylx.order.domain.TOrder;
|
|
|
+import com.ylx.order.domain.dto.OrderDateQueryDTO;
|
|
|
import com.ylx.order.domain.dto.OrderSubmitDTO;
|
|
|
+import com.ylx.order.domain.dto.OrderUpdateStatusDTO;
|
|
|
+import com.ylx.order.domain.vo.OrderDateQueryVo;
|
|
|
+import com.ylx.order.enums.OrderStatusEnum;
|
|
|
+import com.ylx.order.enums.PaymentMethodEnum;
|
|
|
+import com.ylx.order.mapper.OrderStatusFlowMapper;
|
|
|
import com.ylx.order.mapper.TOrderMapper;
|
|
|
+import com.ylx.order.service.OrderStatusFlowService;
|
|
|
import com.ylx.order.service.TOrderService;
|
|
|
import com.ylx.project.domain.Project;
|
|
|
import com.ylx.project.service.ProjectService;
|
|
|
+import com.ylx.shopingfundsdetail.domain.vo.ShoppingFundsDetailAddDto;
|
|
|
+import com.ylx.shopingfundsdetail.enums.ShoppingFundsExpenseTypeEnum;
|
|
|
+import com.ylx.shopingfundsdetail.service.ShoppingFundsDetailService;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
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;
|
|
|
import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
|
/**
|
|
|
@@ -42,6 +64,8 @@ import java.util.*;
|
|
|
@Slf4j
|
|
|
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements TOrderService {
|
|
|
|
|
|
+ private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("M月d日");
|
|
|
+ private final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
|
|
|
@Resource
|
|
|
private ProjectService projectService;
|
|
|
@Resource
|
|
|
@@ -50,6 +74,17 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
|
|
|
private IMaTechnicianService maTechnicianService;
|
|
|
@Resource
|
|
|
private OrderNumberGenerator orderNumberGenerator;
|
|
|
+ @Resource
|
|
|
+ private TWxUserService wxUserService;
|
|
|
+ @Resource
|
|
|
+ private ShoppingFundsDetailService shoppingFundsDetailService;
|
|
|
+ @Resource
|
|
|
+ private CouponService couponService;
|
|
|
+ @Resource
|
|
|
+ private WxPayV3Service wxPayV3Service;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private OrderStatusFlowService orderStatusFlowService;
|
|
|
|
|
|
@Override
|
|
|
public TOrder addOrder(TOrder order) {
|
|
|
@@ -183,113 +218,346 @@ 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(Long.parseLong(wxLoginUser.getId()));
|
|
|
-
|
|
|
- // 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>()
|
|
|
+ // 4. 获取联系人信息
|
|
|
+ TAddress address = this.addressService.getById(dto.getAddressId());
|
|
|
+ if (ObjectUtil.isNull(address)) {
|
|
|
+ throw new ServiceException("客户联系地址不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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)) {
|
|
|
+ if (CollUtil.isEmpty(merchantAddressList)) {
|
|
|
throw new ServiceException("商户地址不存在");
|
|
|
}
|
|
|
|
|
|
- order.setMerchantId(dto.getMerchantId());
|
|
|
- order.setMerchantType(maTechnician.getTechType());
|
|
|
- order.setMerchantNickName(maTechnician.getTeNickName());
|
|
|
- order.setMerchantAvatar(maTechnician.getTeAvatar());
|
|
|
+ // 6. 计算价格与优惠券核销 (核心优化:先算钱、扣券,再落库)
|
|
|
+ BigDecimal basePrice = Optional.ofNullable(project.getPrice()).orElse(BigDecimal.ZERO);
|
|
|
+ BigDecimal trafficFee = Optional.ofNullable(dto.getTrafficFee()).orElse(BigDecimal.ZERO);
|
|
|
+ BigDecimal couponDiscount = BigDecimal.ZERO;
|
|
|
|
|
|
- // 4. 获取联系人信息
|
|
|
- TAddress address = this.addressService.getById(dto.getAddressId());
|
|
|
- if (ObjectUtil.isNull(address)) {
|
|
|
- throw new ServiceException("客户联系地址不存在");
|
|
|
+ if (ObjectUtil.isNotNull(dto.getCouponId())) {
|
|
|
+ // 计算抵扣金额
|
|
|
+ couponDiscount = this.couponService.calculateDiscountAmount(dto.getCouponId(), openId, basePrice);
|
|
|
+ // 立即核销优惠券,保证与订单在同一事务中
|
|
|
+ this.couponService.useCoupon(dto.getCouponId(), openId, null, 1);
|
|
|
}
|
|
|
|
|
|
- order.setContactPersonName(address.getUserName());
|
|
|
- order.setContactPhoneNumber(address.getPhone());
|
|
|
- order.setContactAddressInfo(address.getDetailAddress());
|
|
|
+ // 实付金额 = 商品原价 - 优惠券优惠 + 车费
|
|
|
+ BigDecimal finalAmount = basePrice.subtract(couponDiscount).add(trafficFee)
|
|
|
+ .setScale(2, RoundingMode.HALF_UP);
|
|
|
|
|
|
- // 5. 设置价格信息
|
|
|
- LocalDateTime appointmentStartTime = dto.getAppointmentStartTime();
|
|
|
+ // 7. 组装订单对象
|
|
|
+ TOrder order = buildOrder(dto, project, maTechnician, address, merchantAddressList,
|
|
|
+ basePrice, trafficFee, couponDiscount, finalAmount, userId);
|
|
|
|
|
|
- Integer paymentMethod = dto.getPaymentMethod();
|
|
|
- // 优惠券优惠
|
|
|
- BigDecimal couponDiscount = Optional.ofNullable(dto.getCouponDiscount()).orElse(BigDecimal.ZERO);
|
|
|
- // 车费
|
|
|
- BigDecimal trafficFee = Optional.ofNullable(dto.getTrafficFee()).orElse(BigDecimal.ZERO);
|
|
|
- // 商品原价
|
|
|
- BigDecimal price = Optional.ofNullable(project.getPrice()).orElse(BigDecimal.ZERO);
|
|
|
+ // 8. 保存订单
|
|
|
+ boolean saveResult = this.save(order);
|
|
|
+ if (!saveResult) {
|
|
|
+ throw new ServiceException("添加订单失败");
|
|
|
+ }
|
|
|
|
|
|
- // 实付金额 = 商品原价 - 优惠券优惠 + 车费
|
|
|
- BigDecimal finalAmount = price.subtract(couponDiscount).add(trafficFee);
|
|
|
+ // 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 用户端订单列表
|
|
|
+ *
|
|
|
+ * @param dto
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public Page<OrderDateQueryVo> queryOrderList(OrderDateQueryDTO dto) {
|
|
|
+ // 1. 构造分页对象
|
|
|
+ Page<TOrder> page = new Page<>(dto.getPageNum(), dto.getPageSize());
|
|
|
+
|
|
|
+ // 2. 构造查询条件
|
|
|
+ LambdaQueryWrapper<TOrder> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ if (dto.getStartDate() != null) {
|
|
|
+ LocalDateTime start = dto.getStartDate().atStartOfDay();
|
|
|
+ wrapper.ge(TOrder::getCreateTime, start);
|
|
|
+ }
|
|
|
+ if (dto.getEndDate() != null) {
|
|
|
+ LocalDateTime end = dto.getEndDate().atTime(LocalTime.MAX);
|
|
|
+ wrapper.le(TOrder::getCreateTime, end);
|
|
|
+ }
|
|
|
+ if (StrUtil.isNotBlank(dto.getProjectName())) {
|
|
|
+ wrapper.like(TOrder::getProjectName, dto.getProjectName());
|
|
|
+ }
|
|
|
+ if (StrUtil.isNotBlank(dto.getMerchantNickName())) {
|
|
|
+ wrapper.like(TOrder::getMerchantNickName, dto.getMerchantNickName());
|
|
|
+ }
|
|
|
+ wrapper.eq(TOrder::getIsDelete, 0);
|
|
|
+ wrapper.orderByDesc(TOrder::getCreateTime);
|
|
|
+
|
|
|
+ // 3. 执行分页查询
|
|
|
+ Page<TOrder> orderPage = baseMapper.selectPage(page, wrapper);
|
|
|
+
|
|
|
+ // 4. 转换 VO
|
|
|
+ List<OrderDateQueryVo> voList = orderPage.getRecords().stream()
|
|
|
+ .map(this::convertToVo)
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+
|
|
|
+ // 5. 返回分页结果
|
|
|
+
|
|
|
+ Page<OrderDateQueryVo> voPage = new Page<>(orderPage.getCurrent(), orderPage.getSize(), orderPage.getTotal());
|
|
|
+ voPage.setRecords(voList);
|
|
|
|
|
|
- order.setBasePrice(price);
|
|
|
+ return voPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 用户端逻辑删除
|
|
|
+ *
|
|
|
+ * @param orderId
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void logicDeleteOrder(Long orderId) {
|
|
|
+ // 1. 查询订单是否存在
|
|
|
+ TOrder order = getById(orderId);
|
|
|
+ if (order == null) {
|
|
|
+ throw new ServiceException("订单不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 如果已经是删除状态,可提示或直接返回
|
|
|
+ if (Integer.valueOf(1).equals(order.getIsDelete())) {
|
|
|
+ throw new ServiceException("订单已在回收站中");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 执行逻辑删除
|
|
|
+ order.setIsDelete(1);
|
|
|
+ order.setDeletedTime(LocalDateTime.now());
|
|
|
+ boolean updated = updateById(order);
|
|
|
+ if (!updated) {
|
|
|
+ throw new ServiceException("删除失败,请稍后重试");
|
|
|
+ }
|
|
|
+ // 5. 事务成功,返回(Controller中会返回 success 信息)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void processOrderPayment(WxPayOrderNotifyV3Result.DecryptNotifyResult result, TWxUser wxUser, TOrder order) {
|
|
|
+ // 更新订单状态
|
|
|
+ this.lambdaUpdate()
|
|
|
+ .set(TOrder::getStatus, OrderStatusEnum.PENDING_DISPATCH.getCode())
|
|
|
+ .eq(TOrder::getId, order.getId())
|
|
|
+ .update();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ *
|
|
|
+ * 更新订单状态(修改订单表状态必须使用此接口)
|
|
|
+ * @param dto
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void updateOrderStatus(OrderUpdateStatusDTO dto) {
|
|
|
+ Long orderId = dto.getOrderId();
|
|
|
+ Integer newStatus = dto.getStatus();
|
|
|
+
|
|
|
+ // 1. 查询订单
|
|
|
+ TOrder order = this.baseMapper.selectById(orderId);
|
|
|
+ if (order == null) {
|
|
|
+ throw new ServiceException("订单不存在");
|
|
|
+ }
|
|
|
+ Integer oldStatus = order.getStatus();
|
|
|
+
|
|
|
+ // 2. 更新订单状态
|
|
|
+ order.setStatus(newStatus);
|
|
|
+ // 可根据状态同步更新对应时间字段(例如:支付完成时间、接单时间等),此处省略具体业务逻辑
|
|
|
+ int updateCount = this.baseMapper.updateById(order);
|
|
|
+ if (updateCount == 0) {
|
|
|
+ throw new ServiceException("更新订单状态失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 记录状态流转流水
|
|
|
+ OrderStatusFlow flow = new OrderStatusFlow();
|
|
|
+ flow.setOrderId(orderId);
|
|
|
+ flow.setStatus(newStatus);
|
|
|
+ orderStatusFlowService.getBaseMapper().insert(flow);
|
|
|
+
|
|
|
+ log.info("订单状态变更: orderId={}, oldStatus={}, newStatus={}", orderId, oldStatus, newStatus);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将 TOrder 转换为 OrderDateQueryVo,并处理 serviceTime 字段
|
|
|
+ */
|
|
|
+ private OrderDateQueryVo convertToVo(TOrder order) {
|
|
|
+ OrderDateQueryVo vo = new OrderDateQueryVo();
|
|
|
+ // 拷贝相同字段
|
|
|
+ BeanUtils.copyProperties(order, vo);
|
|
|
+ // 手动设置字段名不一致或需要特殊处理的
|
|
|
+ vo.setOrderStatus(order.getStatus());
|
|
|
+ vo.setOrderStatusName(getOrderStatusName(order.getStatus()));
|
|
|
+ // 服务时间范围:预约开始时间 + 项目时长(分钟)
|
|
|
+ // 使用 startTime 和 completedTime 构建服务时间展示
|
|
|
+ String serviceTime = buildServiceTime(order.getStartTime(), order.getCompletedTime());
|
|
|
+ vo.setServiceTime(serviceTime);
|
|
|
+
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 订单状态码 -> 中文描述
|
|
|
+ */
|
|
|
+ private String getOrderStatusName(Integer status) {
|
|
|
+ return OrderStatusEnum.getInfoByCode(status);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 拼接服务时间字符串,例如 “2026-06-08 14:30 (90分钟)”
|
|
|
+ */
|
|
|
+ private String buildServiceTime(LocalDateTime startTime, LocalDateTime completedTime) {
|
|
|
+ if (startTime == null || completedTime == null) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ String date = startTime.format(dateFormatter);
|
|
|
+ String start = startTime.format(timeFormatter);
|
|
|
+ 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.setPaymentMethod(paymentMethod);
|
|
|
-
|
|
|
+ order.setCouponId(dto.getCouponId());
|
|
|
order.setCreateTime(DateUtils.getNowDate());
|
|
|
- order.setAppointmentStartTime(appointmentStartTime);
|
|
|
+ order.setAppointmentStartTime(dto.getAppointmentStartTime());
|
|
|
order.setStatus(0);
|
|
|
order.setExecStatus(0);
|
|
|
order.setDispatchedStatus(0);
|
|
|
+ order.setPaymentMethod(dto.getPaymentMethod());
|
|
|
|
|
|
- // 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 -> {
|
|
|
+
|
|
|
+ merchantAddressList.stream().filter(a -> a.getType() == 1).findFirst().ifPresent(addr -> {
|
|
|
order.setMerchantLongitude(new BigDecimal(addr.getLongitude()));
|
|
|
order.setMerchantLatitude(new BigDecimal(addr.getLatitude()));
|
|
|
});
|
|
|
- virtualAddrOpt.ifPresent(addr -> {
|
|
|
+ merchantAddressList.stream().filter(a -> a.getType() == 2).findFirst().ifPresent(addr -> {
|
|
|
order.setVirtualLongitude(new BigDecimal(addr.getLongitude()));
|
|
|
order.setVirtualLatitude(new BigDecimal(addr.getLatitude()));
|
|
|
});
|
|
|
|
|
|
- boolean saveResult = this.save(order);
|
|
|
- if (!saveResult) {
|
|
|
- throw new ServiceException("添加订单失败");
|
|
|
+ // 设置支付状态
|
|
|
+ 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("支付服务异常,请稍后重试");
|
|
|
}
|
|
|
}
|
|
|
}
|