← Back to the library
BackendCore · 7 min

A retry creates two payments

“The client timed out, retried the request, and both attempts reached the payment provider.”

ERROR CODES YOU MAY SEE

A code is a clue. Use its meaning and surrounding evidence to narrow the cause.

23505PostgreSQL SQLSTATE · standard

A write violated a uniqueness constraint (unique_violation).

Inspect the constraint name. A collision on an idempotency key can identify a retry, but other unique constraints can fail too; it does not prove a payment succeeded.

Official reference for 23505 (opens in new tab)Code definition reviewed
ECONNRESETNode.js system errors · platform

The peer forcibly closed the connection.

Correlate client, proxy, and server logs. A reset alone does not reveal whether an operation completed before the connection ended.

Official reference for ECONNRESET (opens in new tab)Code definition reviewed

THE PRINCIPLE

At-most-once UX requires idempotent server behavior, not merely a disabled submit button.

FIRST MOVES

  1. Generate one idempotency key when the user creates the intent; reuse it for every retry.
  2. Persist the key, normalized request hash, status, and response atomically.
  3. Return the stored response for an identical retry; reject reuse with different parameters.
  4. Reconcile ambiguous provider timeouts instead of starting a second payment.

TOOLS: THEN → NOW

Client-only button locksDatabase uniqueness plus provider idempotency keys
Cron-only recoveryDurable workflows and reconciliation workers

PATTERN SNAPSHOT

INSERT INTO payment_attempts (idempotency_key, request_hash, status)
VALUES ($1, $2, 'created')
ON CONFLICT (idempotency_key) DO NOTHING;

-- Fetch the existing attempt before doing external work.

CLOSE THE AI. EXPLAIN THIS.

What should the API do if the same idempotency key arrives with a different amount?

Guide reviewed