package com.tcm.training.admin.live.service; import cn.idev.excel.FastExcel; 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.tcm.training.admin.app.auth.service.AppAuthSessionService; import com.tcm.training.admin.app.course.vo.AppCoursePurchaseOrderDetailVO; import com.tcm.training.admin.app.course.vo.AppCoursePurchaseOrderItemVO; import com.tcm.training.admin.common.DateTimes; import com.tcm.training.admin.common.PageResponse; import com.tcm.training.admin.common.PhoneMasker; import com.tcm.training.admin.live.dto.CoursePurchaseOrderRefundRequest; import com.tcm.training.admin.live.entity.CoursePurchaseOrder; import com.tcm.training.admin.live.excel.CoursePurchaseOrderExcelRow; import com.tcm.training.admin.live.mapper.CoursePurchaseOrderMapper; import com.tcm.training.admin.live.vo.CoursePurchaseOrderListItemVO; import com.tcm.training.admin.live.vo.CoursePurchaseOrderListRow; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.math.BigDecimal; import java.net.URLEncoder; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Service public class CoursePurchaseOrderAppService { private static final String COURSE_TYPE_HEALTH = "HEALTH_COURSE"; private static final String STATUS_PAID = "PAID"; private static final String STATUS_REFUNDED = "REFUNDED"; private static final String STATUS_WAITING_PAYMENT = "WAITING_PAYMENT"; private static final String STATUS_CANCELLED = "CANCELLED"; private static final String PAYMENT_METHOD_WECHAT = "WECHAT"; private static final String PAYMENT_METHOD_ALIPAY = "ALIPAY"; private final CoursePurchaseOrderMapper coursePurchaseOrderMapper; private final AppAuthSessionService appAuthSessionService; /** * 注入养生课程购买订单服务依赖。 */ public CoursePurchaseOrderAppService(CoursePurchaseOrderMapper coursePurchaseOrderMapper, AppAuthSessionService appAuthSessionService) { this.coursePurchaseOrderMapper = coursePurchaseOrderMapper; this.appAuthSessionService = appAuthSessionService; } /** * 分页查询后台养生课程购买订单。 */ public PageResponse page(int page, int pageSize, String orderNo, String userPhone, String productName, String paymentMethod, String status, LocalDateTime createdFrom, LocalDateTime createdTo) { IPage result = coursePurchaseOrderMapper.selectOrderPage( new Page(page, pageSize), trimToNull(orderNo), trimToNull(userPhone), trimToNull(productName), trimToNull(paymentMethod), normalizeStatus(status), createdFrom, createdTo ); return PageResponse.of(toList(result.getRecords()), result.getTotal(), result.getCurrent(), result.getSize()); } /** * 分页查询当前登录用户的 APP 购课记录。 */ public PageResponse appUserPage(int page, int pageSize, String productName) { Long userId = appAuthSessionService.requireLoginUserId(); IPage result = coursePurchaseOrderMapper.selectAppUserOrderPage( new Page(page, pageSize), userId, trimToNull(productName) ); return PageResponse.of(toAppList(result.getRecords()), result.getTotal(), result.getCurrent(), result.getSize()); } /** * 获取当前登录用户的 APP 购课记录详情。 */ public AppCoursePurchaseOrderDetailVO appUserDetail(String orderNo) { Long userId = appAuthSessionService.requireLoginUserId(); CoursePurchaseOrderListRow row = coursePurchaseOrderMapper.selectAppUserOrderDetail(userId, trimToNull(orderNo)); if (row == null) { throw new IllegalArgumentException("购课记录不存在"); } return toAppDetail(row); } /** * 导出后台养生课程购买订单。 */ public void export(HttpServletResponse response, String orderNo, String userPhone, String productName, String paymentMethod, String status, LocalDateTime createdFrom, LocalDateTime createdTo) throws IOException { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String filename = URLEncoder.encode("养生课程购买订单", "UTF-8").replace("+", "%20"); response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + filename + ".xlsx"); List rows = coursePurchaseOrderMapper.selectOrderList( trimToNull(orderNo), trimToNull(userPhone), trimToNull(productName), trimToNull(paymentMethod), normalizeStatus(status), createdFrom, createdTo ); List exports = new ArrayList(); for (CoursePurchaseOrderListRow row : rows) { CoursePurchaseOrderExcelRow export = new CoursePurchaseOrderExcelRow(); export.setOrderNo(row.getOrderNo()); export.setUserNickname(defaultText(row.getUserNickname())); export.setUserPhone(PhoneMasker.mask(row.getUserPhone())); export.setProductName(defaultText(row.getProductName())); export.setOrderAmount(row.getOrderAmount()); export.setPaymentMethod(paymentMethodText(row.getPaymentMethod())); export.setStatus(statusText(row.getStatus())); export.setRefundAmount(resolveRefundAmount(row.getStatus(), row.getOrderAmount(), row.getRefundAmount())); export.setRefundRemark(defaultText(row.getRefundRemark())); export.setCreatedAt(DateTimes.format(row.getCreatedAt())); exports.add(export); } FastExcel.write(response.getOutputStream(), CoursePurchaseOrderExcelRow.class) .sheet("养生课程购买订单") .doWrite(exports); } public void refund(Long id, CoursePurchaseOrderRefundRequest request) { CoursePurchaseOrder order = requireOrder(id); if (!STATUS_PAID.equals(order.getStatus())) { throw new IllegalArgumentException("仅已支付订单允许退款"); } if (request.getRefundAmount().compareTo(order.getOrderAmount()) > 0) { throw new IllegalArgumentException("退款金额不能超过用户实付金额"); } // TODO: 支付方案确认后,在这里接入真实退款流程并在退款成功后更新订单状态与退款时间。 throw new IllegalArgumentException("退款功能待支付方案确认后接入"); } /** * 删除后台已退款养生课程购买订单。 */ @Transactional public void delete(Long id) { CoursePurchaseOrder order = requireOrder(id); if (!STATUS_REFUNDED.equals(order.getStatus())) { throw new IllegalArgumentException("仅已退款订单允许删除"); } coursePurchaseOrderMapper.deleteById(id); } /** * 校验养生课程购买订单存在。 */ private CoursePurchaseOrder requireOrder(Long id) { CoursePurchaseOrder order = coursePurchaseOrderMapper.selectOne(new LambdaQueryWrapper() .eq(CoursePurchaseOrder::getId, id) .eq(CoursePurchaseOrder::getCourseType, COURSE_TYPE_HEALTH) .eq(CoursePurchaseOrder::getDeleted, 0) .last("limit 1")); if (order == null) { throw new IllegalArgumentException("养生课程购买订单不存在"); } return order; } /** * 将后台订单查询结果转换为后台列表响应。 */ private List toList(List rows) { List list = new ArrayList(); for (CoursePurchaseOrderListRow row : rows) { CoursePurchaseOrderListItemVO item = new CoursePurchaseOrderListItemVO(); item.setId(row.getId()); item.setOrderNo(row.getOrderNo()); item.setUserNickname(row.getUserNickname()); item.setUserPhone(PhoneMasker.mask(row.getUserPhone())); item.setProductName(row.getProductName()); item.setOrderAmount(row.getOrderAmount()); item.setPaymentMethod(row.getPaymentMethod()); item.setRefundAmount(resolveRefundAmount(row.getStatus(), row.getOrderAmount(), row.getRefundAmount())); item.setRefundRemark(row.getRefundRemark()); item.setStatus(row.getStatus()); item.setCreatedAt(row.getCreatedAt()); list.add(item); } return list; } /** * 将订单查询结果转换为 APP 购课记录列表响应。 */ private List toAppList(List rows) { List list = new ArrayList(); for (CoursePurchaseOrderListRow row : rows) { AppCoursePurchaseOrderItemVO item = new AppCoursePurchaseOrderItemVO(); item.setId(row.getId()); item.setOrderNo(row.getOrderNo()); item.setCourseId(row.getCourseId()); item.setCourseVideoId(row.getCourseVideoId()); item.setProductName(row.getProductName()); item.setCourseTitle(row.getCourseTitle()); item.setChapterName(row.getChapterName()); item.setCoverImage(row.getCoverImage()); item.setOrderAmount(row.getOrderAmount()); item.setPaymentMethod(row.getPaymentMethod()); item.setPaymentMethodText(paymentMethodText(row.getPaymentMethod())); item.setStatus(row.getStatus()); // APP 购课记录页只需标记是否退款,避免前端重复判断订单状态。 item.setShowRefundTag(Boolean.valueOf(STATUS_REFUNDED.equals(row.getStatus()))); item.setPaidAt(row.getPaidAt()); list.add(item); } return list; } /** * 将订单详情查询结果转换为 APP 购课记录详情响应。 */ private AppCoursePurchaseOrderDetailVO toAppDetail(CoursePurchaseOrderListRow row) { AppCoursePurchaseOrderDetailVO detail = new AppCoursePurchaseOrderDetailVO(); detail.setId(row.getId()); detail.setOrderNo(row.getOrderNo()); detail.setCourseId(row.getCourseId()); detail.setCourseVideoId(row.getCourseVideoId()); detail.setProductName(row.getProductName()); detail.setCourseTitle(row.getCourseTitle()); detail.setChapterName(row.getChapterName()); detail.setCoverImage(row.getCoverImage()); detail.setOrderAmount(row.getOrderAmount()); detail.setPaymentMethod(row.getPaymentMethod()); detail.setPaymentMethodText(paymentMethodText(row.getPaymentMethod())); detail.setStatus(row.getStatus()); // 详情页退款信息直接由后端归一化,避免前端重复拼装退款状态。 detail.setRefundAmount(resolveRefundAmount(row.getStatus(), row.getOrderAmount(), row.getRefundAmount())); detail.setShowRefundTag(Boolean.valueOf(STATUS_REFUNDED.equals(row.getStatus()))); detail.setCreatedAt(row.getCreatedAt()); detail.setPaidAt(row.getPaidAt()); detail.setRefundedAt(row.getRefundedAt()); return detail; } /** * 解析订单应展示的退款金额。 */ private BigDecimal resolveRefundAmount(String status, BigDecimal orderAmount, BigDecimal refundAmount) { if (!STATUS_REFUNDED.equals(status)) { return null; } return refundAmount == null ? orderAmount : refundAmount; } /** * 归一化后台筛选状态。 */ private String normalizeStatus(String status) { String normalized = trimToNull(status); if (normalized == null) { return null; } if (STATUS_PAID.equals(normalized) || STATUS_REFUNDED.equals(normalized)) { return normalized; } return null; } /** * 将空白字符串归一化为 null。 */ private String trimToNull(String value) { if (value == null) { return null; } String trimmed = value.trim(); return trimmed.isEmpty() ? null : trimmed; } /** * 缺省文本兜底。 */ private String defaultText(String value) { return value == null || value.trim().isEmpty() ? "-" : value.trim(); } /** * 解析支付方式展示文案。 */ private String paymentMethodText(String paymentMethod) { if (PAYMENT_METHOD_WECHAT.equals(paymentMethod)) { return "微信支付"; } if (PAYMENT_METHOD_ALIPAY.equals(paymentMethod)) { return "支付宝支付"; } return "-"; } /** * 解析订单状态展示文案。 */ private String statusText(String status) { if (STATUS_WAITING_PAYMENT.equals(status)) { return "待支付"; } if (STATUS_PAID.equals(status)) { return "已支付"; } if (STATUS_REFUNDED.equals(status)) { return "已退款"; } if (STATUS_CANCELLED.equals(status)) { return "已取消"; } return "-"; } }