package com.doumee.service.business.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.doumee.config.wx.WxPayProperties; import com.doumee.config.wx.WxPayV3Service; 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.Utils; import com.doumee.dao.business.OrdersMapper; import com.doumee.dao.business.OrdersRefundMapper; import com.doumee.dao.business.model.Orders; import com.doumee.dao.business.model.OrdersRefund; import com.doumee.dao.dto.OrdersRefundPageDTO; import com.doumee.dao.vo.OrdersRefundPageVO; import com.doumee.service.business.OrdersRefundService; import com.github.yulichang.wrapper.MPJLambdaWrapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * 订单退款记录Service实现 * @author rk * @date 2026/04/13 */ @Slf4j @Service public class OrdersRefundServiceImpl implements OrdersRefundService { @Autowired private OrdersRefundMapper ordersRefundMapper; @Autowired private OrdersMapper ordersMapper; @Autowired private WxPayV3Service wxPayV3Service; @Autowired private WxPayProperties wxPayProperties; @Override public Integer create(OrdersRefund ordersRefund) { ordersRefundMapper.insert(ordersRefund); return ordersRefund.getId(); } @Override public void deleteById(Integer id) { ordersRefundMapper.deleteById(id); } @Override public void delete(OrdersRefund ordersRefund) { QueryWrapper deleteWrapper = new QueryWrapper<>(ordersRefund); ordersRefundMapper.delete(deleteWrapper); } @Override public void deleteByIdInBatch(List ids) { if (CollectionUtils.isEmpty(ids)) { return; } ordersRefundMapper.deleteBatchIds(ids); } @Override public void updateById(OrdersRefund ordersRefund) { ordersRefundMapper.updateById(ordersRefund); } @Override public void updateByIdInBatch(List ordersRefunds) { if (CollectionUtils.isEmpty(ordersRefunds)) { return; } for (OrdersRefund ordersRefund : ordersRefunds) { this.updateById(ordersRefund); } } @Override public OrdersRefund findById(Integer id) { return ordersRefundMapper.selectById(id); } @Override public OrdersRefund findOne(OrdersRefund ordersRefund) { QueryWrapper wrapper = new QueryWrapper<>(ordersRefund); return ordersRefundMapper.selectOne(wrapper); } @Override public List findList(OrdersRefund ordersRefund) { QueryWrapper wrapper = new QueryWrapper<>(ordersRefund); return ordersRefundMapper.selectList(wrapper); } @Override public PageData findPage(PageWrap pageWrap) { IPage page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity()); QueryWrapper queryWrapper = new QueryWrapper<>(); OrdersRefund model = pageWrap.getModel(); if (model != null) { if (model.getOrderId() != null) { queryWrapper.lambda().eq(OrdersRefund::getOrderId, model.getOrderId()); } if (model.getType() != null) { queryWrapper.lambda().eq(OrdersRefund::getType, model.getType()); } } for (PageWrap.SortData sortData : pageWrap.getSorts()) { if (sortData.getDirection().equalsIgnoreCase(PageWrap.DESC)) { queryWrapper.orderByDesc(sortData.getProperty()); } else { queryWrapper.orderByAsc(sortData.getProperty()); } } return PageData.from(ordersRefundMapper.selectPage(page, queryWrapper)); } @Override public long count(OrdersRefund ordersRefund) { QueryWrapper wrapper = new QueryWrapper<>(ordersRefund); return ordersRefundMapper.selectCount(wrapper); } @Override public PageData refundPage(PageWrap pageWrap) { OrdersRefundPageDTO model = pageWrap.getModel(); IPage page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity()); MPJLambdaWrapper wrapper = new MPJLambdaWrapper<>(); wrapper.selectAll(OrdersRefund.class) .select("o.code as orderCode") .select("o.goods_info as goodsInfo") .select("o.type as orderType") .select("o.good_level as goodLevel") .select("o.total_amount as totalAmount") .select("o.pay_amount as payAmount") .select("c2.name as goodLevelName") .innerJoin("orders o on o.id = t.ORDER_ID ") .leftJoin("category c1 on c1.id = o.GOOD_TYPE and c1.DELETED = 0") .leftJoin("category c2 on c2.id = c1.RELATION_ID and c2.DELETED = 0 and c2.TYPE = 3") .eq(OrdersRefund::getDeleted, 0); if (model != null) { if (StringUtils.isNotBlank(model.getOrderCode())) { wrapper.like("o.code", model.getOrderCode()); } if (StringUtils.isNotBlank(model.getGoodsInfo())) { wrapper.like("o.goods_info", model.getGoodsInfo()); } if (model.getOrderType() != null) { wrapper.eq("o.type", model.getOrderType()); } if (model.getRefundStatus() != null) { wrapper.eq(OrdersRefund::getStatus, model.getRefundStatus()); } if (model.getCreateStartTime() != null) { wrapper.ge(OrdersRefund::getCreateTime, model.getCreateStartTime()); } if (model.getCreateEndTime() != null) { wrapper.le(OrdersRefund::getCreateTime, model.getCreateEndTime()); } } wrapper.orderByDesc(OrdersRefund::getCreateTime); IPage refundPage = ordersRefundMapper.selectJoinPage(page, OrdersRefund.class, wrapper); // 转换为 VO List voList = new ArrayList<>(); if (refundPage != null && refundPage.getRecords() != null) { for (OrdersRefund r : refundPage.getRecords()) { OrdersRefundPageVO vo = new OrdersRefundPageVO(); vo.setId(r.getId()); vo.setOrderId(r.getOrderId()); vo.setOrderCode(r.getOrderCode()); vo.setGoodsInfo(r.getGoodsInfo()); vo.setOrderType(r.getOrderType()); vo.setGoodLevelName(r.getGoodLevelName()); vo.setTotalAmount(r.getTotalAmount()); vo.setPayAmount(r.getPayAmount()); vo.setRefundAmount(r.getRefundAmount()); vo.setCreateTime(r.getCreateTime()); vo.setRefundStatus(r.getStatus()); voList.add(vo); } } PageData result = new PageData<>( refundPage != null ? refundPage.getCurrent() : pageWrap.getPage(), refundPage != null ? refundPage.getSize() : pageWrap.getCapacity()); result.setRecords(voList); if (refundPage != null) { result.setTotal(refundPage.getTotal()); } return result; } @Override @Transactional(rollbackFor = Exception.class) public void retryRefund(Integer id) { // 1. 查询退款记录 OrdersRefund refundRecord = ordersRefundMapper.selectById(id); if (refundRecord == null || Constants.ONE.equals(refundRecord.getDeleted())) { throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "退款记录不存在"); } if (!Constants.TWO.equals(refundRecord.getStatus())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "仅退款失败的记录可重新退款"); } // 2. 查询关联订单 Orders order = ordersMapper.selectById(refundRecord.getOrderId()); if (order == null || Constants.ONE.equals(order.getDeleted())) { throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "关联订单不存在"); } if (StringUtils.isBlank(order.getOutTradeNo())) { throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "订单缺少微信支付单号"); } // 3. 发起微信退款 String outRefundNo = com.doumee.core.utils.ID.nextGUID(); com.wechat.pay.java.service.refund.model.Refund refundResult; try { refundResult = wxPayV3Service.refund( outRefundNo, order.getOutTradeNo(), order.getPayAmount(), refundRecord.getRefundAmount(), "重新退款", wxPayProperties.getV3RefundNotifyUrl()); } catch (Exception e) { log.error("重新退款调用异常, refundRecordId={}", id, e); throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "退款失败,请稍后重试"); } // 4. 更新退款记录 Date now = new Date(); com.wechat.pay.java.service.refund.model.Status wxStatus = refundResult.getStatus(); OrdersRefund update = new OrdersRefund(); update.setId(id); update.setRefundCode(outRefundNo); update.setUpdateTime(now); if (com.wechat.pay.java.service.refund.model.Status.SUCCESS.equals(wxStatus)) { update.setStatus(Constants.ONE); update.setRefundTime(now); } else if (com.wechat.pay.java.service.refund.model.Status.PROCESSING.equals(wxStatus)) { update.setStatus(Constants.ZERO); } else { update.setStatus(Constants.TWO); update.setRefundRemark("重新退款仍然失败"); } ordersRefundMapper.updateById(update); } }