**Brief approach**I’d accept an Idempotency-Key header and persist a record with request hash, status, response, timestamps. On duplicate requests return the stored response. For concurrent same-key requests use either optimistic upsert (preferred for scale) or a short DB lock.**Schema (SQL)**sql
CREATE TABLE idempotency (
key TEXT PRIMARY KEY,
request_hash TEXT,
status TEXT, -- in_progress / completed / failed
response JSONB, -- cached response
created_at TIMESTAMP,
updated_at TIMESTAMP,
expires_at TIMESTAMP
);
**Pseudocode (optimistic upsert)**python
def handle_request(idempotency_key, request_body):
req_hash = hash(request_body)
now = now()
expires = now + TTL
# Try insert in_progress; if exists, read and act accordingly
inserted = INSERT ... ON CONFLICT DO NOTHING
if inserted:
UPDATE idempotency SET status='in_progress', request_hash=req_hash, updated_at=now, expires_at=expires
response = process_payment() # actual payment call with external idempotency if available
UPDATE idempotency SET status='completed', response=response, updated_at=now
return response
else:
row = SELECT * FROM idempotency WHERE key = idempotency_key
if row.status == 'completed' and row.request_hash == req_hash:
return row.response
if row.status == 'in_progress':
wait_with_timeout_and_poll(row) # small backoff, then re-read
if row.request_hash != req_hash:
return 409 Conflict
**Locking vs Optimistic Upsert**- Locking (SELECT FOR UPDATE): simple, strong serialization, but reduces throughput and can cause deadlocks under high traffic.- Optimistic upsert: higher concurrency, fewer locks, scales better; must handle races by checking status/hash and using short polling/backoff. Prefer optimistic for mobile APIs with many clients.**Expiry & Pruning**- TTL per entry (e.g., 24–72 hours) set in expires_at.- Background job: DELETE WHERE expires_at < now() OR keep audit logs in cold storage.- For long-running payments keep audit separate; idempotency store can be trimmed aggressively.**Scaling**- Use a fast KV store (Redis with persistence / DynamoDB / CockroachDB) for low latency.- For Redis: use SETNX for initial reservation and a small TTL to avoid stuck in_progress; move final response to durable DB.- Shard keys (consistent hashing) and keep entries compact.**Trade-offs / Notes**- Always combine server-side idempotency with provider-side idempotency tokens for external payment gateways.- Use request hash to detect conflicting semantics (same key, different payload -> 409).- Ensure TTL covers expected client retries but not indefinite storage to control cost.This design balances safety for payments with scalability required for many mobile clients.