DonationStatusUpdateService.java

package org.petify.funding.service;

import org.petify.funding.client.AchievementClient;
import org.petify.funding.model.Donation;
import org.petify.funding.model.DonationStatus;
import org.petify.funding.model.Payment;
import org.petify.funding.model.PaymentStatus;
import org.petify.funding.repository.DonationRepository;
import org.petify.funding.repository.PaymentRepository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Slf4j
public class DonationStatusUpdateService {

    private final DonationRepository donationRepository;
    private final PaymentRepository paymentRepository;
    private final AchievementClient achievementClient;

    @Transactional
    public void handlePaymentStatusChange(Long paymentId, PaymentStatus newStatus) {
        log.info("Processing payment status change for payment {} to {}", paymentId, newStatus);

        Payment payment = paymentRepository.findById(paymentId)
                .orElseThrow(() -> new RuntimeException("Payment not found: " + paymentId));

        Donation donation = payment.getDonation();
        log.info("Found donation {} with current status: {}", donation.getId(), donation.getStatus());

        if (newStatus == PaymentStatus.SUCCEEDED) {
            if (donation.getStatus() != DonationStatus.COMPLETED) {
                log.info("Payment succeeded - updating donation {} status to COMPLETED", donation.getId());
                updateDonationStatus(donation.getId(), DonationStatus.COMPLETED);

                addExperiencePointsForDonation(donation);
            } else {
                log.info("Donation {} already completed", donation.getId());
            }
        } else if (newStatus == PaymentStatus.FAILED || newStatus == PaymentStatus.CANCELLED) {
            log.info("Payment failed/cancelled for donation {}", donation.getId());

            donation = donationRepository.findById(donation.getId())
                    .orElseThrow(() -> new RuntimeException("Donation not found"));

            if (donation.hasReachedMaxPaymentAttempts() && !donation.hasSuccessfulPayment()) {
                log.info("Max payment attempts reached - marking donation {} as FAILED", donation.getId());
                updateDonationStatus(donation.getId(), DonationStatus.FAILED);
            }
        }
    }

    @Transactional
    public void updateDonationStatus(Long donationId, DonationStatus newStatus) {
        log.info("Updating donation {} status to {}", donationId, newStatus);

        Donation donation = donationRepository.findById(donationId)
                .orElseThrow(() -> new RuntimeException("Donation not found: " + donationId));

        DonationStatus oldStatus = donation.getStatus();
        donation.setStatus(newStatus);

        if (newStatus == DonationStatus.COMPLETED && oldStatus != DonationStatus.COMPLETED
                && donation.getDonatedAt() == null) {
            donation.setDonatedAt(java.time.Instant.now());
            log.info("Set donatedAt for donation {}", donationId);
        }

        Donation saved = donationRepository.save(donation);
        log.info("Donation {} status updated from {} to {} (saved status: {})",
                donationId, oldStatus, newStatus, saved.getStatus());
    }

    private void addExperiencePointsForDonation(Donation donation) {
        try {
            String donorUsername = donation.getDonorUsername();
            if (donorUsername != null && !donorUsername.trim().isEmpty()) {
                int xpPoints = donation.getAmount().intValue();

                achievementClient.addDonationExperiencePointsForUser(donorUsername, xpPoints);
                log.info("Added {} XP points for donation {} (amount: {} PLN) to user: {}",
                        xpPoints, donation.getId(), donation.getAmount(), donorUsername);
            } else {
                log.info("Skipping XP award for anonymous donation {}", donation.getId());
            }
        } catch (Exception e) {
            log.error("Failed to add XP points for donation {} by user: {}",
                    donation.getId(), donation.getDonorUsername(), e);
        }
    }
}