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);
}
}
}
相关文章
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).
阅读文章 →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).
阅读文章 →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).
阅读文章 →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).
阅读文章 →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).
阅读文章 →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).
阅读文章 →