Implements Stripe's idempotency mechanism to prevent duplicate requests.
Stripe stores idempotency keys for 24 hours. Requests with the same Idempotency-Key header return the cached response without re-executing.
Usage
The PaperTiger.Plugs.Idempotency plug automatically handles this for POST requests.
Implementation
- Stores responses in ETS keyed by idempotency key
- TTL: 24 hours (matches Stripe)
- Cleanup runs hourly to remove expired entries
Examples
# Client retries request with same key
headers = [{"idempotency-key", "req_123"}]
# First request executes and caches response
Stripe.Charge.create(%{...}, headers: headers)
# Second request returns cached response (no duplicate charge)
Stripe.Charge.create(%{...}, headers: headers)
Summary
Functions
Checks if a request with this idempotency key has been processed.
Returns a specification to start this module under a supervisor.
Clears all idempotency keys.
Clears idempotency keys for a specific namespace.
Starts the Idempotency GenServer.
Stores a response for the given idempotency key.
Functions
Checks if a request with this idempotency key has been processed.
Returns:
{:cached, response}- Key exists, return cached response:new_request- Key doesn't exist, proceed with request (and reserves the key atomically):in_progress- Another request with this key is currently processing
Race Condition Protection
Uses atomic ETS insert_new to prevent race conditions. If two requests
arrive simultaneously with the same key, only one will get :new_request,
the other will get :in_progress and should retry or wait.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec clear() :: :ok
Clears all idempotency keys.
Useful for test cleanup.
@spec clear_namespace(pid() | :global) :: :ok
Clears idempotency keys for a specific namespace.
Used by PaperTiger.Test to clean up after each test.
@spec start_link(keyword()) :: GenServer.on_start()
Starts the Idempotency GenServer.
Stores a response for the given idempotency key.
The response will be cached for 24 hours.