Skip to content

[Phase 1] Idempotency Key & Request Replay Safety Layer #66

@syed-reza98

Description

@syed-reza98

Priority: P0 (Critical)

Phase: 1 - E-Commerce Core
Estimate: 2 days
Type: Story

Context

Prevent duplicate side effects (double charges, duplicate orders, inventory double-decrement) by introducing a generic idempotency layer for write-intent endpoints.

Scope

  • Table: IdempotencyKey (key, actorId, route, requestHash, responseJson, status, expiresAt)
  • Middleware: Extract Idempotency-Key header, validate format (uuid or slug), enforce uniqueness per route + actor until expiry
  • Safe replay: Return cached response if prior success; block if in-flight
  • In-flight protection: row-level status PENDING → COMPLETED / ERRORED; consider Redis optimization later
  • Apply to: order create, payment attempt create, refund create, inventory reservation create

Acceptance Criteria

  • Duplicate POST with identical body + Idempotency-Key returns 200 with identical payload (no additional side effects)
  • Concurrent identical POST produces single side effect
  • Conflicting body with same key returns 409 + explanatory error
  • Key expiry configurable (default 24h, min 5m, max 72h)
  • Keys cleaned by scheduled sweep (success + expired) preserving last 7d metrics
  • Metrics emitted: idempotency.replay.count, idempotency.conflict.count, idempotency.inflight.gauge
  • Structured log includes key, route, status, duration

Data Model (Draft)

model IdempotencyKey {
  id           String   @id @default(cuid())
  key          String   @unique
  actorId      String?  // user or system client
  route        String
  requestHash  String
  responseJson String? // minimal JSON snapshot for replay
  status       IdempotencyStatus @default(PENDING)
  expiresAt    DateTime
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt

  @@index([route])
  @@index([expiresAt])
}

enum IdempotencyStatus {
  PENDING
  COMPLETED
  ERRORED
}

Dependencies

Metrics Targets

  • Replay ratio (duplicate vs unique) < 5%
  • Conflict error detection: 100% logged
  • Average lookup latency < 3ms (SQLite dev), < 1ms (Postgres prod) after index tuning

Testing Checklist

  • Duplicate POST same body same key returns cached response
  • Duplicate POST different body same key returns 409
  • Concurrent requests do not double-create order under load (simulate 10 parallel)
  • Expired key allows new side effect

Risk

High financial & integrity impact if absent (score: 17). Prevents double charges & inventory corruption.

References

  • Stripe Idempotency design patterns
  • docs/GITHUB_ISSUES_COMPARISON_ANALYSIS.md

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status

In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions