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<CoursePurchaseOrderListItemVO> page(int page,
|
int pageSize,
|
String orderNo,
|
String userPhone,
|
String productName,
|
String paymentMethod,
|
String status,
|
LocalDateTime createdFrom,
|
LocalDateTime createdTo) {
|
IPage<CoursePurchaseOrderListRow> result = coursePurchaseOrderMapper.selectOrderPage(
|
new Page<CoursePurchaseOrderListRow>(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<AppCoursePurchaseOrderItemVO> appUserPage(int page, int pageSize, String productName) {
|
Long userId = appAuthSessionService.requireLoginUserId();
|
IPage<CoursePurchaseOrderListRow> result = coursePurchaseOrderMapper.selectAppUserOrderPage(
|
new Page<CoursePurchaseOrderListRow>(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<CoursePurchaseOrderListRow> rows = coursePurchaseOrderMapper.selectOrderList(
|
trimToNull(orderNo),
|
trimToNull(userPhone),
|
trimToNull(productName),
|
trimToNull(paymentMethod),
|
normalizeStatus(status),
|
createdFrom,
|
createdTo
|
);
|
List<CoursePurchaseOrderExcelRow> exports = new ArrayList<CoursePurchaseOrderExcelRow>();
|
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<CoursePurchaseOrder>()
|
.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<CoursePurchaseOrderListItemVO> toList(List<CoursePurchaseOrderListRow> rows) {
|
List<CoursePurchaseOrderListItemVO> list = new ArrayList<CoursePurchaseOrderListItemVO>();
|
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<AppCoursePurchaseOrderItemVO> toAppList(List<CoursePurchaseOrderListRow> rows) {
|
List<AppCoursePurchaseOrderItemVO> list = new ArrayList<AppCoursePurchaseOrderItemVO>();
|
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 "-";
|
}
|
}
|