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() .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; } } }