| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408 |
- package com.haha.service.impl;
- import com.alibaba.fastjson2.JSON;
- import com.alibaba.fastjson2.JSONArray;
- import com.alibaba.fastjson2.JSONObject;
- import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
- import com.baomidou.mybatisplus.core.metadata.IPage;
- import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import com.haha.common.constant.OrderConstants;
- import com.haha.common.enums.OrderStatus;
- import com.haha.common.enums.PayScoreState;
- import com.haha.common.enums.PayStatus;
- import com.haha.common.enums.PaymentChannel;
- import com.haha.common.config.CommonConfig;
- import com.haha.common.enums.UserTag;
- import com.haha.common.utils.EntityLabelUtils;
- import com.haha.common.utils.OrderUtils;
- import com.haha.common.vo.OrderItemVO;
- import com.haha.common.vo.OrderStatisticsVO;
- import com.haha.common.vo.OrderVO;
- import com.haha.entity.Device;
- import com.haha.entity.Order;
- import com.haha.entity.OrderGoods;
- import com.haha.entity.Shop;
- import com.haha.entity.User;
- import com.haha.mapper.DeviceMapper;
- import com.haha.mapper.OrderGoodsMapper;
- import com.haha.mapper.OrderMapper;
- import com.haha.mapper.ShopMapper;
- import com.haha.mapper.UserMapper;
- import com.haha.service.UserTagService;
- import com.haha.service.OrderGoodsService;
- import com.haha.service.RefundService;
- import com.haha.service.OrderService;
- import com.haha.service.InviteActivityService;
- import com.haha.service.payment.PaymentService;
- import com.haha.service.payment.RefundResult;
- import com.haha.service.payment.payscore.PayScoreCreateRequest;
- import com.haha.service.payment.payscore.PayScoreResult;
- import com.haha.service.payment.payscore.PayScoreService;
- import lombok.RequiredArgsConstructor;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Lazy;
- import org.springframework.scheduling.annotation.Async;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import java.math.BigDecimal;
- import java.math.RoundingMode;
- import java.time.LocalDate;
- import java.time.LocalDateTime;
- import java.time.LocalTime;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Objects;
- import java.util.stream.Collectors;
- @Slf4j
- @Service
- @RequiredArgsConstructor
- public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
- private final DeviceMapper deviceMapper;
- private final ShopMapper shopMapper;
- private final UserMapper userMapper;
- private final CommonConfig commonConfig;
- private final UserTagService userTagService;
- @Autowired
- @Lazy
- private PaymentService paymentService;
- @Autowired
- @Lazy
- private PayScoreService payScoreService;
- @Autowired
- @Lazy
- private RefundService refundService;
- @Autowired
- @Lazy
- private OrderGoodsService orderGoodsService;
- @Autowired
- @Lazy
- private InviteActivityService inviteActivityService;
- @Autowired(required = false)
- private com.haha.sdk.HahaClient hahaClient;
- @Autowired
- @Lazy
- private OrderGoodsMapper orderGoodsMapper;
- @Autowired
- @Lazy
- private com.haha.service.ProductService productService;
- @Autowired
- @Lazy
- private com.haha.service.OrderInventoryService orderInventoryService;
- @Override
- public IPage<Order> getPage(int page, int pageSize, String orderNo, String deviceId,
- String payStatus, Integer status, String startDate, String endDate,
- List<Integer> statusList) {
- return getPage(page, pageSize, orderNo, deviceId, payStatus, status, startDate, endDate, statusList, null);
- }
- @Override
- public IPage<Order> getPage(int page, int pageSize, String orderNo, String deviceId,
- String payStatus, Integer status, String startDate, String endDate,
- List<Integer> statusList, String phone) {
- // 确保分页参数有效(MyBatis-Plus 要求页码从 1 开始)
- if (page < 1) {
- page = 1;
- }
- if (pageSize < 1 || pageSize > 100) {
- pageSize = 10; // 默认每页 10 条,最大 100 条
- }
- log.info("订单分页查询:page={}, pageSize={}, orderNo={}, deviceId={}, phone={}", page, pageSize, orderNo, deviceId, phone);
- LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
- // 手机号查询:先根据手机号查出用户ID,再按用户ID过滤订单
- List<Long> userIdsByPhone = null;
- if (phone != null && !phone.isEmpty()) {
- LambdaQueryWrapper<User> userWrapper = new LambdaQueryWrapper<>();
- userWrapper.like(User::getPhone, phone);
- List<User> users = userMapper.selectList(userWrapper);
- if (users != null && !users.isEmpty()) {
- userIdsByPhone = users.stream().map(User::getId).collect(Collectors.toList());
- wrapper.in(Order::getUserId, userIdsByPhone);
- } else {
- // 没有匹配到用户,返回空结果
- return new Page<>(page, pageSize);
- }
- }
- // 构建其他查询条件
- wrapper.like(orderNo != null && !orderNo.isEmpty(), Order::getOrderNo, orderNo)
- .like(deviceId != null && !deviceId.isEmpty(), Order::getDeviceId, deviceId)
- .eq(payStatus != null && !payStatus.isEmpty(), Order::getPayStatus, payStatus)
- .eq(status != null, Order::getStatus, status)
- .in(statusList != null && !statusList.isEmpty(), Order::getStatus, statusList);
- // 时间范围查询
- if (startDate != null && !startDate.isEmpty()) {
- LocalDateTime startDateTime = LocalDate.parse(startDate).atStartOfDay();
- wrapper.ge(Order::getCreateTime, startDateTime);
- }
- if (endDate != null && !endDate.isEmpty()) {
- LocalDateTime endDateTime = LocalDate.parse(endDate).atTime(LocalTime.MAX);
- wrapper.le(Order::getCreateTime, endDateTime);
- }
- // 按创建时间倒序
- wrapper.orderByDesc(Order::getCreateTime);
- // 分页查询
- IPage<Order> orderPage = this.page(new Page<>(page, pageSize), wrapper);
- // 批量填充标签字段和关联信息(含用户消费标签)
- fillOrderLabels(orderPage.getRecords());
- return orderPage;
- }
- @Override
- public Order getDetailById(Long id) {
- Order order = this.getById(id);
- if (order == null) {
- return null;
- }
- fillOrderLabels(List.of(order));
- return order;
- }
- @Override
- public OrderVO getDetailVOById(Long id) {
- Order order = this.getById(id);
- if (order == null) {
- return null;
- }
- // 填充关联标签信息(门店名称、设备名称等)
- fillOrderLabels(List.of(order));
- OrderVO vo = new OrderVO();
- vo.setId(order.getId());
- vo.setOrderNo(order.getOrderNo());
- vo.setOrderName(order.getOrderName());
- vo.setOutTradeNo(order.getOutTradeNo());
- vo.setActivityId(order.getActivityId());
- vo.setDeviceId(order.getDeviceId());
- vo.setTotalAmount(order.getTotalAmount());
- vo.setDiscountAmount(order.getDiscountAmount());
- vo.setPaidAmount(order.getPaidAmount());
- vo.setPayStatus(order.getPayStatus());
- vo.setPayStatusLabel(getPayStatusLabel(order.getPayStatus()));
- vo.setStatus(order.getStatus());
- vo.setStatusText(getStatusLabel(order.getStatus()));
- vo.setCreateTime(order.getCreateTime());
- vo.setPayTime(order.getPayTime());
- vo.setVideoUrl(order.getVideoUrl());
- vo.setConfidence(order.getConfidence());
- vo.setShopName(order.getStoreName() != null ? order.getStoreName() : order.getShopName());
- vo.setDeviceName(order.getDeviceName());
- vo.setPayType(order.getPayType());
- List<OrderItemVO> products = orderGoodsService.getVOByOrderId(id);
- // 填充每个商品的已退款数量
- if (refundService != null && !products.isEmpty()) {
- Map<Long, Integer> refundedQtyMap = refundService.getRefundedQuantityMap(id);
- for (OrderItemVO p : products) {
- p.setRefundedQuantity(refundedQtyMap.getOrDefault(p.getId(), 0));
- }
- }
- vo.setProducts(products);
- // 填充用户消费标签
- if (order.getUserId() != null) {
- Map<Long, UserTag> tagMap = userTagService.batchComputeUserTags(List.of(order.getUserId()));
- UserTag tag = tagMap.get(order.getUserId());
- if (tag != null) {
- vo.setUserTag(tag.getCode());
- vo.setUserTagLabel(tag.getDescription());
- }
- }
- return vo;
- }
- private String getPayStatusLabel(String payStatus) {
- if (payStatus == null) {
- return "未知";
- }
- switch (payStatus) {
- case OrderConstants.PAY_STATUS_UNPAID:
- return "未支付";
- case OrderConstants.PAY_STATUS_PAID:
- return "已支付";
- case OrderConstants.PAY_STATUS_PARTIAL_REFUND:
- return "部分退款";
- case OrderConstants.PAY_STATUS_REFUND:
- return "全额退款";
- default:
- return "未知";
- }
- }
- private String getStatusLabel(Integer status) {
- if (status == null) {
- return "未知";
- }
- switch (status) {
- case OrderConstants.STATUS_PENDING_PAYMENT:
- return "待支付";
- case OrderConstants.STATUS_COMPLETED:
- return "已完成";
- case OrderConstants.STATUS_CANCELLED:
- return "已取消";
- case OrderConstants.STATUS_CLOSED:
- return "已关闭";
- default:
- return "未知";
- }
- }
- @Override
- public OrderStatisticsVO getStatistics(String startDate, String endDate) {
- LocalDateTime startDateTime = null;
- LocalDateTime endDateTime = null;
- if (startDate != null && !startDate.isEmpty()) {
- startDateTime = LocalDate.parse(startDate).atStartOfDay();
- }
- if (endDate != null && !endDate.isEmpty()) {
- endDateTime = LocalDate.parse(endDate).atTime(LocalTime.MAX);
- }
- // 时间范围内的订单总数
- Long totalOrders = baseMapper.countByDateRange(startDateTime, endDateTime);
- // 时间范围内的已完成订单数
- Long completedOrders = baseMapper.countCompletedByDateRange(startDateTime, endDateTime);
- // 时间范围内的已退款订单数
- Long refundOrders = baseMapper.countRefundedByDateRange(startDateTime, endDateTime);
- // 时间范围内的总销售额(所有已完成订单)
- BigDecimal totalAmount = baseMapper.sumCompletedOrderAmount(startDateTime);
- if (endDateTime != null) {
- totalAmount = baseMapper.selectObjs(
- new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Order>()
- .eq(Order::getStatus, OrderConstants.STATUS_COMPLETED)
- .eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
- .ge(startDateTime != null, Order::getCreateTime, startDateTime)
- .le(endDateTime != null, Order::getCreateTime, endDateTime)
- .select(Order::getPaidAmount)
- ).stream()
- .map(obj -> obj instanceof BigDecimal ? (BigDecimal) obj : BigDecimal.ZERO)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- }
- // 按支付渠道统计金额
- List<Map<String, Object>> channelStats = baseMapper.sumAmountByChannel(startDateTime, endDateTime);
- BigDecimal wechatAmount = BigDecimal.ZERO;
- BigDecimal alipayAmount = BigDecimal.ZERO;
- BigDecimal otherAmount = BigDecimal.ZERO;
- if (channelStats != null) {
- for (Map<String, Object> stat : channelStats) {
- String channel = (String) stat.get("channel");
- BigDecimal amount = stat.get("amount") instanceof BigDecimal
- ? (BigDecimal) stat.get("amount")
- : new BigDecimal(stat.get("amount").toString());
- if (channel == null) {
- otherAmount = otherAmount.add(amount);
- continue;
- }
- String lowerChannel = channel.toLowerCase();
- if (lowerChannel.contains(PaymentChannel.WECHAT.getCode())) {
- wechatAmount = wechatAmount.add(amount);
- } else if (lowerChannel.contains(PaymentChannel.ALIPAY.getCode())) {
- alipayAmount = alipayAmount.add(amount);
- } else {
- otherAmount = otherAmount.add(amount);
- }
- }
- }
- return OrderStatisticsVO.builder()
- .totalOrders(totalOrders != null ? totalOrders : 0)
- .completedOrders(completedOrders != null ? completedOrders : 0)
- .refundOrders(refundOrders != null ? refundOrders : 0)
- .totalAmount(totalAmount != null ? totalAmount.setScale(2, RoundingMode.HALF_UP).toString() : "0.00")
- .wechatAmount(wechatAmount.setScale(2, RoundingMode.HALF_UP).toString())
- .alipayAmount(alipayAmount.setScale(2, RoundingMode.HALF_UP).toString())
- .otherAmount(otherAmount.setScale(2, RoundingMode.HALF_UP).toString())
- .build();
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public boolean refund(Long orderId, String reason) {
- // 委托给 PaymentService 处理退款
- // PaymentService.refund() 内部已经处理了:
- // - 订单状态校验
- // - 调用第三方退款 API
- // - 更新退款状态和支付状态
- RefundResult result = paymentService.refund(orderId, reason);
- return result.isSuccess();
- }
- @Override
- public List<Order> getOrderListByUserId(Long userId, Integer status) {
- if (status != null) {
- return lambdaQuery()
- .eq(Order::getUserId, userId)
- .eq(Order::getStatus, status)
- .ne(Order::getOrderType, OrderConstants.ORDER_TYPE_IN)
- .orderByDesc(Order::getId)
- .list();
- } else {
- return lambdaQuery()
- .eq(Order::getUserId, userId)
- .ne(Order::getOrderType, OrderConstants.ORDER_TYPE_IN)
- .orderByDesc(Order::getId)
- .list();
- }
- }
- @Override
- public boolean cancelOrder(Long orderId) {
- return lambdaUpdate().eq(Order::getId, orderId).set(Order::getStatus, OrderConstants.STATUS_CANCELLED).update();
- }
- @Override
- public boolean updateOrderStatus(Long orderId, Integer status) {
- return lambdaUpdate().eq(Order::getId, orderId).set(Order::getStatus, status).update();
- }
- @Override
- public Order getOrderByOutTradeNo(String outTradeNo) {
- return lambdaQuery().eq(Order::getOutTradeNo, outTradeNo).one();
- }
- @Override
- public Order getOrderBydeviceId(String deviceId) {
- return lambdaQuery().eq(Order::getDeviceId, deviceId).orderByDesc(Order::getCreateTime).one();
- }
- @Override
- public Order getOrderByActivityId(String activityId) {
- if (activityId == null || activityId.isEmpty()) {
- return null;
- }
- return lambdaQuery().eq(Order::getActivityId, activityId).one();
- }
- @Override
- public boolean updatePayStatus(String orderNo, String payStatus) {
- boolean updated = lambdaUpdate().eq(Order::getOrderNo, orderNo).set(Order::getPayStatus, payStatus).update();
- if (updated && (OrderConstants.PAY_STATUS_PAID.equals(payStatus)
- || OrderConstants.PAY_STATUS_REFUND.equals(payStatus)
- || OrderConstants.PAY_STATUS_PARTIAL_REFUND.equals(payStatus))) {
- notifyHahaOrderStatusChanged(orderNo);
- }
- return updated;
- }
- @Override
- public void notifyHahaOrderStatusChanged(String orderNo) {
- if (hahaClient == null || orderNo == null || orderNo.isEmpty()) {
- return;
- }
- try {
- // 优先使用哈哈平台的 orderId,本地 orderNo 作为 fallback
- Order order = lambdaQuery().eq(Order::getOrderNo, orderNo).one();
- String hahaRef = (order != null && order.getHahaOrderId() != null && !order.getHahaOrderId().isEmpty())
- ? order.getHahaOrderId() : orderNo;
- hahaClient.getOrderApi().setPayStatus(hahaRef);
- log.info("订单状态已同步到哈哈平台 - hahaRef: {}, localOrderNo: {}", hahaRef, orderNo);
- } catch (Exception e) {
- log.error("同步订单状态到哈哈平台失败 - orderNo: {}", orderNo, e);
- }
- }
- /**
- * 从AI识别结果创建订单
- *
- * 业务流程:
- * 1. 幂等性检查:防止重复创建订单
- * 2. 保存商品识别信息(此时不计算金额,因为AI识别结果不包含价格)
- * 3. 解析视频URL等资源信息
- * 4. 创建订单记录,状态为待支付,金额为0(等待订单回调时由设备端提供实付金额)
- *
- * 注意:
- * - AI识别结果(sku_list)只包含商品编码(code)和数量(quantity),不包含价格
- * - 商品价格在订单回调时由设备端计算并传来
- * - 此方法只创建订单框架,实际金额在handleOrderCallback中更新
- *
- * @param activityId 活动ID(设备开门会话ID)
- * @param deviceId 设备ID
- * @param userId 用户ID
- * @param skuListStr 商品列表JSON字符串,格式:[{"code":"C001","quantity":2}](不包含price)
- * @param resourceInfoStr 资源信息JSON字符串,包含视频URL等
- * @param confidence AI识别置信度
- * @return 创建的订单对象,失败返回null
- */
- @Override
- @Transactional
- public Order createOrderFromRecognition(String activityId, String deviceId, String userId,
- String skuListStr, String resourceInfoStr, BigDecimal confidence) {
- // 【步骤1】幂等性检查:如果该activityId已存在订单,直接返回,避免重复创建
- if (activityId != null && !activityId.isEmpty()) {
- Order existingOrder = getOrderByActivityId(activityId);
- if (existingOrder != null) {
- log.info("订单已存在,跳过创建 - activityId: {}, orderId: {}", activityId, existingOrder.getId());
- return existingOrder;
- }
- }
- // 【步骤2】解析视频URL等资源信息
- String videoUrl = null;
- if (resourceInfoStr != null && !resourceInfoStr.isEmpty()) {
- JSONObject resourceInfo = JSON.parseObject(resourceInfoStr);
- videoUrl = resourceInfo.getString("video_url");
- }
- // 【步骤3】创建订单记录
- // 注意:
- // 1. AI识别结果不包含商品价格,只包含商品编码和数量
- // 2. 此时订单金额设为0(或NULL),等待设备端回调时更新为实付金额
- // 3. 设备端会根据商品编码查询价格,并计算优惠后得到最终实付金额
- if (userId != null && deviceId != null) {
- Order order = new Order();
- order.setOrderNo(OrderUtils.getOrderNo());
- order.setActivityId(activityId); // 开门会话ID
- order.setUserId(Long.parseLong(userId)); // 用户ID
- order.setDeviceId(deviceId); // 设备ID
- // 下单时写入门店信息,避免后续设备换门店导致历史订单门店失真
- try {
- Device device = deviceMapper.selectByDeviceId(deviceId);
- if (device != null && device.getShopId() != null) {
- order.setShopId(device.getShopId());
- Shop shop = shopMapper.selectById(device.getShopId());
- if (shop != null) {
- order.setStoreName(shop.getName());
- order.setShopName(shop.getName());
- }
- }
- } catch (Exception e) {
- log.warn("写入订单门店信息失败: deviceId={}", deviceId, e);
- }
- order.setTotalAmount(BigDecimal.ZERO); // 初始金额为0,等待回调更新
- order.setItems(skuListStr); // 商品列表JSON(包含code和quantity)
- order.setStatus(OrderConstants.STATUS_PENDING_PAYMENT); // 订单状态:待支付
- order.setPayStatus(OrderConstants.PAY_STATUS_UNPAID); // 支付状态:未支付
- order.setCreateTime(LocalDateTime.now()); // 创建时间
- if (videoUrl != null) {
- order.setVideoUrl(videoUrl); // 购物视频URL
- }
- if (confidence != null) {
- order.setConfidence(confidence); // AI识别置信度
- }
- boolean saved = this.save(order);
- if (saved) {
- log.info("订单创建成功 - 订单ID: {}, 用户: {}, 等待回调更新金额", order.getId(), userId);
- return order;
- } else {
- log.error("订单创建失败 - 用户: {}, 设备: {}", userId, deviceId);
- return null;
- }
- } else {
- log.warn("订单创建跳过 - 缺少必要参数: userId={}, deviceId={}", userId, deviceId);
- return null;
- }
- }
- /**
- * 创建订单(带支付分预授权信息),支付分字段在 INSERT 时直接写入
- */
- public Order createOrderFromRecognition(String activityId, String deviceId, String userId,
- String skuListStr, String resourceInfoStr, BigDecimal confidence,
- String payScoreOrderId, String payScoreState, String serviceId) {
- Order order = createOrderFromRecognition(activityId, deviceId, userId, skuListStr, resourceInfoStr, confidence);
- if (order != null && payScoreOrderId != null) {
- order.setPayScoreOrderId(payScoreOrderId);
- order.setPayScoreState(payScoreState != null ? payScoreState : PayScoreState.CREATED.getCode());
- order.setServiceId(serviceId);
- order.setPayChannel(PaymentChannel.WECHAT_PAYSCORE.getCode());
- order.setPayType("微信支付分");
- this.updateById(order);
- }
- return order;
- }
- /**
- * 批量填充订单标签字段(含用户消费标签)
- * 优化:使用批量SQL预计算用户标签,避免N+1查询
- */
- private void fillOrderLabels(List<Order> orders) {
- if (orders == null || orders.isEmpty()) {
- return;
- }
- // 1. 批量计算用户消费标签
- List<Long> userIds = orders.stream()
- .map(Order::getUserId)
- .filter(Objects::nonNull)
- .distinct()
- .collect(Collectors.toList());
- Map<Long, UserTag> userTagMap = userTagService.batchComputeUserTags(userIds);
- // 2. 批量查询用户信息(手机号)
- Map<Long, User> userMap = new HashMap<>();
- if (!userIds.isEmpty()) {
- List<User> users = userMapper.selectBatchIds(userIds);
- if (users != null) {
- userMap = users.stream().collect(Collectors.toMap(User::getId, u -> u, (a, b) -> a));
- }
- }
- // 3. 批量查询设备信息,避免循环内逐条查询(N+1)
- List<String> deviceIds = orders.stream()
- .map(Order::getDeviceId)
- .filter(id -> id != null && !id.isEmpty())
- .distinct()
- .collect(Collectors.toList());
- Map<String, Device> deviceMap = new HashMap<>();
- if (!deviceIds.isEmpty()) {
- List<Device> devices = deviceMapper.selectList(
- new LambdaQueryWrapper<Device>().in(Device::getDeviceId, deviceIds));
- if (devices != null) {
- deviceMap = devices.stream().collect(Collectors.toMap(Device::getDeviceId, d -> d, (a, b) -> a));
- }
- }
- // 4. 批量查询门店信息(用于历史订单未写入 shopId 时回退查询)
- Map<Long, Shop> shopMap = new HashMap<>();
- List<Long> shopIds = deviceMap.values().stream()
- .map(Device::getShopId)
- .filter(Objects::nonNull)
- .distinct()
- .collect(Collectors.toList());
- if (!shopIds.isEmpty()) {
- List<Shop> shops = shopMapper.selectBatchIds(shopIds);
- if (shops != null) {
- shopMap = shops.stream().collect(Collectors.toMap(Shop::getId, s -> s, (a, b) -> a));
- }
- }
- // 5. 逐条填充标签(使用预加载的 Map,无额外 SQL)
- for (Order order : orders) {
- // 用户消费标签 + 手机号
- if (order.getUserId() != null) {
- UserTag tag = userTagMap.get(order.getUserId());
- if (tag != null) {
- order.setUserTag(tag.getCode());
- order.setUserTagLabel(tag.getDescription());
- }
- // 填充手机号
- User user = userMap.get(order.getUserId());
- if (user != null) {
- order.setPhone(user.getPhone());
- }
- }
- // 支付状态标签
- if (order.getPayStatus() != null) {
- var payLabel = EntityLabelUtils.getPayStatusLabel(order.getPayStatus());
- order.setPayStatusLabel(payLabel.getLabel());
- }
- // 订单状态标签
- if (order.getStatus() != null) {
- var statusLabel = EntityLabelUtils.getStatusLabel("order", order.getStatus());
- order.setStatusLabel(statusLabel.getLabel());
- }
- // 如果 payType 为空但 payChannel 有值,从 payChannel 获取描述
- if (order.getPayType() == null || order.getPayType().isEmpty()) {
- if (order.getPayChannel() != null && !order.getPayChannel().isEmpty()) {
- PaymentChannel channel = PaymentChannel.fromCode(order.getPayChannel());
- if (channel != null) {
- order.setPayType(channel.getDescription());
- }
- } else if (order.getOutTradeNo() != null && !order.getOutTradeNo().isEmpty()) {
- order.setPayType("微信支付"); // 兼容旧数据
- } else if (PayStatus.isPaid(order.getPayStatus())) {
- order.setPayType("免费");
- }
- }
- // 补充设备/门店名称(从预加载的Map取值,无额外SQL)
- if (order.getDeviceId() != null && !order.getDeviceId().isEmpty()) {
- Device device = deviceMap.get(order.getDeviceId());
- if (device != null) {
- order.setDeviceName(device.getName());
- // 历史订单未写入门店信息时,回退到 device→shop
- if (order.getShopId() == null && device.getShopId() != null) {
- order.setShopId(device.getShopId());
- Shop shop = shopMap.get(device.getShopId());
- if (shop != null) {
- order.setShopName(shop.getName());
- order.setStoreName(shop.getName());
- }
- }
- }
- }
- }
- }
- @Override
- @Transactional
- public Order createOrderWithPayScore(String activityId, String deviceId, String userId,
- String skuListStr, String resourceInfoStr, BigDecimal confidence,
- String openId) {
- Order order = createOrderFromRecognition(activityId, deviceId, userId,
- skuListStr, resourceInfoStr, confidence);
- if (order == null) {
- return null;
- }
- // 立即设置支付渠道,防止后续回调到达时 payChannel 为 null 而跳过扣费
- order.setPayChannel(PaymentChannel.WECHAT_PAYSCORE.getCode());
- order.setPayType("微信支付分");
- this.updateById(order);
- if (openId == null || openId.isEmpty()) {
- log.warn("[支付分集成] openId为空,跳过支付分服务订单创建 - orderId: {}", order.getId());
- return order;
- }
- try {
- PayScoreResult result = payScoreService.createPayScoreOrder(order.getId(), openId);
- if (result.isSuccess()) {
- log.info("[支付分集成] 支付分服务订单创建成功 - orderId: {}, outOrderNo: {}",
- order.getId(), result.getOutOrderNo());
- order.setPayScoreOrderId(result.getOutOrderNo());
- order.setPayScoreState(result.getState());
- this.updateById(order);
- } else {
- log.warn("[支付分集成] 支付分服务订单创建失败 - orderId: {}, errorCode: {}, errorMsg: {}",
- order.getId(), result.getErrorCode(), result.getErrorMsg());
- }
- } catch (Exception e) {
- log.error("[支付分集成] 支付分服务订单创建异常 - orderId: {}", order.getId(), e);
- }
- return order;
- }
- /**
- * 构建后付费项目列表(post_payments)
- * 从 order_goods 表查询已保存的商品,按微信支付分智慧零售规范构建
- * name="商品信息", description=商品名, amount=单价(元), count=数量
- */
- private List<PayScoreCreateRequest.PostPayment> buildPostPayments(Long orderId, BigDecimal totalAmount) {
- List<PayScoreCreateRequest.PostPayment> payments = new ArrayList<>();
- try {
- List<OrderGoods> goodsList = orderGoodsMapper.selectList(
- new LambdaQueryWrapper<OrderGoods>()
- .eq(OrderGoods::getOrderId, orderId));
- if (goodsList == null || goodsList.isEmpty()) {
- log.warn("未找到订单商品信息,使用订单金额构建fallback post_payments - orderId: {}", orderId);
- Order order = this.getById(orderId);
- if (order != null) {
- PayScoreCreateRequest.PostPayment payment = new PayScoreCreateRequest.PostPayment();
- payment.setName("商品信息");
- payment.setAmount(order.getTotalAmount() != null ? order.getTotalAmount() : BigDecimal.ZERO);
- payment.setDescription(order.getOrderName() != null ? order.getOrderName() : "商品");
- payment.setCount(1);
- payments.add(payment);
- }
- return payments;
- }
- for (OrderGoods goods : goodsList) {
- PayScoreCreateRequest.PostPayment payment = new PayScoreCreateRequest.PostPayment();
- payment.setName("商品信息");
- // amount 为行总金额(单价 × 数量),微信校验: total_amount = sum(post_payments[].amount)
- BigDecimal unitPrice = goods.getPrice() != null ? goods.getPrice()
- : (goods.getMoney() != null ? goods.getMoney() : BigDecimal.ZERO);
- int quantity = goods.getProductNum() != null ? goods.getProductNum() : 1;
- payment.setAmount(unitPrice.multiply(BigDecimal.valueOf(quantity)));
- payment.setDescription(goods.getProductName() != null ? goods.getProductName() : "未知商品");
- payment.setCount(1);
- payments.add(payment);
- }
- // 修正四舍五入累计误差:确保 sum(post_payments) 严格等于 totalAmount
- if (totalAmount != null && !payments.isEmpty()) {
- BigDecimal sum = payments.stream()
- .map(PayScoreCreateRequest.PostPayment::getAmount)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- BigDecimal diff = totalAmount.subtract(sum);
- if (diff.compareTo(BigDecimal.ZERO) != 0) {
- PayScoreCreateRequest.PostPayment last = payments.get(payments.size() - 1);
- last.setAmount(last.getAmount().add(diff));
- log.info("post_payments余额平账: 商品sum={}, 订单total={}, 差额={}, 调整末项amount为{}",
- sum, totalAmount, diff, last.getAmount());
- }
- }
- log.info("构建post_payments成功 - orderId: {}, 商品数量: {}", orderId, payments.size());
- } catch (Exception e) {
- log.error("构建post_payments失败 - orderId: {}", orderId, e);
- }
- return payments;
- }
- /**
- * 使用支付分完结订单(扣费)
- *
- * 业务流程:
- * 1. 验证订单是否存在
- * 2. 检查是否创建了支付分服务订单
- * 3. 调用微信支付分完结接口进行扣费
- * 4. 扣费成功后触发邀请激活逻辑(异步)
- *
- * 注意:
- * - 如果订单未使用支付分,直接更新订单状态
- * - 优惠金额已在设备端计算,totalAmount为实付金额
- *
- * @param orderId 订单ID
- * @param totalAmount 订单总金额(设备端已计算优惠后的实付金额)
- * @return 是否完结成功
- */
- @Override
- @Transactional
- public boolean completeOrderWithPayScore(Long orderId, BigDecimal totalAmount) {
- Order order = this.getById(orderId);
- if (order == null) {
- log.error("[支付分集成] 订单不存在 - orderId: {}", orderId);
- return false;
- }
- if (order.getPayScoreOrderId() == null) {
- log.warn("[支付分集成] 订单未创建支付分服务订单,直接更新状态 - orderId: {}", orderId);
- order.setTotalAmount(totalAmount);
- order.setPayStatus(PayStatus.PAID.getCode());
- order.setStatus(OrderStatus.COMPLETED.getCode());
- order.setPayTime(LocalDateTime.now());
- boolean updateResult = this.updateById(order);
- // 支付成功后扣减库存
- if (updateResult) {
- try {
- orderInventoryService.decreaseStockOnPaid(order);
- } catch (Exception e) {
- log.error("[支付分集成] 扣减库存异常 - orderNo={}", order.getOrderNo(), e);
- }
- triggerInviteActivationAsync(order.getUserId(), orderId);
- }
- return updateResult;
- }
- try {
- List<PayScoreCreateRequest.PostPayment> payments = buildPostPayments(orderId, totalAmount);
- PayScoreResult result = payScoreService.completePayScoreOrder(orderId, totalAmount, payments);
- if (result.isSuccess()) {
- log.info("[支付分集成] 支付分完结成功 - orderId: {}, state: {}", orderId, result.getState());
- // 订单完成后异步触发邀请激活逻辑
- triggerInviteActivationAsync(order.getUserId(), orderId);
- return true;
- } else {
- log.error("[支付分集成] 支付分完结失败 - orderId: {}, errorCode: {}, errorMsg: {}",
- orderId, result.getErrorCode(), result.getErrorMsg());
- return false;
- }
- } catch (Exception e) {
- log.error("[支付分集成] 支付分完结异常 - orderId: {}", orderId, e);
- return false;
- }
- }
- /**
- * 异步触发邀请活动激活
- * 当被邀请人完成首单后,自动激活邀请关系并发放奖励优惠券
- *
- * 使用 @Async 异步执行,避免阻塞订单主流程
- * 邀请激活失败不影响订单流程的正常运行
- *
- * @param userId 下单用户ID(被邀请人)
- * @param orderId 订单ID
- */
- @Async("taskExecutor")
- public void triggerInviteActivationAsync(Long userId, Long orderId) {
- try {
- log.info("[邀请激活] 收到订单完成事件,开始处理邀请激活 - orderId: {}, userId: {}", orderId, userId);
- // 调用邀请服务激活邀请记录并发放奖励
- boolean success = inviteActivityService.activateInviteRecord(userId, orderId);
- if (success) {
- log.info("[邀请激活] 邀请激活成功 - orderId: {}, userId: {}", orderId, userId);
- } else {
- log.warn("[邀请激活] 邀请激活失败或无待激活记录(可能该用户未被邀请或已激活)- orderId: {}, userId: {}", orderId, userId);
- }
- } catch (Exception e) {
- // 邀请激活异常不应影响订单流程,仅记录错误日志
- // 失败的奖励可以通过定时任务或手动补发来处理
- log.error("[邀请激活] 处理订单完成事件时发生异常 - orderId: {}, userId: {}", orderId, userId, e);
- }
- }
- // ==================== 用户端订单业务方法 ====================
- @Override
- public List<Map<String, Object>> getUserOrderListWithProducts(Long userId, Integer status) {
- List<Order> orders = getOrderListByUserId(userId, status);
- if (orders.isEmpty()) {
- return new ArrayList<>();
- }
- return buildOrderListBatch(orders);
- }
- @Override
- public List<Map<String, Object>> getUserOrderListWithProducts(Long userId, Map<String, Object> params) {
- Integer status = null;
- if (params != null && params.containsKey("status")) {
- status = Integer.valueOf(params.get("status").toString());
- }
- return getUserOrderListWithProducts(userId, status);
- }
- @Override
- public Map<String, Object> getUserOrderDetail(Long userId, Long orderId, String orderNo, String outTradeNo) {
- Order order = null;
- if (orderId != null) {
- order = this.getById(orderId);
- } else if (orderNo != null && !orderNo.isEmpty()) {
- order = this.lambdaQuery()
- .eq(Order::getOrderNo, orderNo)
- .one();
- } else if (outTradeNo != null && !outTradeNo.isEmpty()) {
- order = this.getOrderByOutTradeNo(outTradeNo);
- }
- if (order == null) {
- return null;
- }
- if (!order.getUserId().equals(userId)) {
- return null;
- }
- // IN类型订单不在用户端展示
- if (OrderConstants.ORDER_TYPE_IN.equals(order.getOrderType())) {
- return null;
- }
- // 补充设备名称;历史订单未写入门店信息时,回退到 device→shop 查询
- if (order.getDeviceId() != null && !order.getDeviceId().isEmpty()) {
- try {
- Device device = deviceMapper.selectByDeviceId(order.getDeviceId());
- if (device != null) {
- order.setDeviceName(device.getName());
- if (order.getShopId() == null && device.getShopId() != null) {
- order.setShopId(device.getShopId());
- Shop shop = shopMapper.selectById(device.getShopId());
- if (shop != null) {
- order.setShopName(shop.getName());
- order.setStoreName(shop.getName());
- }
- }
- }
- } catch (Exception e) {
- log.warn("获取订单关联门店信息失败: orderId={}, deviceId={}", order.getId(), order.getDeviceId(), e);
- }
- }
- Map<String, Object> detail = buildOrderMap(order);
- detail.put("statusText", OrderConstants.getStatusDesc(order.getStatus()));
- // 加载退款申请记录
- try {
- if (refundService != null) {
- List<com.haha.entity.Refund> refundList = refundService.getByOrderId(order.getId());
- if (refundList != null && !refundList.isEmpty()) {
- List<Map<String, Object>> refundRecords = new ArrayList<>();
- for (com.haha.entity.Refund refund : refundList) {
- Map<String, Object> record = new HashMap<>();
- record.put("id", refund.getId());
- record.put("refundNo", refund.getRefundNo());
- record.put("refundAmount", refund.getRefundAmount());
- record.put("status", refund.getStatus());
- record.put("reason", refund.getReason());
- record.put("remark", refund.getRemark());
- record.put("createTime", refund.getCreateTime());
- record.put("reviewTime", refund.getReviewTime());
- // 加载退款商品明细
- List<com.haha.entity.RefundItem> items = refundService.getRefundItems(refund.getId());
- if (items != null && !items.isEmpty()) {
- List<Map<String, Object>> itemMaps = new ArrayList<>();
- for (com.haha.entity.RefundItem item : items) {
- Map<String, Object> im = new HashMap<>();
- im.put("productName", item.getProductName());
- im.put("quantity", item.getQuantity());
- im.put("price", item.getPrice());
- im.put("refundAmount", item.getRefundAmount());
- itemMaps.add(im);
- }
- record.put("items", itemMaps);
- }
- refundRecords.add(record);
- }
- detail.put("refunds", refundRecords);
- }
- }
- } catch (Exception e) {
- log.warn("加载订单 {} 退款记录失败: {}", order.getId(), e.getMessage());
- }
- return detail;
- }
- @Override
- public Map<String, Object> getUserOrderDetail(Long userId, Map<String, Object> params) {
- Long orderId = null;
- String orderNo = null;
- String outTradeNo = null;
- if (params.containsKey("orderId")) {
- orderId = Long.valueOf(params.get("orderId").toString());
- } else if (params.containsKey("orderNo")) {
- orderNo = params.get("orderNo").toString();
- } else if (params.containsKey("outTradeNo")) {
- outTradeNo = params.get("outTradeNo").toString();
- } else {
- return null;
- }
- return getUserOrderDetail(userId, orderId, orderNo, outTradeNo);
- }
- @Override
- public boolean cancelUserOrder(Long userId, Long orderId) {
- Order order = this.getById(orderId);
- if (order == null) {
- return false;
- }
- if (!order.getUserId().equals(userId)) {
- return false;
- }
- if (order.getStatus() != OrderConstants.STATUS_PENDING_PAYMENT) {
- return false;
- }
- return cancelOrder(orderId);
- }
- @Override
- public boolean cancelUserOrder(Long userId, Map<String, Object> params) {
- if (params == null || !params.containsKey("orderId")) {
- return false;
- }
- Long orderId = Long.valueOf(params.get("orderId").toString());
- return cancelUserOrder(userId, orderId);
- }
- /**
- * 批量构建订单列表(一次查询商品和退款数据,避免 N+1)
- */
- private List<Map<String, Object>> buildOrderListBatch(List<Order> orders) {
- List<Long> orderIds = orders.stream().map(Order::getId).collect(Collectors.toList());
- // 批量查询所有订单的商品
- Map<Long, List<OrderItemVO>> goodsMap = orderGoodsService.getVOByOrderIds(orderIds);
- // 批量查询所有订单的退款商品数量
- Map<Long, Map<Long, Integer>> refundQtyMap = null;
- if (refundService != null) {
- refundQtyMap = refundService.getRefundedQuantityMapByOrderIds(orderIds);
- }
- List<Map<String, Object>> orderList = new ArrayList<>();
- for (Order order : orders) {
- Map<String, Object> orderMap = buildOrderMapBase(order);
- List<OrderItemVO> goodsList = goodsMap.getOrDefault(order.getId(), new ArrayList<>());
- Map<Long, Integer> refundQtys = refundQtyMap != null
- ? refundQtyMap.getOrDefault(order.getId(), new HashMap<>())
- : new HashMap<>();
- orderMap.put("products", buildProductList(goodsList, refundQtys));
- orderList.add(orderMap);
- }
- return orderList;
- }
- /**
- * 构建订单Map基础字段(不含 products)
- */
- private Map<String, Object> buildOrderMapBase(Order order) {
- Map<String, Object> orderMap = new HashMap<>();
- orderMap.put("id", order.getId());
- orderMap.put("orderNo", order.getOrderNo());
- orderMap.put("orderName", order.getOrderName() != null ? order.getOrderName() : "");
- orderMap.put("outTradeNo", order.getOutTradeNo());
- orderMap.put("deviceId", order.getDeviceId());
- orderMap.put("storeName", order.getStoreName() != null ? order.getStoreName() : "");
- orderMap.put("totalAmount", order.getTotalAmount());
- orderMap.put("discountAmount", order.getDiscountAmount() != null ? order.getDiscountAmount() : 0);
- orderMap.put("paidAmount", order.getPaidAmount() != null ? order.getPaidAmount() : order.getTotalAmount());
- orderMap.put("payStatus", order.getPayStatus());
- orderMap.put("payChannel", order.getPayChannel());
- orderMap.put("status", order.getStatus());
- orderMap.put("createTime", order.getCreateTime());
- orderMap.put("payTime", order.getPayTime());
- orderMap.put("confidence", order.getConfidence());
- orderMap.put("refundStatus", order.getRefundStatus());
- orderMap.put("refundAmount", order.getRefundAmount());
- orderMap.put("currentRefundId", order.getCurrentRefundId());
- orderMap.put("payScoreOrderId", order.getPayScoreOrderId());
- return orderMap;
- }
- /**
- * 构建商品列表(使用预查询数据)
- */
- private List<Map<String, Object>> buildProductList(List<OrderItemVO> goodsList, Map<Long, Integer> refundQtys) {
- List<Map<String, Object>> products = new ArrayList<>();
- for (OrderItemVO goods : goodsList) {
- Map<String, Object> product = new HashMap<>();
- product.put("id", goods.getId());
- product.put("orderGoodsId", goods.getId());
- product.put("productId", goods.getProductId());
- product.put("name", goods.getProductName());
- product.put("price", goods.getPrice());
- product.put("quantity", goods.getProductNum());
- product.put("image", normalizeImageUrl(goods.getPic()));
- product.put("pic", goods.getPic());
- product.put("subtotal", goods.getPrice() != null && goods.getProductNum() != null
- ? goods.getPrice().multiply(new BigDecimal(goods.getProductNum()))
- : new BigDecimal(0));
- product.put("refundedQuantity", refundQtys.getOrDefault(goods.getId(), 0));
- products.add(product);
- }
- return products;
- }
- /**
- * 构建订单Map(含商品信息)
- */
- private Map<String, Object> buildOrderMap(Order order) {
- Map<String, Object> orderMap = new HashMap<>();
- orderMap.put("id", order.getId());
- orderMap.put("orderNo", order.getOrderNo());
- orderMap.put("orderName", order.getOrderName() != null ? order.getOrderName() : "");
- orderMap.put("outTradeNo", order.getOutTradeNo());
- orderMap.put("deviceId", order.getDeviceId());
- orderMap.put("storeName", order.getStoreName() != null ? order.getStoreName() : "");
- orderMap.put("totalAmount", order.getTotalAmount());
- orderMap.put("discountAmount", order.getDiscountAmount() != null ? order.getDiscountAmount() : 0);
- orderMap.put("paidAmount", order.getPaidAmount() != null ? order.getPaidAmount() : order.getTotalAmount());
- orderMap.put("payStatus", order.getPayStatus());
- orderMap.put("payChannel", order.getPayChannel());
- orderMap.put("status", order.getStatus());
- orderMap.put("createTime", order.getCreateTime());
- orderMap.put("payTime", order.getPayTime());
- orderMap.put("confidence", order.getConfidence());
- orderMap.put("refundStatus", order.getRefundStatus());
- orderMap.put("refundAmount", order.getRefundAmount());
- orderMap.put("currentRefundId", order.getCurrentRefundId());
- orderMap.put("payScoreOrderId", order.getPayScoreOrderId());
- orderMap.put("products", getProductsFromDatabase(order.getId()));
- return orderMap;
- }
- /**
- * 从数据库查询订单商品信息
- */
- private List<Map<String, Object>> getProductsFromDatabase(Long orderId) {
- List<Map<String, Object>> products = new ArrayList<>();
- if (orderId == null) {
- return products;
- }
- try {
- // 查询已退款数量
- Map<Long, Integer> refundedQuantityMap = null;
- try {
- if (refundService != null) {
- refundedQuantityMap = refundService.getRefundedQuantityMap(orderId);
- }
- } catch (Exception e) {
- log.warn("查询订单 {} 已退款商品数量失败: {}", orderId, e.getMessage());
- }
- List<OrderItemVO> goodsList = orderGoodsService.getVOByOrderId(orderId);
- if (goodsList != null && !goodsList.isEmpty()) {
- for (OrderItemVO goods : goodsList) {
- Map<String, Object> product = new HashMap<>();
- product.put("id", goods.getId());
- product.put("orderGoodsId", goods.getId());
- product.put("productId", goods.getProductId());
- product.put("name", goods.getProductName());
- product.put("price", goods.getPrice());
- product.put("quantity", goods.getProductNum());
- product.put("image", normalizeImageUrl(goods.getPic()));
- product.put("pic", goods.getPic());
- product.put("subtotal", goods.getPrice() != null && goods.getProductNum() != null
- ? goods.getPrice().multiply(new BigDecimal(goods.getProductNum()))
- : new BigDecimal(0));
- int refundedQty = refundedQuantityMap != null
- ? refundedQuantityMap.getOrDefault(goods.getId(), 0) : 0;
- product.put("refundedQuantity", refundedQty);
- products.add(product);
- }
- }
- } catch (Exception e) {
- log.error("查询订单 {} 的商品信息失败:{}", orderId, e.getMessage());
- }
- return products;
- }
- /**
- * 修复缺失商品记录的订单:解析 orders.items (sku_list) 补写 t_order_goods
- * 优先使用订单 paidAmount,其次 totalAmount 作为商品金额基准
- *
- * @param activityId 指定 activityId 修复单个订单,为 null 则批量修复所有缺失商品的订单
- * @return 修复的订单数
- */
- @Transactional(rollbackFor = Exception.class)
- public int repairOrdersMissingGoods(String activityId) {
- LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
- wrapper.isNotNull(Order::getItems).ne(Order::getItems, "").ne(Order::getItems, "[]");
- if (activityId != null && !activityId.isEmpty()) {
- wrapper.eq(Order::getActivityId, activityId);
- }
- List<Order> orders = this.list(wrapper);
- int fixed = 0;
- for (Order order : orders) {
- String items = order.getItems();
- if (items == null || items.isEmpty()) {
- continue;
- }
- try {
- JSONArray skuList = JSON.parseArray(items);
- if (skuList == null || skuList.isEmpty()) {
- continue;
- }
- // 先删除已有商品记录(可能是之前占位写入的零售价数据),以最新金额重写
- orderGoodsMapper.delete(
- new LambdaQueryWrapper<OrderGoods>().eq(OrderGoods::getOrderId, order.getId()));
- // 计算每个商品的单价:用订单实付金额(或订单金额)均摊
- BigDecimal orderAmount = order.getPaidAmount() != null
- && order.getPaidAmount().compareTo(BigDecimal.ZERO) > 0
- ? order.getPaidAmount()
- : order.getTotalAmount() != null ? order.getTotalAmount() : BigDecimal.ZERO;
- // 计算总商品数量,按数量比例分配金额
- int totalQty = 0;
- for (int i = 0; i < skuList.size(); i++) {
- JSONObject sku = skuList.getJSONObject(i);
- Integer number = sku.getInteger("number");
- totalQty += (number != null ? Math.abs(number) : 1);
- }
- BigDecimal unitPrice = totalQty > 0
- ? orderAmount.divide(BigDecimal.valueOf(totalQty), 2, RoundingMode.HALF_UP)
- : orderAmount;
- for (int i = 0; i < skuList.size(); i++) {
- JSONObject sku = skuList.getJSONObject(i);
- String code = sku.getString("code");
- Integer number = sku.getInteger("number");
- if (code == null || code.isEmpty()) {
- continue;
- }
- int qty = number != null ? Math.abs(number) : 1;
- com.haha.entity.Product product = productService.getProductByCode(code);
- OrderGoods goods = new OrderGoods();
- goods.setOrderId(order.getId());
- goods.setOrderNo(order.getOrderNo());
- goods.setActivityId(order.getActivityId());
- goods.setDeviceId(order.getDeviceId());
- goods.setUserId(order.getUserId());
- goods.setCode(code);
- goods.setProductNum(qty);
- goods.setCreateTime(LocalDateTime.now());
- goods.setPrice(unitPrice);
- goods.setMoney(unitPrice.multiply(BigDecimal.valueOf(qty)));
- if (product != null) {
- goods.setProductId(product.getId());
- goods.setBarCode(product.getBarcode());
- goods.setProductName(product.getName());
- goods.setPic(product.getPic());
- }
- orderGoodsMapper.insert(goods);
- }
- fixed++;
- log.info("补写订单商品成功 - orderId: {}, activityId: {}, 金额基准: {}",
- order.getId(), order.getActivityId(), orderAmount);
- } catch (Exception e) {
- log.error("补写订单商品失败 - orderId: {}", order.getId(), e);
- }
- }
- return fixed;
- }
- /**
- * 批量修复已支付订单的支付状态同步到哈哈平台
- * 匹配规则:优先 hahaOrderId,其次 16 位纯数字 orderNo(曾被哈哈覆盖过的历史数据),否则跳过
- *
- * @return { synced: 成功数, skipped: 跳过数, failed: 失败数 }
- */
- @Transactional(rollbackFor = Exception.class)
- public Map<String, Object> repairPayStatusSync() {
- List<Order> orders = lambdaQuery()
- .eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
- .list();
- int synced = 0, skipped = 0, failed = 0;
- for (Order order : orders) {
- String hahaRef = order.getHahaOrderId();
- if (hahaRef == null || hahaRef.isEmpty()) {
- // 历史订单可能被哈哈短号覆盖过 orderNo(16位纯数字 = 旧行为)
- if (order.getOrderNo() != null && order.getOrderNo().matches("^\\d{16}$")) {
- hahaRef = order.getOrderNo();
- }
- }
- if (hahaRef == null || hahaRef.isEmpty()) {
- log.info("跳过同步 - orderNo: {}, 无法确定哈哈订单号", order.getOrderNo());
- skipped++;
- continue;
- }
- try {
- hahaClient.getOrderApi().setPayStatus(hahaRef);
- synced++;
- log.info("支付状态同步成功 - hahaRef: {}, localOrderNo: {}", hahaRef, order.getOrderNo());
- } catch (Exception e) {
- failed++;
- log.error("支付状态同步失败 - hahaRef: {}, localOrderNo: {}, error: {}",
- hahaRef, order.getOrderNo(), e.getMessage());
- }
- }
- Map<String, Object> result = new HashMap<>();
- result.put("total", orders.size());
- result.put("synced", synced);
- result.put("skipped", skipped);
- result.put("failed", failed);
- log.info("批量同步支付状态完成 - total: {}, synced: {}, skipped: {}, failed: {}",
- orders.size(), synced, skipped, failed);
- return result;
- }
- /**
- * 修复指定订单的支付分扣费,支持订单ID或订单编号
- *
- * @param orderIdOrNo 订单ID(数字)或订单编号
- * @param payScoreOrderId 微信支付分服务订单号(outOrderNo),为空则使用订单上的值
- * @return { success: true/false, message: "..." }
- */
- @Transactional(rollbackFor = Exception.class)
- public Map<String, Object> repairPayScorePayment(String orderIdOrNo, String payScoreOrderId) {
- Order order;
- if (orderIdOrNo.matches("^\\d{1,19}$")) {
- order = this.getById(Long.parseLong(orderIdOrNo));
- } else {
- order = lambdaQuery().eq(Order::getOrderNo, orderIdOrNo).one();
- }
- if (order == null) {
- return Map.of("success", false, "message", "订单不存在: " + orderIdOrNo);
- }
- // 如果本地丢失 payScoreOrderId,尝试从 Redis 自动恢复
- if ((order.getPayScoreOrderId() == null || order.getPayScoreOrderId().isEmpty())
- && (payScoreOrderId == null || payScoreOrderId.isEmpty())) {
- String recovered = payScoreService.tryRecoverPayScoreOrderId(order.getUserId(), order.getDeviceId());
- if (recovered != null) {
- payScoreOrderId = recovered;
- log.info("[支付分修复] 自动恢复 payScoreOrderId - orderId: {}, recovered: {}", order.getId(), recovered);
- }
- }
- // 如果提供了 outOrderNo(手动或自动恢复),先回填到本地订单
- if (payScoreOrderId != null && !payScoreOrderId.isEmpty()) {
- if (order.getPayScoreOrderId() == null || order.getPayScoreOrderId().isEmpty()) {
- order.setPayScoreOrderId(payScoreOrderId);
- order.setPayScoreState(PayScoreState.DOING.getCode());
- order.setPayChannel(PaymentChannel.WECHAT_PAYSCORE.getCode());
- order.setPayType("微信支付分");
- this.updateById(order);
- log.info("[支付分修复] 回填 payScoreOrderId - orderId: {}, orderNo: {}, payScoreOrderId: {}",
- order.getId(), order.getOrderNo(), payScoreOrderId);
- } else if (!payScoreOrderId.equals(order.getPayScoreOrderId())) {
- return Map.of("success", false, "message",
- "提供的 outOrderNo 与订单上的不一致: " + order.getPayScoreOrderId());
- }
- }
- // 重新加载以获取最新状态
- order = this.getById(order.getId());
- if (order.getPayScoreOrderId() == null || order.getPayScoreOrderId().isEmpty()) {
- return Map.of("success", false, "message",
- "未找到支付分商户单号,请从微信商户后台查找后手动输入");
- }
- if (PayScoreState.isFinished(order.getPayScoreState())) {
- return Map.of("success", false, "message", "支付分订单已完结,无需修复: " + order.getPayScoreState());
- }
- if (!OrderConstants.PAY_STATUS_UNPAID.equals(order.getPayStatus())) {
- return Map.of("success", false, "message", "订单已支付,无需修复");
- }
- if (order.getTotalAmount() == null || order.getTotalAmount().compareTo(BigDecimal.ZERO) <= 0) {
- return Map.of("success", false, "message", "订单金额为0,无法扣费");
- }
- BigDecimal amount = order.getPaidAmount() != null
- && order.getPaidAmount().compareTo(BigDecimal.ZERO) > 0
- ? order.getPaidAmount() : order.getTotalAmount();
- boolean result = completeOrderWithPayScore(order.getId(), amount);
- log.info("[支付分修复] orderId: {}, orderNo: {}, payScoreOrderId: {}, amount: {}, result: {}",
- order.getId(), order.getOrderNo(), order.getPayScoreOrderId(), amount, result);
- return result
- ? Map.of("success", true, "message", "扣费成功")
- : Map.of("success", false, "message", "扣费失败,请查看服务端日志");
- }
- /**
- * 标准化图片URL
- */
- private String normalizeImageUrl(String picUrl) {
- if (picUrl == null || picUrl.isEmpty()) {
- return picUrl;
- }
- if (picUrl.startsWith("http://") || picUrl.startsWith("https://")) {
- return picUrl;
- }
- return commonConfig.getImageDomainPrefix() + picUrl;
- }
- }
|