IdempotencyStore.java
Bridget/payment-gateway-microservices/services/payment-service/src/main/java/com/checkout/paymentgw/payment/idempotency/IdempotencyStore.java
package com.checkout.paymentgw.payment.idempotency;
import com.checkout.paymentgw.payment.api.dto.PostPaymentResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Optional;
/**
* Redis-backed idempotency cache keyed by {@code merchantId + Idempotency-Key}.
*
* <p>If two requests with the same key arrive concurrently, the loser's request is
* also stored with the same key — so they end up returning the same response. For
* very strict guarantees, replace {@code SET …} with {@code SET … NX} and reject
* the second writer with {@code 409 Conflict}; we keep it simple here.
*/
@Component
public class IdempotencyStore {
private static final Duration TTL = Duration.ofHours(24);
private final StringRedisTemplate redis;
private final ObjectMapper mapper;
public IdempotencyStore(StringRedisTemplate redis, ObjectMapper mapper) {
this.redis = redis;
this.mapper = mapper;
}
public Optional<PostPaymentResponse> lookup(String merchantId, String idempotencyKey) {
String json = redis.opsForValue().get(key(merchantId, idempotencyKey));
if (json == null) {
return Optional.empty();
}
try {
return Optional.of(mapper.readValue(json, PostPaymentResponse.class));
} catch (JsonProcessingException e) {
// Cache poisoned. Treat as cache miss; the next response overwrites it.
return Optional.empty();
}
}
public void store(String merchantId, String idempotencyKey, PostPaymentResponse response) {
try {
redis.opsForValue().set(key(merchantId, idempotencyKey),
mapper.writeValueAsString(response), TTL);
} catch (JsonProcessingException e) {
// Non-fatal — idempotency is a best-effort optimisation.
}
}
private static String key(String merchantId, String idempotencyKey) {
return "idem:" + merchantId + ":" + idempotencyKey;
}
}
相关文章
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).
阅读文章 →