RefundCommandService.java

package com.newbit.payment.command.application.service;

import com.newbit.common.exception.BusinessException;
import com.newbit.common.exception.ErrorCode;
import com.newbit.notification.command.application.dto.request.NotificationSendRequest;
import com.newbit.notification.command.application.service.NotificationCommandService;
import com.newbit.payment.command.application.dto.TossPaymentApiDto;
import com.newbit.payment.command.application.dto.response.PaymentRefundResponse;
import com.newbit.payment.command.domain.aggregate.Payment;
import com.newbit.payment.command.domain.aggregate.PaymentStatus;
import com.newbit.payment.command.domain.aggregate.Refund;
import com.newbit.payment.command.domain.repository.PaymentRepository;
import com.newbit.payment.command.domain.repository.RefundRepository;
import com.newbit.purchase.command.application.service.DiamondTransactionCommandService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class RefundCommandService extends AbstractPaymentService<PaymentRefundResponse> {

    private static final int DIAMOND_UNIT_PRICE = 100;
    private final RefundRepository refundRepository;
    private final NotificationCommandService notificationCommandService;
    private final DiamondTransactionCommandService diamondTransactionCommandService;

    public RefundCommandService(PaymentRepository paymentRepository,
                                RefundRepository refundRepository,
                                TossPaymentApiClient tossPaymentApiClient,
                                NotificationCommandService notificationCommandService,
                                DiamondTransactionCommandService diamondTransactionCommandService
    ) {
        super(paymentRepository, tossPaymentApiClient);
        this.refundRepository = refundRepository;
        this.notificationCommandService = notificationCommandService;
        this.diamondTransactionCommandService = diamondTransactionCommandService;
    }

    @Transactional
    public PaymentRefundResponse cancelPayment(Long paymentId, String cancelReason) {
        Payment payment = findPaymentById(paymentId);
        
        validateCancelable(payment);
        
        TossPaymentApiDto.PaymentResponse response = tossPaymentApiClient.cancelPayment(
                payment.getPaymentKey(), 
                cancelReason
        );
        
        payment.refund();
        payment = paymentRepository.save(payment);
        
        Refund refund = Refund.createRefund(
                payment, 
                payment.getAmount(), 
                cancelReason, 
                response.getPaymentKey(),
                false
        );
        
        Refund savedRefund = refundRepository.save(refund);

        String notificationContent = String.format("환불이 완료되었습니다. (환불금액 : %,d)", savedRefund.getAmount().intValue());

        int refundDiamondAmount = savedRefund.getAmount().intValue() / DIAMOND_UNIT_PRICE;
        diamondTransactionCommandService.applyDiamondRefund(
                payment.getUserId(),
                savedRefund.getRefundId(),
                refundDiamondAmount
        );

        notificationCommandService.sendNotification(
                new NotificationSendRequest(
                        payment.getUserId()
                        , 15L
                        , savedRefund.getRefundId()
                        , notificationContent
                )
        );
        
        return createRefundResponse(savedRefund);
    }
    
    @Transactional
    public PaymentRefundResponse cancelPaymentPartial(Long paymentId, BigDecimal cancelAmount, String cancelReason) {
        Payment payment = findPaymentById(paymentId);
        
        validatePartialCancelable(payment);
        
        validateRefundAmount(payment, cancelAmount);
        
        TossPaymentApiDto.PaymentResponse response = tossPaymentApiClient.cancelPaymentPartial(
                payment.getPaymentKey(), 
                cancelReason,
                cancelAmount.longValue()
        );
        
        payment.partialRefund(cancelAmount);
        payment = paymentRepository.save(payment);
        
        Refund refund = Refund.createRefund(
                payment, 
                cancelAmount, 
                cancelReason, 
                response.getPaymentKey(),
                true
        );
        
        Refund savedRefund = refundRepository.save(refund);

        int refundDiamondAmount = savedRefund.getAmount().intValue() / DIAMOND_UNIT_PRICE;
        diamondTransactionCommandService.applyDiamondRefund(
                payment.getUserId(),
                savedRefund.getRefundId(),
                refundDiamondAmount
        );

        String notificationContent = String.format("환불이 완료되었습니다. (환불금액 : %,d)", savedRefund.getAmount().intValue());


        notificationCommandService.sendNotification(
                new NotificationSendRequest(
                        payment.getUserId()
                        , 15L
                        , savedRefund.getRefundId()
                        , notificationContent
                )
        );

        return createRefundResponse(savedRefund);
    }
    
    @Transactional(readOnly = true)
    public List<PaymentRefundResponse> getRefundsByPaymentId(Long paymentId) {
        if (!paymentRepository.existsById(paymentId)) {
            throw new BusinessException(ErrorCode.PAYMENT_NOT_FOUND);
        }
        
        List<Refund> refunds = refundRepository.findByPaymentPaymentId(paymentId);
        
        return refunds.stream()
                .map(this::createRefundResponse)
                .collect(Collectors.toList());
    }
    
    @Override
    @Transactional(readOnly = true)
    public PaymentRefundResponse getPayment(Long paymentId) {
        List<Refund> refunds = refundRepository.findByPaymentPaymentId(paymentId);
        
        if (refunds.isEmpty()) {
            throw new BusinessException(ErrorCode.PAYMENT_NOT_REFUNDABLE);
        }
        
        return createRefundResponse(refunds.get(refunds.size() - 1));
    }
   
    @Transactional(readOnly = true)
    public PaymentRefundResponse getRefund(Long refundId) {
        Refund refund = refundRepository.findById(refundId)
                .orElseThrow(() -> new BusinessException(ErrorCode.PAYMENT_REFUND_NOT_FOUND));
        
        return createRefundResponse(refund);
    }
    private PaymentRefundResponse createRefundResponse(Refund refund) {
        return PaymentRefundResponse.builder()
                .refundId(refund.getRefundId())
                .paymentId(refund.getPayment().getPaymentId())
                .amount(refund.getAmount())
                .reason(refund.getReason())
                .refundKey(refund.getRefundKey())
                .refundedAt(refund.getRefundedAt())
                .bankCode(refund.getBankCode())
                .accountNumber(refund.getAccountNumber())
                .holderName(refund.getHolderName())
                .build();
    }
}