S SmartDocs
Chuỗi bài: Bridget java 112 dòng · Cập nhật 2026-05-08

PaymentPersistenceService.java

Bridget/payment-gateway-microservices/services/payment-service/src/main/java/com/checkout/paymentgw/payment/application/PaymentPersistenceService.java

package com.checkout.paymentgw.payment.application;

import com.checkout.paymentgw.events.PaymentEvent;
import com.checkout.paymentgw.events.Topics;
import com.checkout.paymentgw.payment.api.dto.PostPaymentRequest;
import com.checkout.paymentgw.payment.api.dto.PostPaymentResponse;
import com.checkout.paymentgw.payment.domain.Payment;
import com.checkout.paymentgw.payment.domain.PaymentRepository;
import com.checkout.paymentgw.payment.domain.PaymentStatus;
import com.checkout.paymentgw.payment.outbox.OutboxMessage;
import com.checkout.paymentgw.payment.outbox.OutboxRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.util.UUID;

/**
 * Owns the {@code @Transactional} persistence of a payment + its outbox message.
 *
 * <p>This is intentionally a separate Spring bean (not an inner method on
 * {@link CreatePaymentUseCase}) so that Spring's AOP proxy actually applies
 * the transaction. A {@code @Transactional} method called from another method
 * of the <em>same</em> class via {@code this.x()} bypasses the proxy and silently
 * runs without a transaction — a classic Spring gotcha.
 */
@Service
public class PaymentPersistenceService {

    private final PaymentRepository payments;
    private final OutboxRepository outbox;
    private final ObjectMapper mapper;

    public PaymentPersistenceService(PaymentRepository payments,
                                     OutboxRepository outbox,
                                     ObjectMapper mapper) {
        this.payments = payments;
        this.outbox = outbox;
        this.mapper = mapper;
    }

    @Transactional
    public PostPaymentResponse save(String merchantId,
                                    PostPaymentRequest request,
                                    PaymentStatus status,
                                    String authCode,
                                    String declineReason) {
        UUID id = UUID.randomUUID();
        String last4 = request.getCardNumber().substring(request.getCardNumber().length() - 4);
        Payment row = Payment.builder()
                .id(id)
                .merchantId(merchantId)
                .status(status)
                .cardNumberLastFour(last4)
                .expiryMonth(request.getExpiryMonth())
                .expiryYear(request.getExpiryYear())
                .currency(request.getCurrency())
                .amount(request.getAmount())
                .authCode(authCode)
                .declineReason(declineReason)
                .createdAt(Instant.now())
                .build();
        payments.save(row);

        outbox.save(buildOutboxMessage(row));

        return PostPaymentResponse.builder()
                .id(row.getId())
                .status(row.getStatus().name())
                .cardNumberLastFour(row.getCardNumberLastFour())
                .expiryMonth(row.getExpiryMonth())
                .expiryYear(row.getExpiryYear())
                .currency(row.getCurrency())
                .amount(row.getAmount())
                .build();
    }

    private OutboxMessage buildOutboxMessage(Payment row) {
        PaymentEvent.EventType type = switch (row.getStatus()) {
            case AUTHORIZED -> PaymentEvent.EventType.PAYMENT_AUTHORISED;
            case DECLINED   -> PaymentEvent.EventType.PAYMENT_DECLINED;
            case REJECTED   -> PaymentEvent.EventType.PAYMENT_FAILED;
            case FAILED     -> PaymentEvent.EventType.PAYMENT_FAILED;
        };
        PaymentEvent ev = PaymentEvent.builder()
                .eventId(UUID.randomUUID().toString())
                .eventType(type)
                .paymentId(row.getId())
                .merchantId(row.getMerchantId())
                .currency(row.getCurrency())
                .amount(row.getAmount())
                .cardNumberLastFour(row.getCardNumberLastFour())
                .status(row.getStatus().name())
                .reason(row.getDeclineReason())
                .occurredAt(row.getCreatedAt())
                .build();
        try {
            return OutboxMessage.builder()
                    .id(UUID.randomUUID())
                    .aggregateId(row.getId())
                    .topic(Topics.PAYMENTS_EVENTS)
                    .keyHint(row.getId().toString())
                    .payload(mapper.writeValueAsString(ev))
                    .createdAt(Instant.now())
                    .build();
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("Cannot serialise payment event", e);
        }
    }
}

Bài viết liên quan

Bridget java Cập nhật 2026-03-30

PaymentGatewayApplication.java

PaymentGatewayApplication.java — java source code from the Bridget learning materials (Bridget/payment-gateway-challenge-java/src/main/java/com/checkout/payment/gateway/PaymentGatewayApplication.java).

Đọc bài viết →
Bridget java Cập nhật 2026-03-20

ApplicationConfiguration.java

ApplicationConfiguration.java — java source code from the Bridget learning materials (Bridget/payment-gateway-challenge-java/src/main/java/com/checkout/payment/gateway/configuration/ApplicationConfiguration.java).

Đọc bài viết →
Bridget java Cập nhật 2026-03-30

RequestResponseLogger.java

RequestResponseLogger.java — java source code from the Bridget learning materials (Bridget/payment-gateway-challenge-java/src/main/java/com/checkout/payment/gateway/configuration/RequestResponseLogger.java).

Đọc bài viết →
Bridget java Cập nhật 2026-04-14

PaymentGatewayController.java

PaymentGatewayController.java — java source code from the Bridget learning materials (Bridget/payment-gateway-challenge-java/src/main/java/com/checkout/payment/gateway/controller/PaymentGatewayController.java).

Đọc bài viết →
Bridget java Cập nhật 2026-03-20

PaymentStatus.java

PaymentStatus.java — java source code from the Bridget learning materials (Bridget/payment-gateway-challenge-java/src/main/java/com/checkout/payment/gateway/enums/PaymentStatus.java).

Đọc bài viết →
Bridget java Cập nhật 2026-03-29

BankErrorException.java

BankErrorException.java — java source code from the Bridget learning materials (Bridget/payment-gateway-challenge-java/src/main/java/com/checkout/payment/gateway/exception/BankErrorException.java).

Đọc bài viết →