| | |
| | | import com.doumee.biz.system.OperationConfigBiz; |
| | | import com.doumee.biz.system.SystemDictDataBiz; |
| | | import com.doumee.config.wx.WxMiniConfig; |
| | | import com.doumee.config.wx.WxMiniUtilService; |
| | | import com.doumee.config.wx.WxPayProperties; |
| | | import com.doumee.config.wx.WxPayV3Service; |
| | | import com.wechat.pay.java.service.refund.model.Refund; |
| | | import com.doumee.core.constants.Constants; |
| | | import com.doumee.core.constants.ResponseStatus; |
| | | import com.doumee.core.exception.BusinessException; |
| | | import com.doumee.core.model.PageData; |
| | | import com.doumee.core.model.PageWrap; |
| | | import com.doumee.core.utils.DateUtil; |
| | | import com.doumee.core.utils.Tencent.MapUtil; |
| | | import com.doumee.core.utils.geocode.MapUtil; |
| | | import com.doumee.core.utils.Utils; |
| | | import com.doumee.dao.business.*; |
| | | import com.doumee.dao.business.model.*; |
| | |
| | | import com.doumee.dao.dto.MyOrderDTO; |
| | | import com.doumee.dao.dto.OrderItemDTO; |
| | | import com.doumee.dao.vo.*; |
| | | import com.doumee.service.business.NoticeService; |
| | | import com.doumee.service.business.OrderLogService; |
| | | import com.doumee.service.business.OrdersService; |
| | | import com.doumee.dao.business.model.Notice; |
| | | import com.doumee.service.business.AreasService; |
| | | import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest; |
| | | import com.doumee.service.business.PricingRuleService; |
| | | import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; |
| | | import com.github.binarywang.wxpay.exception.WxPayException; |
| | | import com.github.xiaoymin.knife4j.core.util.CollectionUtils; |
| | |
| | | import org.springframework.data.redis.core.RedisTemplate; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.web.context.request.RequestContextHolder; |
| | | import org.springframework.web.context.request.ServletRequestAttributes; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.net.InetAddress; |
| | | import java.net.UnknownHostException; |
| | | import java.util.*; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * 寄存订单信息Service实现 |
| | |
| | | @Autowired |
| | | private RevenueMapper revenueMapper; |
| | | |
| | | @Autowired |
| | | private WxMiniUtilService wxMiniUtilService; |
| | | |
| | | |
| | | @Autowired |
| | | private SystemUserMapper systemUserMapper; |
| | | |
| | | @Autowired |
| | | private PricingRuleMapper pricingRuleMapper; |
| | | |
| | | @Autowired |
| | | private PricingRuleService pricingRuleService; |
| | | |
| | | @Autowired |
| | | private RedisTemplate<String, Object> redisTemplate; |
| | | |
| | |
| | | |
| | | @Autowired |
| | | private AreasService areasService; |
| | | |
| | | @Autowired |
| | | private NoticeService noticeService; |
| | | |
| | | @Autowired |
| | | private WxPayV3Service wxPayV3Service; |
| | | |
| | | @Autowired |
| | | private WxPayProperties wxPayProperties; |
| | | |
| | | @Override |
| | | public Integer create(Orders orders) { |
| | |
| | | */ |
| | | @Override |
| | | public PriceCalculateVO calculateLocalPrice(CalculateLocalPriceDTO dto) { |
| | | // 天数校验,最少1天 |
| | | int days = dto.getEstimatedDepositDays() != null && dto.getEstimatedDepositDays() > 0 |
| | | ? dto.getEstimatedDepositDays() : 1; |
| | | // 根据开始和结束时间计算天数,最少1天 |
| | | long diffMs = dto.getDepositEndTime().getTime() - dto.getDepositStartTime().getTime(); |
| | | int days = (int) Math.max(1, (diffMs / (1000 * 60 * 60 * 24)) + 1); |
| | | |
| | | // 收集所有物品类型ID |
| | | List<Integer> categoryIds = new ArrayList<>(); |
| | |
| | | itemPriceTotal += subtotal; |
| | | } |
| | | |
| | | // 保价费用:报价金额 × 保价费率(字典 INSURANCE_RATE),元→分 |
| | | // 保价费用:报价金额 × 保价费率(字典 INSURANCE_RATE),元→分(保价金额>0时计费) |
| | | long insuranceFeeFen = 0L; |
| | | if (Boolean.TRUE.equals(dto.getInsured()) && dto.getDeclaredAmount() != null) { |
| | | if (dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0) { |
| | | BigDecimal insuranceFeeYuan = calculateInsuranceFee(dto.getDeclaredAmount()); |
| | | insuranceFeeFen = insuranceFeeYuan.multiply(new BigDecimal(100)).longValue(); |
| | | } |
| | |
| | | // 1. 调用腾讯地图距离矩阵API计算驾车距离 |
| | | String from = dto.getFromLat() + "," + dto.getFromLgt(); |
| | | String to = dto.getToLat() + "," + dto.getToLgt(); |
| | | JSONObject distanceResult = MapUtil.distanceSingle("driving", from, to); |
| | | JSONObject distanceResult = MapUtil.direction("driving", from, to); |
| | | BigDecimal distance = distanceResult.getBigDecimal("distance"); |
| | | // distance 单位为米,转为公里 |
| | | BigDecimal distanceKm = distance.divide(new BigDecimal(1000), 2, RoundingMode.HALF_UP); |
| | |
| | | itemPriceTotal += subtotal; |
| | | } |
| | | |
| | | // 4. 保价费用:报价金额 × 保价费率(字典 INSURANCE_RATE),元→分 |
| | | // 4. 保价费用:报价金额 × 保价费率(字典 INSURANCE_RATE),元→分(保价金额>0时计费) |
| | | long insuranceFeeFen = 0L; |
| | | if (Boolean.TRUE.equals(dto.getInsured()) && dto.getDeclaredAmount() != null) { |
| | | if (dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0) { |
| | | BigDecimal insuranceFeeYuan = calculateInsuranceFee(dto.getDeclaredAmount()); |
| | | insuranceFeeFen = insuranceFeeYuan.multiply(new BigDecimal(100)).longValue(); |
| | | } |
| | | |
| | | // 5. 加急费用:物品价格 × 加急系数(字典 URGENT_COEFFICIENT) |
| | | long urgentFeeFen = 0L; |
| | | if (Boolean.TRUE.equals(dto.getUrgent())) { |
| | | String urgentRateStr = systemDictDataBiz.queryByCode( |
| | | Constants.OPERATION_CONFIG, Constants.OP_URGENT_COEFFICIENT).getCode(); |
| | | BigDecimal urgentRate = new BigDecimal(urgentRateStr); |
| | | urgentFeeFen = new BigDecimal(itemPriceTotal).multiply(urgentRate) |
| | | .setScale(0, RoundingMode.HALF_UP).longValue(); |
| | | } |
| | | String urgentRateStr = systemDictDataBiz.queryByCode( |
| | | Constants.OPERATION_CONFIG, Constants.OP_URGENT_COEFFICIENT).getCode(); |
| | | BigDecimal urgentRate = new BigDecimal(urgentRateStr); |
| | | urgentFeeFen = new BigDecimal(itemPriceTotal).multiply(urgentRate) |
| | | .setScale(0, RoundingMode.HALF_UP).longValue(); |
| | | |
| | | // 6. 总价格 = 物品价格 + 保价费用 + 加急费用 |
| | | long totalPrice = itemPriceTotal + insuranceFeeFen + urgentFeeFen; |
| | | // 6. 总价格 = 物品价格 + 保价费用 + 加急费用(加急时才包含加急费) |
| | | long totalPrice = itemPriceTotal + insuranceFeeFen; |
| | | if (Boolean.TRUE.equals(dto.getUrgent())) { |
| | | totalPrice += urgentFeeFen; |
| | | } |
| | | |
| | | PriceCalculateVO result = new PriceCalculateVO(); |
| | | result.setItemList(itemList); |
| | |
| | | result.setUrgentFee(urgentFeeFen); |
| | | result.setTotalPrice(totalPrice); |
| | | result.setDistance(distanceKm); |
| | | |
| | | // 7. 预计送达时长:pricing_rule type=2(fieldA=1标速达,fieldA=2极速达) |
| | | List<PricingRule> timeRules = pricingRuleMapper.selectList(new QueryWrapper<PricingRule>().lambda() |
| | | .eq(PricingRule::getDeleted, Constants.ZERO) |
| | | .eq(PricingRule::getType, Constants.TWO) |
| | | .eq(PricingRule::getCityId, dto.getCityId()) |
| | | .in(PricingRule::getFieldA, Arrays.asList("1", "2"))); |
| | | for (PricingRule tr : timeRules) { |
| | | BigDecimal baseKm = new BigDecimal(tr.getFieldB()); |
| | | int baseHours = Integer.parseInt(tr.getFieldC()); |
| | | BigDecimal extraKm = new BigDecimal(tr.getFieldD()); |
| | | int extraHours = Integer.parseInt(tr.getFieldE()); |
| | | int hours; |
| | | if (distanceKm.compareTo(baseKm) <= 0) { |
| | | hours = baseHours; |
| | | } else { |
| | | BigDecimal overDistance = distanceKm.subtract(baseKm); |
| | | int extraCount = overDistance.divide(extraKm, 0, RoundingMode.CEILING).intValue(); |
| | | hours = baseHours + extraCount * extraHours; |
| | | } |
| | | if ("1".equals(tr.getFieldA())) { |
| | | result.setStandardHours(hours); |
| | | } else if ("2".equals(tr.getFieldA())) { |
| | | result.setUrgentHours(hours); |
| | | } |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | |
| | | takeLgt = BigDecimal.valueOf(takeShop.getLongitude()); |
| | | takeLocationValue = takeShop.getAddress(); |
| | | } else if (dto.getTakeLat() != null && dto.getTakeLgt() != null && StringUtils.isNotBlank(dto.getTakeLocation())) { |
| | | // 无取件门店,校验存件点与自选取件点是否在同一城市 |
| | | if (!MapUtil.isSameCity(depositShop.getLatitude(), depositShop.getLongitude(), |
| | | dto.getTakeLat().doubleValue(), dto.getTakeLgt().doubleValue())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "异地寄存订单存取点不在同一城市,如需请选择同城门店"); |
| | | } |
| | | takeLat = dto.getTakeLat(); |
| | | takeLgt = dto.getTakeLgt(); |
| | | takeLocationValue = dto.getTakeLocation(); |
| | |
| | | // ========== 3. 计算费用 ========== |
| | | PriceCalculateVO priceResult; |
| | | if (Constants.ZERO.equals(dto.getType())) { |
| | | // 就地寄存:计算天数 |
| | | long diffMs = takeTime.getTime() - depositTime.getTime(); |
| | | int days = (int) Math.max(1, (diffMs / (1000 * 60 * 60 * 24)) + 1); |
| | | |
| | | // 就地寄存 |
| | | CalculateLocalPriceDTO priceDTO = new CalculateLocalPriceDTO(); |
| | | priceDTO.setCityId(dto.getCityId()); |
| | | priceDTO.setEstimatedDepositDays(days); |
| | | priceDTO.setDepositStartTime(depositTime); |
| | | priceDTO.setDepositEndTime(takeTime); |
| | | priceDTO.setItems(dto.getItems()); |
| | | priceDTO.setInsured(dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0); |
| | | priceDTO.setDeclaredAmount(dto.getDeclaredAmount()); |
| | | priceResult = calculateLocalPrice(priceDTO); |
| | | } else { |
| | |
| | | priceDTO.setToLat(takeLat); |
| | | priceDTO.setToLgt(takeLgt); |
| | | priceDTO.setItems(dto.getItems()); |
| | | priceDTO.setInsured(dto.getDeclaredAmount() != null && dto.getDeclaredAmount().compareTo(BigDecimal.ZERO) > 0); |
| | | priceDTO.setDeclaredAmount(dto.getDeclaredAmount()); |
| | | priceDTO.setUrgent(Constants.ONE.equals(dto.getIsUrgent())); |
| | | priceResult = calculateRemotePrice(priceDTO); |
| | |
| | | |
| | | // 物品信息 |
| | | orders.setGoodType(dto.getGoodType()); |
| | | orders.setGoodLevel(goodTypeCategory.getRelationId()); |
| | | // 拼接物品信息:物品类型名称、尺寸名称*数量(数组字符串) |
| | | List<String> goodsParts = new ArrayList<>(); |
| | | for (ItemPriceVO itemVO : priceResult.getItemList()) { |
| | |
| | | if (member == null || StringUtils.isBlank(member.getOpenid())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付"); |
| | | } |
| | | PayResponse payResponse = wxPay(orders, member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER); |
| | | PayResponse payResponse = wxPayV3(orders.getOutTradeNo(), orders.getTotalAmount(), orders.getId(), |
| | | member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER); |
| | | payResponse.setLockKey(lockKey); |
| | | return payResponse; |
| | | } |
| | |
| | | if (member == null || StringUtils.isBlank(member.getOpenid())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付"); |
| | | } |
| | | return wxPay(orders, member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER); |
| | | return wxPayV3(orders.getOutTradeNo(), orders.getTotalAmount(), orders.getId(), |
| | | member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER); |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 唤起微信支付V3 |
| | | * |
| | | * @param outTradeNo 商户订单号 |
| | | * @param totalCents 支付金额(分) |
| | | * @param orderId 订单主键 |
| | | * @param openid 用户微信openid |
| | | * @param ordersAttach 订单支付类型 |
| | | * @return PayResponse 包含微信调起参数和订单主键 |
| | | */ |
| | | private PayResponse wxPayV3(String outTradeNo, Long totalCents, Integer orderId, |
| | | String openid, Constants.OrdersAttach ordersAttach) { |
| | | Map<String, String> payParams = wxPayV3Service.createOrder( |
| | | outTradeNo, |
| | | ordersAttach.getName(), |
| | | totalCents != null ? totalCents : 0L, |
| | | openid, |
| | | wxPayProperties.getV3NotifyUrl(), |
| | | ordersAttach.getKey() |
| | | ); |
| | | |
| | | PayResponse payResponse = new PayResponse(); |
| | | payResponse.setResponse(payParams); |
| | | payResponse.setOrderId(orderId); |
| | | return payResponse; |
| | | } |
| | | |
| | | |
| | | |
| | |
| | | // 寄件门店占比:fieldA=0(企业寄)/1(个人寄) |
| | | int depositFieldA = Constants.equalsInteger(depositShop.getCompanyType(), Constants.ONE) ? Constants.ZERO : Constants.ONE; |
| | | BigDecimal depositShopRata = getRevenueShareRata(cityId, depositFieldA); |
| | | // 取件门店占比:fieldA=2(企业取)/3(个人取) |
| | | int takeFieldA = Constants.equalsInteger(takeShop.getCompanyType(), Constants.ONE) ? Constants.TWO : Constants.THREE; |
| | | BigDecimal takeShopRata = getRevenueShareRata(cityId, takeFieldA); |
| | | // 取件门店占比:无取件门店时比例为0 |
| | | BigDecimal takeShopRata = BigDecimal.ZERO; |
| | | if (takeShop != null) { |
| | | int takeFieldA = Constants.equalsInteger(takeShop.getCompanyType(), Constants.ONE) ? Constants.TWO : Constants.THREE; |
| | | takeShopRata = getRevenueShareRata(cityId, takeFieldA); |
| | | } |
| | | |
| | | // 计算薪酬(分):totalAmount 为分,rata 为比例值(如 0.15 表示 15%) |
| | | long driverFee = new BigDecimal(totalAmount).multiply(driverRata).longValue(); |
| | | long depositShopFee = new BigDecimal(totalAmount).multiply(depositShopRata).longValue(); |
| | | long takeShopFee = totalAmount - driverFee - depositShopFee; |
| | | long takeShopFee = new BigDecimal(totalAmount).multiply(takeShopRata).longValue(); |
| | | |
| | | orders.setDriverFee(driverFee); |
| | | orders.setDepositShopFee(depositShopFee); |
| | |
| | | refund.setCreateTime(now); |
| | | refund.setDeleted(Constants.ZERO); |
| | | |
| | | // 调用微信退款,全额退款 |
| | | String refundCode = wxMiniUtilService.wxRefund(order.getOutTradeNo(), order.getPayAmount(), order.getPayAmount()); |
| | | refund.setRefundCode(refundCode); |
| | | refund.setRefundTime(new Date()); |
| | | // 调用微信退款V3,全额退款 |
| | | Refund refundResult = wxPayV3Service.refund(order.getOutTradeNo(), order.getPayAmount(), order.getPayAmount(), |
| | | "订单退款", wxPayProperties.getV3RefundNotifyUrl()); |
| | | refund.setRefundCode(refundResult.getOutRefundNo()); |
| | | refund.setStatus(Constants.ZERO); // 退款中 |
| | | ordersRefundMapper.insert(refund); |
| | | |
| | | order.setStatus(Constants.OrderStatus.cancelled.getStatus()); |
| | |
| | | ordersMapper.updateById(order); |
| | | |
| | | saveCancelLog(order, "会员取消订单(待寄存,全额退款)", reason, memberId); |
| | | // 通知会员:退款中 |
| | | sendOrderNotice(memberId, Constants.MemberOrderNotify.REFUNDING, orderId, |
| | | "orderNo", order.getCode()); |
| | | return; |
| | | } |
| | | |
| | |
| | | order.setCancelTime(now); |
| | | ordersMapper.updateById(order); |
| | | saveCancelLog(order, "会员申请取消订单(已寄存/已接单)", reason, memberId); |
| | | // 通知存件门店:退款申请 |
| | | if (order.getDepositShopId() != null) { |
| | | sendShopNotice(order.getDepositShopId(), Constants.ShopOrderNotify.REFUNDING, orderId, |
| | | "orderNo", order.getCode()); |
| | | } |
| | | return; |
| | | } |
| | | |
| | |
| | | orderLogService.create(log); |
| | | } |
| | | |
| | | /** |
| | | * 发送订单站内信通知 |
| | | */ |
| | | private void sendOrderNotice(Integer memberId, Constants.MemberOrderNotify notify, Integer orderId, String... params) { |
| | | Notice notice = new Notice(); |
| | | notice.setUserType(0); // 0=会员 |
| | | notice.setUserId(memberId); |
| | | notice.setTitle(notify.getTitle()); |
| | | notice.setContent(notify.format(params)); |
| | | notice.setObjId(orderId); |
| | | notice.setObjType(0); // 0=订单 |
| | | notice.setStatus(0); // 0=未读 |
| | | notice.setIsdeleted(Constants.ZERO); |
| | | notice.setCreateDate(new Date()); |
| | | noticeService.create(notice); |
| | | } |
| | | |
| | | /** |
| | | * 发送门店站内信通知 |
| | | */ |
| | | private void sendShopNotice(Integer shopId, Constants.ShopOrderNotify notify, Integer orderId, String... params) { |
| | | Notice notice = new Notice(); |
| | | notice.setUserType(2); // 2=门店 |
| | | notice.setUserId(shopId); |
| | | notice.setTitle(notify.getTitle()); |
| | | notice.setContent(notify.format(params)); |
| | | notice.setObjId(orderId); |
| | | notice.setObjType(0); // 0=订单 |
| | | notice.setStatus(0); // 0=未读 |
| | | notice.setIsdeleted(Constants.ZERO); |
| | | notice.setCreateDate(new Date()); |
| | | noticeService.create(notice); |
| | | } |
| | | |
| | | /** |
| | | * 通知存件门店和取件门店(订单完成/评价等) |
| | | */ |
| | | private void notifyBothShops(Orders order, Constants.ShopOrderNotify notify, String... params) { |
| | | if (order.getDepositShopId() != null) { |
| | | sendShopNotice(order.getDepositShopId(), notify, order.getId(), params); |
| | | } |
| | | if (order.getTakeShopId() != null) { |
| | | sendShopNotice(order.getTakeShopId(), notify, order.getId(), params); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = {Exception.class, BusinessException.class}) |
| | | public void handleStorageOrderPayNotify(String outTradeNo, String wxTradeNo) { |
| | |
| | | order.setUpdateTime(now); |
| | | // 生成会员核销码 |
| | | order.setMemberVerifyCode(generateVerifyCode()); |
| | | // 异地寄存:计算预计送达时间 |
| | | if (Constants.ONE.equals(order.getType()) |
| | | && order.getDepositLat() != null && order.getDepositLgt() != null |
| | | && order.getTakeLat() != null && order.getTakeLgt() != null) { |
| | | EstimatedDeliveryResultVO deliveryResult = calculateEstimatedDelivery( |
| | | Integer.valueOf(order.getCityId()), |
| | | order.getDepositLat().doubleValue(), order.getDepositLgt().doubleValue(), |
| | | order.getTakeLat().doubleValue(), order.getTakeLgt().doubleValue()); |
| | | // isUrgent: 0=标速达; 1=极速达 |
| | | BigDecimal hours = Constants.ONE.equals(order.getIsUrgent()) |
| | | ? deliveryResult.getExpressHours() |
| | | : deliveryResult.getStandardHours(); |
| | | if (hours != null) { |
| | | long millis = hours.multiply(new BigDecimal("3600000")) |
| | | .setScale(0, RoundingMode.HALF_UP).longValue(); |
| | | order.setEstimatedDeliveryTime(new Date(now.getTime() + millis)); |
| | | } |
| | | } |
| | | ordersMapper.updateById(order); |
| | | |
| | | // 通知会员:订单待核验 |
| | | sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_VERIFY, order.getId(), |
| | | "orderNo", order.getCode(), |
| | | "storeCode", order.getMemberVerifyCode()); |
| | | |
| | | // 就地寄存订单:通知存件门店待核验 |
| | | if (Constants.ZERO.equals(order.getType()) && order.getDepositShopId() != null) { |
| | | sendShopNotice(order.getDepositShopId(), Constants.ShopOrderNotify.WAIT_VERIFY, order.getId(), |
| | | "orderNo", order.getCode()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | otherOrders.setCreateTime(now); |
| | | otherOrdersMapper.insert(otherOrders); |
| | | |
| | | // 5. 唤起微信支付 |
| | | return wxPayForOtherOrder(otherOrders, member.getOpenid(), Constants.OrdersAttach.OVERDUE_FEE); |
| | | // 5. 唤起微信支付V3 |
| | | return wxPayV3(otherOrders.getOutTradeNo(), otherOrders.getPayAccount(), otherOrders.getId(), |
| | | member.getOpenid(), Constants.OrdersAttach.OVERDUE_FEE); |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = {Exception.class, BusinessException.class}) |
| | | public PayResponse payShopDeposit(Integer shopId) { |
| | | public PayResponse payShopDeposit(Integer memberId) { |
| | | // 1. 查询门店信息 |
| | | ShopInfo shopInfo = shopInfoMapper.selectById(shopId); |
| | | ShopInfo shopInfo = shopInfoMapper.selectOne(new QueryWrapper<ShopInfo>().lambda() |
| | | .eq(ShopInfo::getRegionMemberId,memberId)); |
| | | if (shopInfo == null) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "门店不存在"); |
| | | } |
| | |
| | | otherOrders.setMemberId(shopInfo.getRegionMemberId()); |
| | | otherOrders.setPayAccount(shopInfo.getDepositAmount()); |
| | | otherOrders.setPayStatus(Constants.ZERO); |
| | | otherOrders.setCode("SD" + new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(now) + shopId); |
| | | otherOrders.setCode("SD" + new java.text.SimpleDateFormat("yyyyMMddHHmmss").format(now) + shopInfo.getId()); |
| | | otherOrders.setOutTradeNo(outTradeNo); |
| | | otherOrders.setDeleted(Constants.ZERO); |
| | | otherOrders.setCreateTime(now); |
| | | otherOrdersMapper.insert(otherOrders); |
| | | |
| | | // 5. 唤起微信支付 |
| | | return wxPayForOtherOrder(otherOrders, member.getOpenid(), Constants.OrdersAttach.SHOP_DEPOSIT); |
| | | // 5. 唤起微信支付V3 |
| | | return wxPayV3(otherOrders.getOutTradeNo(), otherOrders.getPayAccount(), otherOrders.getId(), |
| | | member.getOpenid(), Constants.OrdersAttach.SHOP_DEPOSIT); |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 通知相关门店:订单已结算 |
| | | notifyBothShops(order, Constants.ShopOrderNotify.SETTLED, |
| | | "orderNo", order.getCode(), |
| | | "amount", String.valueOf(Constants.getFormatMoney( |
| | | order.getTotalAmount() != null ? order.getTotalAmount() : 0L))); |
| | | } |
| | | } |
| | | |
| | |
| | | if (isRemote && order.getAcceptDriver() != null) { |
| | | updateTargetScore(Constants.THREE, order.getAcceptDriver()); |
| | | } |
| | | |
| | | // 通知会员:订单已评价 |
| | | sendOrderNotice(memberId, Constants.MemberOrderNotify.EVALUATED, order.getId(), |
| | | "orderNo", order.getCode()); |
| | | |
| | | // 通知存件门店和取件门店:订单已评价 |
| | | notifyBothShops(order, Constants.ShopOrderNotify.EVALUATED, |
| | | "orderNo", order.getCode()); |
| | | } |
| | | |
| | | /** |
| | |
| | | saveVerifyImages(order.getId(), images, Constants.FileType.ORDER_DEPOSIT.getKey(), shopId); |
| | | // 记录订单日志 |
| | | saveShopVerifyLog(order, "门店确认寄存", "门店【" + shopName + "】确认寄存", remark, shopId); |
| | | // 通知会员:门店核销成功 |
| | | if (Constants.equalsInteger(order.getType(), Constants.ONE)) { |
| | | // 异地寄存 → 待抢单 |
| | | sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_GRAB, order.getId(), |
| | | "orderNo", order.getCode()); |
| | | } else { |
| | | // 就地寄存 → 待取件提醒 |
| | | sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.WAIT_PICKUP_REMIND, order.getId(), |
| | | "orderNo", order.getCode()); |
| | | } |
| | | } else if (Constants.equalsInteger(status, Constants.OrderStatus.arrived.getStatus())) { |
| | | // 异地寄存 + 无取件门店 → 无法核销(客户自取,无门店操作) |
| | | if (Constants.equalsInteger(order.getType(), Constants.ONE) && order.getTakeShopId() == null) { |
| | |
| | | generateRevenueRecords(order.getId()); |
| | | // 记录订单日志 |
| | | saveShopVerifyLog(order, "门店确认取件", "门店【" + shopName + "】确认取件,订单完成", remark, shopId); |
| | | // 通知会员:订单已完成 |
| | | sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.FINISHED, order.getId(), |
| | | "orderNo", order.getCode()); |
| | | // 通知存件门店和取件门店:订单已完成 |
| | | String settleDays = operationConfigBiz.getConfig().getSettlementDate(); |
| | | notifyBothShops(order, Constants.ShopOrderNotify.FINISHED, |
| | | "orderNo", order.getCode(), |
| | | "settleDays", settleDays != null ? settleDays : "7"); |
| | | } else { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许核销"); |
| | | } |
| | |
| | | refundRecord.setDeleted(Constants.ZERO); |
| | | ordersRefundMapper.insert(refundRecord); |
| | | |
| | | // 调用微信退款(放在最后,确保前置操作全部成功) |
| | | String refundCode = wxMiniUtilService.wxRefund( |
| | | order.getOutTradeNo(), order.getPayAmount(), order.getRefundAmount()); |
| | | // 调用微信退款V3(放在最后,确保前置操作全部成功) |
| | | Refund refundResult = wxPayV3Service.refund( |
| | | order.getOutTradeNo(), order.getPayAmount(), order.getRefundAmount(), |
| | | "订单退款", wxPayProperties.getV3RefundNotifyUrl()); |
| | | |
| | | // 退款成功后回填退款单号和时间 |
| | | refundRecord.setRefundCode(refundCode); |
| | | refundRecord.setRefundTime(new Date()); |
| | | // 退款成功后回填退款单号,标记退款中 |
| | | refundRecord.setRefundCode(refundResult.getOutRefundNo()); |
| | | refundRecord.setStatus(Constants.ZERO); // 退款中 |
| | | ordersRefundMapper.updateById(refundRecord); |
| | | } |
| | | |
| | |
| | | logInfo += ",退款" + Constants.getFormatMoney(order.getRefundAmount()) + "元"; |
| | | } |
| | | saveShopVerifyLog(order, "门店确认出库", logInfo, remark, shopId); |
| | | // 通知会员:订单已完成 |
| | | sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.FINISHED, order.getId(), |
| | | "orderNo", order.getCode()); |
| | | // 通知存件门店和取件门店:订单已完成 |
| | | String settleDays = operationConfigBiz.getConfig().getSettlementDate(); |
| | | notifyBothShops(order, Constants.ShopOrderNotify.FINISHED, |
| | | "orderNo", order.getCode(), |
| | | "settleDays", settleDays != null ? settleDays : "7"); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void memberConfirmReceipt(Integer orderId, Integer memberId) { |
| | | // 1. 查询订单 |
| | | Orders order = ordersMapper.selectById(orderId); |
| | | if (order == null || Constants.equalsInteger(order.getDeleted(), Constants.ONE)) { |
| | | throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "订单不存在"); |
| | | } |
| | | // 2. 校验归属 |
| | | if (!memberId.equals(order.getMemberId())) { |
| | | throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "无权操作该订单"); |
| | | } |
| | | // 3. 校验订单类型:异地寄存 |
| | | if (!Constants.ONE.equals(order.getType())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "仅异地寄存订单可操作"); |
| | | } |
| | | // 4. 校验无取件门店 |
| | | if (order.getTakeShopId() != null) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "该订单有取件门店,需门店确认出库"); |
| | | } |
| | | // 5. 校验状态:已送达(5) |
| | | if (!Constants.equalsInteger(order.getStatus(), Constants.OrderStatus.arrived.getStatus())) { |
| | | throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "当前订单状态不允许确认收货"); |
| | | } |
| | | |
| | | // 6. 更新订单状态为已完成 |
| | | Date now = new Date(); |
| | | order.setStatus(Constants.OrderStatus.finished.getStatus()); |
| | | order.setFinishTime(now); |
| | | order.setUpdateTime(now); |
| | | ordersMapper.updateById(order); |
| | | |
| | | // 7. 生成收益记录 |
| | | calculateAndSaveOrderFees(orderId); |
| | | generateRevenueRecords(orderId); |
| | | |
| | | // 通知会员:订单已完成 |
| | | sendOrderNotice(memberId, Constants.MemberOrderNotify.FINISHED, orderId, |
| | | "orderNo", order.getCode()); |
| | | // 通知存件门店和取件门店:订单已完成 |
| | | String settleDays = operationConfigBiz.getConfig().getSettlementDate(); |
| | | notifyBothShops(order, Constants.ShopOrderNotify.FINISHED, |
| | | "orderNo", order.getCode(), |
| | | "settleDays", settleDays != null ? settleDays : "7"); |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | // 保存附件(obj_type=3 门店入库图片,最多3张) |
| | | saveVerifyImages(order.getId(), images, Constants.FileType.ORDER_TAKE.getKey(), driverId); |
| | | |
| | | // 通知会员:订单已送达 |
| | | String destination = order.getTakeShopAddress() != null ? order.getTakeShopAddress() : ""; |
| | | if (order.getMemberVerifyCode() != null) { |
| | | sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.ARRIVED_HAS_SHOP, order.getId(), |
| | | "orderNo", order.getCode(), |
| | | "destination", destination, |
| | | "pickupCode", order.getMemberVerifyCode()); |
| | | } else { |
| | | sendOrderNotice(order.getMemberId(), Constants.MemberOrderNotify.ARRIVED_NO_SHOP, order.getId(), |
| | | "orderNo", order.getCode(), |
| | | "destination", destination); |
| | | } |
| | | |
| | | // 通知取件门店:订单已送达 |
| | | if (order.getTakeShopId() != null) { |
| | | sendShopNotice(order.getTakeShopId(), Constants.ShopOrderNotify.ARRIVED, order.getId(), |
| | | "orderNo", order.getCode(), |
| | | "destination", destination); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | return Math.max(days, 0); |
| | | } |
| | | |
| | | @Override |
| | | public EstimatedDeliveryResultVO calculateEstimatedDelivery(Integer cityId, |
| | | Double fromLat, Double fromLng, |
| | | Double toLat, Double toLng) { |
| | | // 腾讯地图距离矩阵API计算实际距离 |
| | | String from = fromLat + "," + fromLng; |
| | | String to = toLat + "," + toLng; |
| | | JSONObject distanceResult = MapUtil.direction("driving", from, to); |
| | | |
| | | // 获取距离(米),转公里 |
| | | int distanceMeters = distanceResult.getIntValue("distance"); |
| | | BigDecimal distanceKm = new BigDecimal(distanceMeters) |
| | | .divide(new BigDecimal("1000"), 2, RoundingMode.HALF_UP); |
| | | |
| | | // 根据pricing_rule type=2 计算 标速达(1) 和 极速达(2) 时效 |
| | | BigDecimal standardTime = pricingRuleService.calculateEstimatedTime(cityId, 1, distanceKm); |
| | | BigDecimal expressTime = pricingRuleService.calculateEstimatedTime(cityId, 2, distanceKm); |
| | | |
| | | EstimatedDeliveryResultVO vo = new EstimatedDeliveryResultVO(); |
| | | vo.setDistanceKm(distanceKm); |
| | | vo.setStandardHours(standardTime); |
| | | vo.setExpressHours(expressTime); |
| | | return vo; |
| | | } |
| | | |
| | | } |