PaymentService.java

package com.dmasone.identity.payment.application;

import com.dmasone.identity.orders.application.events.OrderPlacedEvent;
import com.dmasone.identity.payment.domain.PaymentAttempt;
import com.dmasone.identity.payment.domain.PaymentNotFoundException;
import com.dmasone.identity.payment.domain.PaymentRepository;
import com.dmasone.identity.payment.domain.PaymentStatus;
import java.time.Clock;
import java.util.UUID;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * Payment use-case service. It reacts to order placement events, records a
 * simulated authorization result, and keeps the operation idempotent per order.
 */
@Service
public class PaymentService {

    private final PaymentRepository paymentRepository;
    private final SimulatedPaymentGateway paymentGateway;
    private final Clock clock;

    public PaymentService(PaymentRepository paymentRepository, SimulatedPaymentGateway paymentGateway, Clock clock) {
        this.paymentRepository = paymentRepository;
        this.paymentGateway = paymentGateway;
        this.clock = clock;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public PaymentResult authorize(OrderPlacedEvent event) {
        return paymentRepository.findByOrderId(event.orderId())
                .map(PaymentResult::from)
                .orElseGet(() -> createPaymentAttempt(event));
    }

    @Transactional(readOnly = true)
    public PaymentResult findByOrderId(UUID orderId) {
        return paymentRepository.findByOrderId(orderId)
                .map(PaymentResult::from)
                .orElseThrow(() -> new PaymentNotFoundException(orderId));
    }

    private PaymentResult createPaymentAttempt(OrderPlacedEvent event) {
        PaymentStatus status = paymentGateway.authorize(event);
        PaymentAttempt paymentAttempt = PaymentAttempt.create(
                UUID.randomUUID(),
                event.orderId(),
                event.productId(),
                event.quantity(),
                status,
                clock.instant()
        );
        return PaymentResult.from(paymentRepository.save(paymentAttempt));
    }
}