Main bug: retrying on all errors includes client errors (4xx) that are usually non-transient. That can turn one bad request into repeated bad requests (and possible duplicate charges if idempotency is weak).
Key issues:
- Don’t retry most
4xx (400, 401, 403, 404, 422).
429 is retryable, but honor Retry-After.
- Retry
5xx, network timeouts, connection resets.
- Require idempotency key on charge/create-payment calls.
- Add exponential backoff + jitter; 3 immediate retries can amplify outages.
def should_retry(err, status):
if err in ("timeout", "connection_reset"): return True
if status == 429: return True
if 500 <= status <= 599: return True
return False # includes most 4xx
for attempt in range(1, 4):
res = send_payment(idempotency_key=key)
if res.ok: return res
if not should_retry(res.error, res.status): raise PaymentError(res)
sleep(backoff_with_jitter(attempt, retry_after=res.headers.get("Retry-After")))
Also log final failure reason and attempt count for observability.