Files
AIGC/demo/src/main/java/com/example/demo/service/PaymentService.java

341 lines
17 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}