Skip to content

Retrying a timed out POST can repeat a payment operation #7

Description

@sveneberth

UnzerClient._request retries a request whenever it runs into a read timeout:

except (TimeoutError, requests.exceptions.ReadTimeout):
    logger.exception("Caught TimeoutError")
    continue

The retry does not distinguish between HTTP methods. For a GET that is harmless, but a
timed out POST may well have been carried out by the API -- only the response never made
it back in time. The next attempt then repeats an operation that already happened.

How it shows

Seen with POST /v1/payments/authorize for paylater-installment, whose credit check
regularly takes longer than the former 5 s timeout:

  1. attempt 1 times out, while the API completes the authorization
  2. attempt 2 sends the same payload and is rejected with API.320.200.152 (the basket has
    already been used)
  3. _request raises, so the caller sees a failed checkout -- although a successful
    authorization exists on the Unzer side

With authorize this leaves an orphaned reservation behind. With charge it would be a
payment the caller does not know about, and a further attempt could charge twice.

The timeout has meanwhile been raised to 30 s, which makes the situation less likely but
does not remove it: any slow response still ends in the same place.

Options to evaluate

  • retry only idempotent methods (GET, HEAD) after a timeout and let the exception
    through for POST/PUT/DELETE, so the caller can decide
  • the same question applies to the 5xx branch a few lines below, which also continues
  • if the API offers an idempotency key, send one and keep retrying as before
  • alternatively: on a timed out payment call, look the payment up by orderId before
    giving up, and hand back what is already there

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions