-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
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-Keyheader, 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
- Enables: reliable payment/refund operations (links PaymentAttempt issue [Phase 1] PaymentAttempt & PaymentTransaction State Machine #63)
- Will integrate with future Rate Limiting (P0) for composite protection
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
Copilot