01Rate limiting02Databases03Caching04Flash sale05Idempotency06Circuit breaker07Queues & DLQ

Lab / 05 · Idempotent payments

Double-click “Pay” and still get charged once.

When network jitter causes a mobile client to retry a $250 payment, a standard API charges the customer twice. Idempotency keys in Redis protect against double billing by caching the original response and returning X-Idempotent-Replay: true.

LIVE · Replay Cache 5m · Idempotency Keys
Ledger Balance: $1000.00
Idempotency Gate
Header: Idempotency-Key: pay_intent_m59w1ny
// Bank Transaction Ledger
Click "Pay $250.00" or "Simulate Rapid Double-Click" to inspect ledger updates.

On the live site, requests hit real Spring Boot endpoints evaluating Idempotency-Key headers backed by Redis with atomic expiration.

Idempotent Request Lifecycle

First request generates a UUID idempotency key. Subsequent retries return the cached outcome with X-Idempotent-Replay: true without re-charging the credit card.

Browser Client
Double click 'Pay'
Idempotency Gate
Redis check key
Payment Gateway
Stripe / Banking rail
Customer Balance
Account ledger
Double Click without Key: $250 x 2 = $500 charged! | With Idempotency Key: Exactly $250 charged once.

Idempotency Key Handler

public PaymentChargeResult processPayment(PaymentChargeRequest req) { String redisKey = "idempotency:" + req.idempotencyKey(); // 1. Check if key was already processed String cached = redisTemplate.opsForValue().get(redisKey); if (cached != null) { PaymentChargeResult prev = parse(cached); return prev.withReplayed(true); // Return cached response, 0 double-charge } // 2. Fresh transaction: charge external payment gateway PaymentChargeResult result = stripeClient.charge(req.amountCents()); // 3. Store result with 5-minute TTL redisTemplate.opsForValue().set(redisKey, toJson(result), Duration.ofMinutes(5)); return result; }
Full source on GitHub →

Trade-offs I weighed

In-Flight Locking (SETNX PENDING) vs Post-Processing Cache

If two requests with the same key arrive simultaneously, setting a PENDING lock prevents both threads from charging the card while the first is still awaiting the bank gateway.

Payload Fingerprinting vs Key Only

Hashing the request body alongside the key guarantees a client cannot reuse an old idempotency key with different parameters (e.g., changing amount from $10 to $1,000).

Key Retention Window

A 24-hour TTL in production covers virtually all network retries, webhook re-deliveries, and accidental user double-submits without growing Redis memory unboundedly.

← ← 04 Flash saleNext: 06 Circuit breaker → →