package ru.soune.nocopy.service.payment; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.soune.ReferralRepo; import ru.soune.ReferralService; import ru.soune.nocopy.client.YooKassaClient; import ru.soune.nocopy.dto.payment.PaymentMethodDTO; import ru.soune.nocopy.entity.notification.NotificationType; import ru.soune.nocopy.entity.payment.Payment; import ru.soune.nocopy.entity.payment.PaymentMethod; import ru.soune.nocopy.entity.payment.PaymentOperationType; import ru.soune.nocopy.entity.payment.PaymentStatus; import ru.soune.nocopy.entity.search.GlobalSearchTask; import ru.soune.nocopy.entity.tarif.Tariff; import ru.soune.nocopy.entity.tarif.TariffInfo; import ru.soune.nocopy.entity.tarif.TariffStatus; import ru.soune.nocopy.entity.user.User; import ru.soune.nocopy.exception.PaymentNotFoundException; import ru.soune.nocopy.exception.TariffNotFoundException; import ru.soune.nocopy.exception.UserNotFoundException; import ru.soune.nocopy.repository.*; import ru.soune.nocopy.service.notification.NotificationService; import ru.soune.nocopy.service.tariff.TariffInfoService; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Service @RequiredArgsConstructor @Slf4j public class PaymentService { private final PaymentRepository paymentRepository; private final UserRepository userRepository; private final TariffRepository tariffRepository; private final TariffInfoRepository tariffInfoRepository; private final TariffInfoService tariffInfoService; private final ReferralService referralService; private final PaymentMethodRepository paymentMethodRepository; private final YooKassaClient yooKassaClient; private final ReferralRepo referralRepo; private final NotificationService notificationService; @Transactional public Payment createPayment(String email, Long tariffId, String operationType, String paymentUuid) { User user = userRepository.findByEmail(email); if (user == null) { throw new UserNotFoundException(email); } Tariff tariff = tariffRepository.findById(tariffId).orElseThrow(() -> new TariffNotFoundException("Tariff not found")); Payment payment = new Payment(); payment.setStatus(PaymentStatus.PENDING); payment.setAmount(tariff.getPrice()); payment.setOperationType(PaymentOperationType.valueOf(operationType)); payment.setUser(user); payment.setTariff(tariff); payment.setPaymentUuid(paymentUuid); return paymentRepository.save(payment); } private void savePaymentMethod(User user, Map paymentMethodData) { if (paymentMethodData == null) return; String paymentMethodId = (String) paymentMethodData.get("id"); if (paymentMethodRepository.findByPaymentMethodId(paymentMethodId).isPresent()) return; Map card = (Map) paymentMethodData.get("card"); if (card == null) return; PaymentMethod pm = new PaymentMethod(); pm.setUser(user); pm.setPaymentMethodId(paymentMethodId); pm.setLastFour((String) card.get("last4")); pm.setCardType((String) card.get("card_type")); pm.setExpiryMonth((String) card.get("expiry_month")); pm.setExpiryYear((String) card.get("expiry_year")); pm.setCreatedAt(LocalDateTime.now()); paymentMethodRepository.save(pm); } @Transactional(readOnly = true) public List getUserPaymentMethods(Long userId) { User user = userRepository.findById(userId).orElseThrow(); return paymentMethodRepository.findByUserAndActiveTrue(user) .stream() .map(pm -> new PaymentMethodDTO( pm.getPaymentMethodId(), pm.getLastFour(), pm.getCardType(), pm.getExpiryMonth(), pm.getExpiryYear() )) .collect(Collectors.toList()); } @Transactional public void deletePaymentMethod(Long userId, String paymentMethodId) { User user = userRepository.findById(userId).orElseThrow(); PaymentMethod pm = paymentMethodRepository.findByPaymentMethodId(paymentMethodId) .orElseThrow(() -> new RuntimeException("Payment method not found")); if (!pm.getUser().getId().equals(user.getId())) { throw new RuntimeException("Access denied"); } pm.setActive(false); paymentMethodRepository.save(pm); } @Transactional public void changeAutoRenewal(Long userId, boolean autoRenewal) { User user = userRepository.findById(userId).orElseThrow(); TariffInfo tariffInfo = user.getActiveTariffInfo(); if (tariffInfo != null) { tariffInfo.setAutoRenewal(autoRenewal); tariffInfoRepository.save(tariffInfo); } } @Transactional public void handlePaymentNotification(Map notification) { String eventType = (String) notification.get("event"); Map object = (Map) notification.get("object"); String paymentUuid = (String) object.get("id"); Payment payment = paymentRepository.findByPaymentUuid(paymentUuid) .orElseThrow(() -> new PaymentNotFoundException("Payment not found")); if ("payment.succeeded".equals(eventType)) { Tariff tariff = payment.getTariff(); double price = tariff.getPrice(); int intPrice = (int) Math.round(price); payment.setStatus(PaymentStatus.SUCCEEDED); payment.setCancellationReason(null); Map metadata = (Map) object.get("metadata"); Map paymentMethod = (Map) object.get("payment_method"); String paymentMethodId = paymentMethod != null ? (String) paymentMethod.get("id") : null; boolean autoRenewal = metadata != null && Boolean.parseBoolean(metadata.get("auto_renewal")); activateTariffForUser(payment.getUser(), tariff, autoRenewal, paymentMethodId); savePaymentMethod(payment.getUser(), paymentMethod); referralService.onUserAccountRefill(payment.getUser().getId(), intPrice); addSearchPaymentNotification(payment, payment.getUser().getId(), null); } else if ("payment.canceled".equals(eventType)) { payment.setStatus(PaymentStatus.CANCELED); Map cancellationDetails = (Map) object.get("cancellation_details"); String reason = (String) cancellationDetails.get("reason"); payment.setCancellationReason(reason); addSearchPaymentNotification(payment, payment.getUser().getId(), reason); } else if ("payment.waiting_for_capture".equals(eventType)) { payment.setStatus(PaymentStatus.WAITING); payment.setCancellationReason(null); } else { payment.setStatus(PaymentStatus.FAILED); Map cancellationDetails = (Map) object.get("cancellation_details"); String reason = (String) cancellationDetails.get("reason"); payment.setCancellationReason(reason); addSearchPaymentNotification(payment, payment.getUser().getId(), reason); } paymentRepository.save(payment); } private void addSearchPaymentNotification(Payment payment, Long userId, String reason) { ObjectMapper mapper = new ObjectMapper(); ObjectNode context = mapper.createObjectNode(); context.put("status", payment.getStatus().name()); context.put("reason", reason); context.put("amount", payment.getAmount()); notificationService.addNotification(NotificationType.PAYMENT_RESULT, userId, context.toString()); } public List userPayments(String email) { User user = userRepository.findByEmail(email); if (user == null) { throw new UserNotFoundException("User not found"); } return paymentRepository.findByUserId(user.getId()); } public Payment paymentInfo(String uuid) { return paymentRepository.findByPaymentUuid(uuid).orElseThrow(() -> new PaymentNotFoundException("Payment not found")); } private void activateTariffForUser(User user, Tariff tariff, boolean autoRenewal, String paymentMethodId) { String type = tariff.getTariffAccountType(); TariffInfo activeTariffInfo = user.getActiveTariffInfo(); if (type.equals("token")) { Integer tokens = activeTariffInfo.getBoughtTokens(); activeTariffInfo.setBoughtTokens(tokens + tariff.getTokens()); tariffInfoRepository.save(activeTariffInfo); } else { TariffInfo tariffInfo = new TariffInfo(); tariffInfo.setStatus(TariffStatus.ACTIVE); tariffInfo.setStartTariff(LocalDateTime.now()); tariffInfo.setEndTariff(LocalDateTime.now().plusMonths(1)); tariffInfo.setTokens(tariff.getTokens()); tariffInfo.setBoughtTokens(activeTariffInfo.getBoughtTokens()); tariffInfo.setTariff(tariff); activeTariffInfo.setAutoRenewal(autoRenewal); tariffInfo.setPaymentMethodId(paymentMethodId); TariffInfo saveTariffInfo = tariffInfoService.addTariffInfo(tariffInfo); tariffInfoService.linkUserTariffInfo(user, saveTariffInfo); tariffInfoService.linkCompanyTariffInfo(user, saveTariffInfo); } } }