341 lines
17 KiB
Java
341 lines
17 KiB
Java
package com.example.demo.service;
|
||
|
||
import java.math.BigDecimal;
|
||
import java.time.LocalDateTime;
|
||
import java.util.HashMap;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.Optional;
|
||
|
||
import org.slf4j.Logger;
|
||
import org.slf4j.LoggerFactory;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.stereotype.Service;
|
||
import org.springframework.transaction.annotation.Transactional;
|
||
|
||
import com.example.demo.model.*;
|
||
import com.example.demo.repository.PaymentRepository;
|
||
import com.example.demo.repository.MembershipLevelRepository;
|
||
|
||
@Service
|
||
@Transactional
|
||
public class PaymentService {
|
||
private static final Logger logger = LoggerFactory.getLogger(PaymentService.class);
|
||
@Autowired private PaymentRepository paymentRepository;
|
||
@Autowired private OrderService orderService;
|
||
@Autowired private UserService userService;
|
||
@Autowired private AlipayService alipayService;
|
||
@Autowired(required = false) private PayPalService payPalService;
|
||
@Autowired private SystemSettingsService systemSettingsService;
|
||
@Autowired private MembershipLevelRepository membershipLevelRepository;
|
||
|
||
public Payment save(Payment payment) { return paymentRepository.save(payment); }
|
||
@Transactional(readOnly = true) public Optional<Payment> findById(Long id) { return paymentRepository.findByIdWithUser(id); }
|
||
@Transactional(readOnly = true) public Optional<Payment> findByOrderId(String orderId) { return paymentRepository.findByOrderId(orderId); }
|
||
@Transactional(readOnly = true) public Optional<Payment> findByExternalTransactionId(String id) { return paymentRepository.findByExternalTransactionId(id); }
|
||
@Transactional(readOnly = true) public List<Payment> findByUserId(Long userId) { return paymentRepository.findByUserIdOrderByCreatedAtDesc(userId); }
|
||
@Transactional(readOnly = true) public List<Payment> findAll() { return paymentRepository.findAll(); }
|
||
@Transactional(readOnly = true) public List<Payment> findByStatus(PaymentStatus status) { return paymentRepository.findByStatus(status); }
|
||
@Transactional(readOnly = true) public long countByUserId(Long userId) { return paymentRepository.countByUserId(userId); }
|
||
@Transactional(readOnly = true) public long countByStatus(PaymentStatus status) { return paymentRepository.countByStatus(status); }
|
||
@Transactional(readOnly = true) public long countByUserIdAndStatus(Long userId, PaymentStatus status) { return paymentRepository.countByUserIdAndStatus(userId, status); }
|
||
|
||
/**
|
||
* 更新支付方式和描述(带事务,解决懒加载问题)
|
||
*/
|
||
@Transactional
|
||
public Payment updatePaymentMethod(Long paymentId, String method, String description, String username) {
|
||
// 使用findByIdWithUserAndOrder一次性加载User和Order,避免懒加载异常
|
||
Payment payment = paymentRepository.findByIdWithUserAndOrder(paymentId)
|
||
.orElseThrow(() -> new RuntimeException("支付记录不存在"));
|
||
|
||
// 检查权限
|
||
if (!payment.getUser().getUsername().equals(username)) {
|
||
throw new RuntimeException("无权限修改此支付记录");
|
||
}
|
||
|
||
// 只有PENDING状态的支付才能修改支付方式
|
||
if (payment.getStatus() != PaymentStatus.PENDING) {
|
||
throw new RuntimeException("只有待支付状态的订单才能修改支付方式");
|
||
}
|
||
|
||
payment.setPaymentMethod(PaymentMethod.valueOf(method));
|
||
if (description != null && !description.isEmpty()) {
|
||
payment.setDescription(description);
|
||
// 同时更新关联订单的描述
|
||
if (payment.getOrder() != null) {
|
||
payment.getOrder().setDescription(description);
|
||
// 由于在同一事务中,Order会自动保存
|
||
}
|
||
}
|
||
|
||
return paymentRepository.save(payment);
|
||
}
|
||
|
||
public Payment updatePaymentStatus(Long paymentId, PaymentStatus newStatus) {
|
||
Payment payment = paymentRepository.findById(paymentId).orElseThrow(() -> new RuntimeException("Not found"));
|
||
payment.setStatus(newStatus);
|
||
if (newStatus == PaymentStatus.SUCCESS) payment.setPaidAt(LocalDateTime.now());
|
||
return paymentRepository.save(payment);
|
||
}
|
||
|
||
public Payment confirmPaymentSuccess(Long paymentId, String externalTransactionId) {
|
||
// 使用findByIdWithUserAndOrder确保加载User和Order,避免懒加载问题
|
||
Payment payment = paymentRepository.findByIdWithUserAndOrder(paymentId)
|
||
.orElseThrow(() -> new RuntimeException("Not found"));
|
||
|
||
// 检查是否已经处理过(防止重复增加积分和重复创建订单)
|
||
if (payment.getStatus() == PaymentStatus.SUCCESS) {
|
||
logger.info("支付记录已经是成功状态,跳过重复处理: paymentId={}", paymentId);
|
||
return payment;
|
||
}
|
||
|
||
payment.setStatus(PaymentStatus.SUCCESS);
|
||
payment.setPaidAt(LocalDateTime.now());
|
||
payment.setExternalTransactionId(externalTransactionId);
|
||
Payment savedPayment = paymentRepository.save(payment);
|
||
|
||
// 支付成功后更新订单状态为已支付
|
||
// 注意:积分添加逻辑已移至 OrderService.handlePointsForStatusChange
|
||
// 当订单状态变为 PAID 时会自动添加积分,避免重复添加
|
||
try {
|
||
updateOrderStatusForPayment(savedPayment);
|
||
} catch (Exception e) {
|
||
logger.error("支付成功但更新订单状态失败: paymentId={}, error={}", paymentId, e.getMessage(), e);
|
||
}
|
||
|
||
return savedPayment;
|
||
}
|
||
|
||
/**
|
||
* 支付成功后更新关联订单状态为已支付
|
||
*/
|
||
private void updateOrderStatusForPayment(Payment payment) {
|
||
if (payment == null) {
|
||
logger.warn("无法更新订单状态: payment为空");
|
||
return;
|
||
}
|
||
|
||
// 检查是否已经关联了订单
|
||
Order order = payment.getOrder();
|
||
if (order == null) {
|
||
logger.warn("支付记录未关联订单,无法更新状态: paymentId={}", payment.getId());
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 直接调用orderService.updateOrderStatus,不要在这里修改order状态
|
||
// orderService.updateOrderStatus会正确处理状态变更和积分添加
|
||
orderService.updateOrderStatus(order.getId(), OrderStatus.PAID);
|
||
|
||
logger.info("✅ 订单状态更新为已支付: orderId={}, orderNumber={}, paymentId={}",
|
||
order.getId(), order.getOrderNumber(), payment.getId());
|
||
|
||
} catch (Exception e) {
|
||
logger.error("更新订单状态失败: paymentId={}, orderId={}, error={}",
|
||
payment.getId(), order.getId(), e.getMessage(), e);
|
||
throw e;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 根据支付金额增加用户积分
|
||
* 从 system_settings 读取配置的价格来判断套餐类型
|
||
* 标准版 -> 6000积分
|
||
* 专业版 -> 12000积分
|
||
*/
|
||
private void addPointsForPayment(Payment payment) {
|
||
if (payment == null || payment.getUser() == null) {
|
||
logger.warn("无法增加积分: payment或user为空");
|
||
return;
|
||
}
|
||
|
||
java.math.BigDecimal amount = payment.getAmount();
|
||
if (amount == null) {
|
||
logger.warn("无法增加积分: 支付金额为空, paymentId={}", payment.getId());
|
||
return;
|
||
}
|
||
|
||
// 从membership_levels表读取价格和积分(必须从数据库获取,禁止硬编码)
|
||
MembershipLevel standardLevel = membershipLevelRepository.findByName("standard")
|
||
.orElseThrow(() -> new IllegalStateException("数据库中缺少standard会员等级配置"));
|
||
MembershipLevel proLevel = membershipLevelRepository.findByName("professional")
|
||
.orElseThrow(() -> new IllegalStateException("数据库中缺少professional会员等级配置"));
|
||
|
||
int standardPrice = standardLevel.getPrice().intValue();
|
||
int standardPoints = standardLevel.getPointsBonus();
|
||
int proPrice = proLevel.getPrice().intValue();
|
||
int proPoints = proLevel.getPointsBonus();
|
||
|
||
logger.info("会员等级价格: 标准版={}CNY/{}积分, 专业版={}CNY/{}积分", standardPrice, standardPoints, proPrice, proPoints);
|
||
|
||
// 根据金额计算积分
|
||
int points = 0;
|
||
String planName = "";
|
||
int amountInt = amount.intValue();
|
||
|
||
// 判断套餐类型:先检查专业版(价格更高),再检查标准版
|
||
// 允许10%的价格浮动范围
|
||
if (amountInt >= proPrice * 0.9 && amountInt <= proPrice * 1.1) {
|
||
points = proPoints; // 专业版积分
|
||
planName = "专业版";
|
||
} else if (amountInt >= standardPrice * 0.9 && amountInt <= standardPrice * 1.1) {
|
||
points = standardPoints; // 标准版积分
|
||
planName = "标准版";
|
||
} else if (amountInt >= proPrice) {
|
||
// 如果金额大于等于专业版价格,按专业版计算
|
||
points = proPoints;
|
||
planName = "专业版";
|
||
} else if (amountInt >= standardPrice) {
|
||
// 如果金额大于等于标准版价格,按标准版计算
|
||
points = standardPoints;
|
||
planName = "标准版";
|
||
} else {
|
||
logger.info("支付金额不在套餐范围内,不增加积分: amount={}, 标准版价格={}, 专业版价格={}", amountInt, standardPrice, proPrice);
|
||
return;
|
||
}
|
||
|
||
// 增加积分
|
||
Long userId = payment.getUser().getId();
|
||
logger.info("开始为用户增加积分: userId={}, points={}, plan={}, paymentId={}, amount={}", userId, points, planName, payment.getId(), amountInt);
|
||
|
||
userService.addPoints(userId, points);
|
||
|
||
logger.info("✅ 积分增加成功: userId={}, addedPoints={}, plan={}", userId, points, planName);
|
||
}
|
||
|
||
public Payment confirmPaymentFailure(Long paymentId, String failureReason) {
|
||
Payment payment = paymentRepository.findById(paymentId).orElseThrow(() -> new RuntimeException("Not found"));
|
||
payment.setStatus(PaymentStatus.FAILED);
|
||
return paymentRepository.save(payment);
|
||
}
|
||
|
||
public Payment createOrderPayment(Order order, PaymentMethod paymentMethod) {
|
||
Payment payment = new Payment();
|
||
payment.setOrderId(order.getOrderNumber());
|
||
payment.setAmount(order.getTotalAmount());
|
||
payment.setCurrency(order.getCurrency());
|
||
payment.setPaymentMethod(paymentMethod);
|
||
payment.setUser(order.getUser());
|
||
payment.setOrder(order);
|
||
payment.setStatus(PaymentStatus.PENDING);
|
||
return paymentRepository.save(payment);
|
||
}
|
||
|
||
public void deletePayment(Long paymentId) {
|
||
Payment payment = paymentRepository.findById(paymentId).orElseThrow(() -> new RuntimeException("Not found"));
|
||
paymentRepository.delete(payment);
|
||
}
|
||
|
||
@Transactional(readOnly = true)
|
||
public List<Payment> findByUsername(String username) {
|
||
User user = userService.findByUsername(username);
|
||
if (user == null) throw new RuntimeException("User not found");
|
||
return paymentRepository.findByUserIdOrderByCreatedAtDesc(user.getId());
|
||
}
|
||
|
||
@Transactional
|
||
public Payment createPayment(String username, String orderId, String amountStr, String method, String description) {
|
||
// 检查是否已存在相同 orderId 的支付记录
|
||
Optional<Payment> existing = paymentRepository.findByOrderId(orderId);
|
||
if (existing.isPresent()) {
|
||
Payment existingPayment = existing.get();
|
||
// 如果已存在且状态是 PENDING,检查是否是同一用户且金额相同
|
||
if (existingPayment.getStatus() == PaymentStatus.PENDING) {
|
||
// 验证用户和金额是否匹配
|
||
if (existingPayment.getUser().getUsername().equals(username) &&
|
||
existingPayment.getAmount().compareTo(new BigDecimal(amountStr)) == 0) {
|
||
logger.info("复用已存在的PENDING支付记录: orderId={}, paymentId={}", orderId, existingPayment.getId());
|
||
return existingPayment;
|
||
} else {
|
||
// 用户或金额不匹配,生成新的 orderId
|
||
logger.warn("已存在相同orderId的PENDING支付,但用户或金额不匹配,生成新orderId: {}", orderId);
|
||
orderId = orderId + "_" + System.currentTimeMillis();
|
||
}
|
||
} else if (existingPayment.getStatus() == PaymentStatus.SUCCESS) {
|
||
// 如果已存在成功状态的支付,绝对不能复用,必须生成新的 orderId
|
||
logger.warn("⚠️ 已存在相同orderId的成功支付记录,生成新orderId避免冲突: orderId={}, existingPaymentId={}, status={}",
|
||
orderId, existingPayment.getId(), existingPayment.getStatus());
|
||
orderId = orderId + "_" + System.currentTimeMillis();
|
||
} else {
|
||
// 如果是其他状态(FAILED、CANCELLED等),生成新的 orderId
|
||
orderId = orderId + "_" + System.currentTimeMillis();
|
||
logger.info("已存在相同orderId但状态为{},生成新orderId: {}", existingPayment.getStatus(), orderId);
|
||
}
|
||
}
|
||
|
||
User user = null;
|
||
if (username != null) { try { user = userService.findByUsername(username); } catch (Exception e) {} }
|
||
if (user == null) { user = userService.findByUsernameOrNull(username != null ? username : "anon"); if (user == null) user = createAnonymousUser(username != null ? username : "anon"); }
|
||
|
||
BigDecimal amount = new BigDecimal(amountStr);
|
||
|
||
// 先创建待支付订单
|
||
Order order = new Order();
|
||
order.setUser(user);
|
||
order.setOrderNumber("ORD" + System.currentTimeMillis());
|
||
order.setTotalAmount(amount);
|
||
order.setCurrency("CNY");
|
||
order.setStatus(OrderStatus.PENDING); // 待支付状态
|
||
order.setOrderType(OrderType.SUBSCRIPTION);
|
||
|
||
// 使用前端传递的描述,如果没有则根据orderId中的套餐类型设置
|
||
if (description != null && !description.isEmpty()) {
|
||
order.setDescription(description);
|
||
} else if (orderId != null && orderId.contains("_")) {
|
||
// 从orderId中提取套餐类型(如 SUB_standard_xxx -> standard)
|
||
String[] parts = orderId.split("_");
|
||
if (parts.length >= 2) {
|
||
String planType = parts[1];
|
||
if ("standard".equalsIgnoreCase(planType)) {
|
||
order.setDescription("标准版会员订阅 - " + amount + "元");
|
||
} else if ("premium".equalsIgnoreCase(planType)) {
|
||
order.setDescription("专业版会员订阅 - " + amount + "元");
|
||
} else {
|
||
order.setDescription("会员订阅 - " + amount + "元");
|
||
}
|
||
} else {
|
||
order.setDescription("会员订阅 - " + amount + "元");
|
||
}
|
||
} else {
|
||
order.setDescription("会员订阅 - " + amount + "元");
|
||
}
|
||
|
||
Order savedOrder = orderService.createOrder(order);
|
||
logger.info("创建待支付订单: orderId={}, orderNumber={}, amount={}", savedOrder.getId(), savedOrder.getOrderNumber(), amount);
|
||
|
||
// 创建支付记录并关联订单
|
||
Payment payment = new Payment();
|
||
payment.setUser(user);
|
||
payment.setOrderId(orderId);
|
||
payment.setAmount(amount);
|
||
payment.setCurrency("CNY");
|
||
payment.setPaymentMethod(PaymentMethod.valueOf(method));
|
||
payment.setStatus(PaymentStatus.PENDING);
|
||
payment.setCreatedAt(LocalDateTime.now());
|
||
payment.setOrder(savedOrder); // 关联订单
|
||
payment.setDescription(order.getDescription());
|
||
|
||
return paymentRepository.save(payment);
|
||
}
|
||
|
||
private User createAnonymousUser(String username) {
|
||
User user = new User();
|
||
user.setUsername(username);
|
||
user.setEmail(username + "@anon.com");
|
||
user.setPasswordHash("anon");
|
||
user.setRole("ROLE_USER");
|
||
return userService.save(user);
|
||
}
|
||
|
||
@Transactional(readOnly = true)
|
||
public Map<String, Object> getUserPaymentStats(String username) {
|
||
User user = userService.findByUsername(username);
|
||
if (user == null) throw new RuntimeException("User not found");
|
||
Map<String, Object> stats = new HashMap<>();
|
||
stats.put("totalPayments", paymentRepository.countByUserId(user.getId()));
|
||
stats.put("successfulPayments", paymentRepository.countByUserIdAndStatus(user.getId(), PaymentStatus.SUCCESS));
|
||
return stats;
|
||
}
|
||
}
|