|
|
@@ -0,0 +1,258 @@
|
|
|
+package com.yingpai.gupiao.service.impl;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.yingpai.gupiao.domain.dto.CreateOrderDTO;
|
|
|
+import com.yingpai.gupiao.domain.po.PaymentOrder;
|
|
|
+import com.yingpai.gupiao.domain.po.UserSubscription;
|
|
|
+import com.yingpai.gupiao.domain.vo.*;
|
|
|
+import com.yingpai.gupiao.mapper.PaymentOrderMapper;
|
|
|
+import com.yingpai.gupiao.mapper.UserSubscriptionMapper;
|
|
|
+import com.yingpai.gupiao.service.OrderService;
|
|
|
+import com.yingpai.gupiao.service.PaymentConfigService;
|
|
|
+import com.yingpai.gupiao.service.WxPayService;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.LocalTime;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.time.temporal.ChronoUnit;
|
|
|
+import java.util.List;
|
|
|
+import java.util.UUID;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class OrderServiceImpl implements OrderService {
|
|
|
+
|
|
|
+ private final PaymentConfigService configService;
|
|
|
+ private final PaymentOrderMapper orderMapper;
|
|
|
+ private final UserSubscriptionMapper subscriptionMapper;
|
|
|
+ private final WxPayService wxPayService;
|
|
|
+
|
|
|
+ private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public PaymentConfigVO getPaymentConfig(Integer poolType) {
|
|
|
+ return configService.getConfig(poolType);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public WxPayVO createOrder(Long userId, String openid, CreateOrderDTO dto) {
|
|
|
+ PaymentConfigVO config = configService.getConfig(dto.getPoolType());
|
|
|
+
|
|
|
+ String orderNo = generateOrderNo();
|
|
|
+ int amountFen = config.getPrice().multiply(new BigDecimal("100")).intValue();
|
|
|
+
|
|
|
+ PaymentOrder order = PaymentOrder.builder()
|
|
|
+ .orderNo(orderNo)
|
|
|
+ .userId(userId)
|
|
|
+ .openid(openid)
|
|
|
+ .poolType(config.getPoolType())
|
|
|
+ .poolName(config.getPoolName())
|
|
|
+ .amount(config.getPrice())
|
|
|
+ .amountFen(amountFen)
|
|
|
+ .orderStatus(PaymentOrder.STATUS_PENDING)
|
|
|
+ .createTime(LocalDateTime.now())
|
|
|
+ .build();
|
|
|
+
|
|
|
+ orderMapper.insert(order);
|
|
|
+ log.info("创建订单,orderNo: {}, userId: {}, poolType: {}, amount: {}",
|
|
|
+ orderNo, userId, config.getPoolType(), config.getPrice());
|
|
|
+
|
|
|
+ // 测试模式:直接返回订单号,不调用微信支付
|
|
|
+ // 正式环境请改为:return wxPayService.createPrepayOrder(orderNo, openid, amountFen, config.getPoolName());
|
|
|
+ return WxPayVO.builder()
|
|
|
+ .orderNo(orderNo)
|
|
|
+ .timeStamp(String.valueOf(System.currentTimeMillis() / 1000))
|
|
|
+ .nonceStr(java.util.UUID.randomUUID().toString().replace("-", ""))
|
|
|
+ .packageValue("prepay_id=test_" + orderNo)
|
|
|
+ .signType("RSA")
|
|
|
+ .paySign("test_sign")
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void handlePaySuccess(String orderNo, String transactionId) {
|
|
|
+ PaymentOrder order = orderMapper.selectOne(new LambdaQueryWrapper<PaymentOrder>()
|
|
|
+ .eq(PaymentOrder::getOrderNo, orderNo));
|
|
|
+
|
|
|
+ if (order == null) {
|
|
|
+ log.error("订单不存在: {}", orderNo);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (order.getOrderStatus() == PaymentOrder.STATUS_PAID) {
|
|
|
+ log.info("订单已支付,跳过: {}", orderNo);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ order.setOrderStatus(PaymentOrder.STATUS_PAID);
|
|
|
+ order.setTransactionId(transactionId);
|
|
|
+ order.setPayTime(LocalDateTime.now());
|
|
|
+ order.setUpdateTime(LocalDateTime.now());
|
|
|
+ orderMapper.updateById(order);
|
|
|
+
|
|
|
+ createSubscription(order);
|
|
|
+ log.info("支付成功,orderNo: {}", orderNo);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建订阅
|
|
|
+ * 超短池:到当日24点
|
|
|
+ * 强势池:1年
|
|
|
+ */
|
|
|
+ private void createSubscription(PaymentOrder order) {
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
+ LocalDateTime expireTime;
|
|
|
+
|
|
|
+ if (order.getPoolType() == 1) {
|
|
|
+ // 超短池:到当日24点
|
|
|
+ expireTime = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
|
|
|
+ } else {
|
|
|
+ // 强势池:到当年最后一天的12:00
|
|
|
+ expireTime = LocalDateTime.of(now.getYear(), 12, 31, 12, 0, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建新订阅
|
|
|
+ UserSubscription sub = UserSubscription.builder()
|
|
|
+ .userId(order.getUserId())
|
|
|
+ .poolType(order.getPoolType())
|
|
|
+ .orderId(order.getId())
|
|
|
+ .orderNo(order.getOrderNo())
|
|
|
+ .amount(order.getAmount())
|
|
|
+ .startTime(now)
|
|
|
+ .expireTime(expireTime)
|
|
|
+ .status(UserSubscription.STATUS_ACTIVE)
|
|
|
+ .createTime(now)
|
|
|
+ .build();
|
|
|
+ subscriptionMapper.insert(sub);
|
|
|
+ log.info("创建订阅,userId: {}, poolType: {}, expireTime: {}",
|
|
|
+ order.getUserId(), order.getPoolType(), expireTime);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public OrderVO queryOrder(String orderNo) {
|
|
|
+ PaymentOrder order = orderMapper.selectOne(new LambdaQueryWrapper<PaymentOrder>()
|
|
|
+ .eq(PaymentOrder::getOrderNo, orderNo));
|
|
|
+ return order != null ? toOrderVO(order) : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<OrderVO> getUserOrders(Long userId) {
|
|
|
+ return orderMapper.selectList(new LambdaQueryWrapper<PaymentOrder>()
|
|
|
+ .eq(PaymentOrder::getUserId, userId)
|
|
|
+ .orderByDesc(PaymentOrder::getCreateTime))
|
|
|
+ .stream().map(this::toOrderVO).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<SubscriptionVO> getUserSubscriptions(Long userId) {
|
|
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
+ // 返回所有订阅记录,优先显示生效中的(按状态降序、创建时间降序)
|
|
|
+ return subscriptionMapper.selectList(new LambdaQueryWrapper<UserSubscription>()
|
|
|
+ .eq(UserSubscription::getUserId, userId)
|
|
|
+ .orderByDesc(UserSubscription::getStatus) // 生效中的优先
|
|
|
+ .orderByDesc(UserSubscription::getCreateTime))
|
|
|
+ .stream()
|
|
|
+ .map(sub -> toSubscriptionVO(sub, now))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean hasActiveSubscription(Long userId, Integer poolType) {
|
|
|
+ return subscriptionMapper.findActiveSubscription(userId, poolType, LocalDateTime.now()) != null;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void cancelOrder(Long userId, String orderNo) {
|
|
|
+ PaymentOrder order = orderMapper.selectOne(new LambdaQueryWrapper<PaymentOrder>()
|
|
|
+ .eq(PaymentOrder::getOrderNo, orderNo)
|
|
|
+ .eq(PaymentOrder::getUserId, userId));
|
|
|
+
|
|
|
+ if (order == null) throw new RuntimeException("订单不存在");
|
|
|
+ if (order.getOrderStatus() != PaymentOrder.STATUS_PENDING)
|
|
|
+ throw new RuntimeException("订单状态不允许取消");
|
|
|
+
|
|
|
+ order.setOrderStatus(PaymentOrder.STATUS_CANCELLED);
|
|
|
+ order.setCancelTime(LocalDateTime.now());
|
|
|
+ order.setUpdateTime(LocalDateTime.now());
|
|
|
+ orderMapper.updateById(order);
|
|
|
+ log.info("取消订单,orderNo: {}", orderNo);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public String verifyAndHandleNotify(String serialNumber, String nonce, String timestamp, String signature, String body) {
|
|
|
+ // 调用支付服务验签解密
|
|
|
+ String result = wxPayService.verifyAndDecryptNotify(serialNumber, nonce, timestamp, signature, body);
|
|
|
+
|
|
|
+ // 解析结果:orderNo|transactionId|tradeState
|
|
|
+ String[] parts = result.split("\\|");
|
|
|
+ String orderNo = parts[0];
|
|
|
+ String transactionId = parts[1];
|
|
|
+ String tradeState = parts[2];
|
|
|
+
|
|
|
+ if ("SUCCESS".equals(tradeState)) {
|
|
|
+ handlePaySuccess(orderNo, transactionId);
|
|
|
+ } else {
|
|
|
+ log.warn("支付未成功,orderNo: {}, state: {}", orderNo, tradeState);
|
|
|
+ }
|
|
|
+
|
|
|
+ return orderNo;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String generateOrderNo() {
|
|
|
+ String ts = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
|
|
|
+ String uuid = UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase();
|
|
|
+ return "GP" + ts + uuid;
|
|
|
+ }
|
|
|
+
|
|
|
+ private OrderVO toOrderVO(PaymentOrder order) {
|
|
|
+ return OrderVO.builder()
|
|
|
+ .orderId(order.getId())
|
|
|
+ .orderNo(order.getOrderNo())
|
|
|
+ .poolType(order.getPoolType())
|
|
|
+ .poolName(order.getPoolName())
|
|
|
+ .amount(order.getAmount())
|
|
|
+ .orderStatus(order.getOrderStatus())
|
|
|
+ .orderStatusName(getStatusName(order.getOrderStatus()))
|
|
|
+ .createTime(order.getCreateTime().format(DTF))
|
|
|
+ .payTime(order.getPayTime() != null ? order.getPayTime().format(DTF) : null)
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ private SubscriptionVO toSubscriptionVO(UserSubscription sub, LocalDateTime now) {
|
|
|
+ boolean isActive = sub.getStatus() == 1 && sub.getExpireTime().isAfter(now);
|
|
|
+ int remainDays = isActive ? (int) ChronoUnit.DAYS.between(now, sub.getExpireTime()) + 1 : 0;
|
|
|
+
|
|
|
+ return SubscriptionVO.builder()
|
|
|
+ .id(sub.getId())
|
|
|
+ .poolType(sub.getPoolType())
|
|
|
+ .poolName(sub.getPoolType() == 1 ? "超短池" : "强势池")
|
|
|
+ .amount(sub.getAmount())
|
|
|
+ .startTime(sub.getStartTime().format(DTF))
|
|
|
+ .expireTime(sub.getExpireTime().format(DTF))
|
|
|
+ .isActive(isActive)
|
|
|
+ .remainDays(remainDays)
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getStatusName(Integer status) {
|
|
|
+ return switch (status) {
|
|
|
+ case 0 -> "待支付";
|
|
|
+ case 1 -> "已支付";
|
|
|
+ case 2 -> "已取消";
|
|
|
+ default -> "未知";
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|