package com.tcm.training.admin.app.member.service;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.tcm.training.admin.app.auth.service.AppAuthSessionService;
|
import com.tcm.training.admin.app.member.dto.AppMemberPurchaseRequest;
|
import com.tcm.training.admin.app.member.vo.AppMemberPurchaseResultVO;
|
import com.tcm.training.admin.member.entity.AppUser;
|
import com.tcm.training.admin.member.entity.MemberPurchaseOrder;
|
import com.tcm.training.admin.member.mapper.MemberPurchaseOrderMapper;
|
import com.tcm.training.admin.member.mapper.UserMapper;
|
import com.tcm.training.admin.member.service.MemberSettingsAppService;
|
import com.tcm.training.admin.member.vo.MemberPriceSettingsVO;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.math.BigDecimal;
|
import java.time.LocalDateTime;
|
import java.time.format.DateTimeFormatter;
|
|
/**
|
* APP 会员购买服务。
|
*/
|
@Service
|
public class AppMemberPurchaseService {
|
|
private static final int USER_STATUS_ENABLED = 1;
|
private static final String STATUS_PAID = "PAID";
|
private static final String MEMBER_TYPE_MONTH_CARD = "MONTH_CARD";
|
private static final String MEMBER_TYPE_QUARTER_CARD = "QUARTER_CARD";
|
private static final String PAYMENT_METHOD_WECHAT = "WECHAT";
|
private static final String PAYMENT_METHOD_ALIPAY = "ALIPAY";
|
private static final DateTimeFormatter ORDER_NO_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
|
private final AppAuthSessionService appAuthSessionService;
|
private final UserMapper userMapper;
|
private final MemberSettingsAppService memberSettingsAppService;
|
private final MemberPurchaseOrderMapper memberPurchaseOrderMapper;
|
|
/**
|
* 注入 APP 会员购买所需依赖。
|
*/
|
public AppMemberPurchaseService(AppAuthSessionService appAuthSessionService,
|
UserMapper userMapper,
|
MemberSettingsAppService memberSettingsAppService,
|
MemberPurchaseOrderMapper memberPurchaseOrderMapper) {
|
this.appAuthSessionService = appAuthSessionService;
|
this.userMapper = userMapper;
|
this.memberSettingsAppService = memberSettingsAppService;
|
this.memberPurchaseOrderMapper = memberPurchaseOrderMapper;
|
}
|
|
/**
|
* 提交 APP 会员购买,当前阶段按模拟直付处理并立即生效。
|
*/
|
@Transactional
|
public AppMemberPurchaseResultVO purchase(AppMemberPurchaseRequest request) {
|
Long userId = appAuthSessionService.requireLoginUserId();
|
AppUser user = requireEnabledUser(userId);
|
MemberPriceSettingsVO settings = requirePriceSettings();
|
String memberType = normalizeMemberType(request == null ? null : request.getMemberType());
|
String paymentMethod = normalizePaymentMethod(request == null ? null : request.getPaymentMethod());
|
|
PackageSnapshot packageSnapshot = resolveEnabledPackage(settings, memberType);
|
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime memberExpireAt = resolveMemberExpireAt(user.getMemberExpireAt(), packageSnapshot.getMemberDays(), now);
|
|
MemberPurchaseOrder order = buildOrder(user, packageSnapshot, paymentMethod, now);
|
memberPurchaseOrderMapper.insert(order);
|
|
// 会员购买成功后立即更新用户到期时间,APP 可直接回写最新会员态。
|
user.setMemberExpireAt(memberExpireAt);
|
userMapper.updateById(user);
|
order.setId(resolveOrderId(order, user.getId(), now));
|
|
return buildPurchaseResult(order, memberExpireAt);
|
}
|
|
/**
|
* 校验当前登录用户存在且可用。
|
*/
|
private AppUser requireEnabledUser(Long userId) {
|
AppUser user = userMapper.selectById(userId);
|
if (user == null || user.getDeleted() == null || user.getDeleted().intValue() != 0) {
|
throw new IllegalArgumentException("用户不存在");
|
}
|
if (user.getStatus() == null || user.getStatus().intValue() != USER_STATUS_ENABLED) {
|
throw new IllegalArgumentException("用户已禁用");
|
}
|
return user;
|
}
|
|
/**
|
* 读取当前会员价格配置,不存在时阻止下单。
|
*/
|
private MemberPriceSettingsVO requirePriceSettings() {
|
MemberPriceSettingsVO settings = memberSettingsAppService.currentPrice();
|
if (settings == null) {
|
throw new IllegalArgumentException("会员套餐暂未配置");
|
}
|
return settings;
|
}
|
|
/**
|
* 校验并提取当前可购买的会员套餐快照。
|
*/
|
private PackageSnapshot resolveEnabledPackage(MemberPriceSettingsVO settings, String memberType) {
|
if (MEMBER_TYPE_MONTH_CARD.equals(memberType)) {
|
return buildPackageSnapshot(memberType,
|
settings.getMonthCardEnabled(),
|
settings.getMonthCardDays(),
|
settings.getMonthCardPrice(),
|
"月会员暂未开放购买");
|
}
|
if (MEMBER_TYPE_QUARTER_CARD.equals(memberType)) {
|
return buildPackageSnapshot(memberType,
|
settings.getQuarterCardEnabled(),
|
settings.getQuarterCardDays(),
|
settings.getQuarterCardPrice(),
|
"季度会员暂未开放购买");
|
}
|
throw new IllegalArgumentException("会员套餐不合法");
|
}
|
|
/**
|
* 将后台启用配置转换为下单时使用的套餐快照。
|
*/
|
private PackageSnapshot buildPackageSnapshot(String memberType,
|
Boolean enabled,
|
Integer memberDays,
|
BigDecimal orderAmount,
|
String disabledMessage) {
|
if (!Boolean.TRUE.equals(enabled)) {
|
throw new IllegalArgumentException(disabledMessage);
|
}
|
if (memberDays == null || memberDays.intValue() <= 0 || orderAmount == null || orderAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
throw new IllegalArgumentException("会员套餐配置不完整");
|
}
|
return new PackageSnapshot(memberType, memberDays, orderAmount);
|
}
|
|
/**
|
* 规范化会员套餐类型,仅允许月卡或季卡。
|
*/
|
private String normalizeMemberType(String memberType) {
|
String normalized = trimToEmpty(memberType).toUpperCase();
|
if (MEMBER_TYPE_MONTH_CARD.equals(normalized) || MEMBER_TYPE_QUARTER_CARD.equals(normalized)) {
|
return normalized;
|
}
|
throw new IllegalArgumentException("会员套餐不合法");
|
}
|
|
/**
|
* 规范化支付方式入参,仅允许微信或支付宝。
|
*/
|
private String normalizePaymentMethod(String paymentMethod) {
|
String normalized = trimToEmpty(paymentMethod).toUpperCase();
|
if (PAYMENT_METHOD_WECHAT.equals(normalized) || PAYMENT_METHOD_ALIPAY.equals(normalized)) {
|
return normalized;
|
}
|
throw new IllegalArgumentException("支付方式不合法");
|
}
|
|
/**
|
* 计算新的会员到期时间;有效会员按原到期时间顺延,过期会员从当前时间起算。
|
*/
|
private LocalDateTime resolveMemberExpireAt(LocalDateTime currentExpireAt, Integer memberDays, LocalDateTime now) {
|
LocalDateTime baseTime = currentExpireAt != null && currentExpireAt.isAfter(now) ? currentExpireAt : now;
|
return baseTime.plusDays(memberDays == null ? 0L : memberDays.longValue());
|
}
|
|
/**
|
* 组装会员购买订单,支付方案落地前直接记为已支付。
|
*/
|
private MemberPurchaseOrder buildOrder(AppUser user,
|
PackageSnapshot packageSnapshot,
|
String paymentMethod,
|
LocalDateTime now) {
|
MemberPurchaseOrder order = new MemberPurchaseOrder();
|
order.setOrderNo(buildOrderNo(user.getId(), now));
|
order.setAppUserId(user.getId());
|
order.setUserNickname(resolveUserNickname(user));
|
order.setUserPhone(trimToEmpty(user.getPhone()));
|
order.setMemberType(packageSnapshot.getMemberType());
|
order.setMemberDays(packageSnapshot.getMemberDays());
|
order.setOrderAmount(packageSnapshot.getOrderAmount());
|
order.setPaymentMethod(paymentMethod);
|
order.setRefundAmount(null);
|
order.setRefundRemark(null);
|
order.setStatus(STATUS_PAID);
|
order.setPaidAt(now);
|
order.setRefundedAt(null);
|
order.setCancelledAt(null);
|
order.setDeleted(0);
|
return order;
|
}
|
|
/**
|
* 生成会员订单号。
|
*/
|
private String buildOrderNo(Long userId, LocalDateTime now) {
|
String seed = String.valueOf(Math.abs((userId == null ? 0L : userId.longValue()) % 10000L));
|
int nanoTail = Math.abs(now.getNano() / 1000) % 1000000;
|
return "MPO" + ORDER_NO_TIME_FORMATTER.format(now)
|
+ String.format("%06d", nanoTail)
|
+ String.format("%04d", Integer.parseInt(seed));
|
}
|
|
/**
|
* 订单插入后优先使用主键;极端情况下回查同一订单号,避免响应缺少订单编号。
|
*/
|
private Long resolveOrderId(MemberPurchaseOrder order, Long userId, LocalDateTime now) {
|
if (order.getId() != null) {
|
return order.getId();
|
}
|
MemberPurchaseOrder latestOrder = memberPurchaseOrderMapper.selectOne(new LambdaQueryWrapper<MemberPurchaseOrder>()
|
.eq(MemberPurchaseOrder::getAppUserId, userId)
|
.eq(MemberPurchaseOrder::getOrderNo, order.getOrderNo())
|
.eq(MemberPurchaseOrder::getDeleted, 0)
|
.orderByDesc(MemberPurchaseOrder::getPaidAt)
|
.orderByDesc(MemberPurchaseOrder::getId)
|
.last("LIMIT 1"));
|
if (latestOrder != null && latestOrder.getId() != null) {
|
return latestOrder.getId();
|
}
|
return null;
|
}
|
|
/**
|
* 将购买结果转换为 APP 可直接消费的响应体。
|
*/
|
private AppMemberPurchaseResultVO buildPurchaseResult(MemberPurchaseOrder order, LocalDateTime memberExpireAt) {
|
AppMemberPurchaseResultVO resultVO = new AppMemberPurchaseResultVO();
|
resultVO.setOrderId(order.getId());
|
resultVO.setOrderNo(order.getOrderNo());
|
resultVO.setMemberType(order.getMemberType());
|
resultVO.setMemberDays(order.getMemberDays());
|
resultVO.setOrderAmount(order.getOrderAmount());
|
resultVO.setPaymentMethod(order.getPaymentMethod());
|
resultVO.setStatus(order.getStatus());
|
resultVO.setPaidAt(order.getPaidAt());
|
resultVO.setMemberExpireAt(memberExpireAt);
|
return resultVO;
|
}
|
|
/**
|
* 优先使用昵称快照,没有昵称时降级到微信昵称或用户编号。
|
*/
|
private String resolveUserNickname(AppUser user) {
|
String nickname = trimToEmpty(user.getNickname());
|
if (!nickname.isEmpty()) {
|
return nickname;
|
}
|
String wechatNickname = trimToEmpty(user.getWechatNickname());
|
if (!wechatNickname.isEmpty()) {
|
return wechatNickname;
|
}
|
return "用户" + user.getId();
|
}
|
|
/**
|
* 将可空文本转换为去空格字符串。
|
*/
|
private String trimToEmpty(String value) {
|
if (value == null) {
|
return "";
|
}
|
return value.trim();
|
}
|
|
/**
|
* 会员套餐快照,避免业务逻辑反复透传零散字段。
|
*/
|
private static final class PackageSnapshot {
|
|
private final String memberType;
|
private final Integer memberDays;
|
private final BigDecimal orderAmount;
|
|
/**
|
* 创建会员套餐快照。
|
*/
|
private PackageSnapshot(String memberType, Integer memberDays, BigDecimal orderAmount) {
|
this.memberType = memberType;
|
this.memberDays = memberDays;
|
this.orderAmount = orderAmount;
|
}
|
|
/**
|
* 获取会员套餐类型。
|
*/
|
private String getMemberType() {
|
return memberType;
|
}
|
|
/**
|
* 获取会员有效天数。
|
*/
|
private Integer getMemberDays() {
|
return memberDays;
|
}
|
|
/**
|
* 获取订单金额。
|
*/
|
private BigDecimal getOrderAmount() {
|
return orderAmount;
|
}
|
}
|
}
|