DEV Community

Payneteasy
Payneteasy

Posted on

A timeout is not a decline

A request to the processor times out after 30 seconds. Your code catches the exception and shows the user an error. But the processor might have received the request, processed it, and returned success — the response just never made it back before your timeout fired.

Now you have three states, not two: success, failure, unknown. Most integration code only handles the first two. Retry logic reuses the idempotency key (correct), fires again (also correct) — but the UI already told the customer "payment failed," so they open a new tab and try with a fresh key. Now there are two authorizations against the same card for the same order. Idempotency did exactly what it was built for: protecting against duplicate keys, not duplicate intent.

Fixed this by adding a mandatory reconciliation query before any retry: on timeout, hit a status endpoint with the same key before touching the UI state. Added about 400ms to the worst-case path. Phantom duplicate authorizations from flaky-network merchants dropped to nearly zero — the number matters less than the fact the fix required a network call we'd been skipping to save latency.

Anyone else model "timeout" as its own state in the payment state machine, or does it collapse into failure by default in your stack?

Top comments (0)