S SmartDocs
系列: Bridget java 78 行 · 更新于 2026-05-08

PaymentEventConsumer.java

Bridget/payment-gateway-microservices/services/payment-query-service/src/main/java/com/checkout/paymentgw/query/projection/PaymentEventConsumer.java

package com.checkout.paymentgw.query.projection;

import com.checkout.paymentgw.events.PaymentEvent;
import com.checkout.paymentgw.events.Topics;
import com.checkout.paymentgw.query.domain.PaymentView;
import com.checkout.paymentgw.query.domain.PaymentViewRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * Idempotent consumer of {@code payments.events}. Each event is upserted into
 * {@code payments_view} keyed by {@code paymentId}.
 *
 * <p>Idempotency is achieved by primary-key upsert plus a sequence/timestamp guard.
 * For replay, we can drop the read DB and let the consumer rebuild the view from the
 * earliest offset.
 */
@Component
public class PaymentEventConsumer {

    private static final Logger LOG = LoggerFactory.getLogger(PaymentEventConsumer.class);

    private final PaymentViewRepository repository;
    private final ObjectMapper mapper;

    public PaymentEventConsumer(PaymentViewRepository repository, ObjectMapper mapper) {
        this.repository = repository;
        this.mapper = mapper;
    }

    @KafkaListener(topics = Topics.PAYMENTS_EVENTS, groupId = "payment-query-service")
    @Transactional
    public void onMessage(String payload) {
        try {
            PaymentEvent ev = mapper.readValue(payload, PaymentEvent.class);
            apply(ev);
        } catch (Exception e) {
            LOG.error("failed to project event payload={}", payload, e);
            // Don't rethrow — would block the partition. In production, send to a DLT.
        }
    }

    private void apply(PaymentEvent ev) {
        if (ev.paymentId() == null) {
            LOG.warn("event without paymentId — skipping; eventId={}", ev.eventId());
            return;
        }

        PaymentView existing = repository.findById(ev.paymentId()).orElse(null);
        // ordering guard — only overwrite with a newer event
        if (existing != null && existing.getOccurredAt() != null
                && ev.occurredAt() != null
                && existing.getOccurredAt().isAfter(ev.occurredAt())) {
            return;
        }

        PaymentView view = PaymentView.builder()
                .id(ev.paymentId())
                .merchantId(nullSafe(ev.merchantId(), existing == null ? null : existing.getMerchantId()))
                .status(nullSafe(ev.status(), existing == null ? null : existing.getStatus()))
                .cardNumberLastFour(nullSafe(ev.cardNumberLastFour(), existing == null ? null : existing.getCardNumberLastFour()))
                .currency(nullSafe(ev.currency(), existing == null ? null : existing.getCurrency()))
                .amount(ev.amount() != null ? ev.amount() : (existing == null ? null : existing.getAmount()))
                .occurredAt(ev.occurredAt())
                .expiryMonth(existing == null ? null : existing.getExpiryMonth())
                .expiryYear(existing == null ? null : existing.getExpiryYear())
                .build();
        repository.save(view);
    }

    private static String nullSafe(String a, String b) {
        return a != null ? a : b;
    }
}

相关文章

Bridget java 更新于 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).

阅读文章 →
Bridget java 更新于 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).

阅读文章 →
Bridget java 更新于 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).

阅读文章 →
Bridget java 更新于 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).

阅读文章 →
Bridget java 更新于 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).

阅读文章 →
Bridget java 更新于 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).

阅读文章 →