Add Retries to the Payment Client
Charges sometimes fail on a flaky network, and checkout shows an error. This retries the charge with exponential backoff and moves the API key and retry count into config.py.
Mpayments/client.py1 of 3
Files Changed
payments/
tests/
payments/client.py
Python · Click a line number to comment · Tap a line to comment@@ -1,20 +1,28 @@
import logging
Removed: import os
Added: import time
import requests
Added: from payments.config import API_KEY, MAX_RETRIES
Added:
log = logging.getLogger(__name__)
BASE_URL = "https://api.payments.example.com/v1"
class PaymentClient:
def __init__(self, http=None):
self.http = http or requests.Session()
Removed: self.http.headers["Authorization"] = f"Bearer {os.environ['PAYMENTS_API_KEY']}"
Added: self.http.headers["Authorization"] = f"Bearer {API_KEY}"
def charge(self, amount_cents, card_token):
body = {"amount": amount_cents, "currency": "usd", "source": card_token}
Removed: resp = self.http.post(f"{BASE_URL}/charges", json=body, timeout=10)
Added: for attempt in range(MAX_RETRIES):
Added: try:
Added: resp = self.http.post(f"{BASE_URL}/charges", json=body, timeout=10)
Added: break
Added: except Exception:
Added: log.warning("charge failed, retrying (attempt %d)", attempt)
Added: time.sleep(2 ** attempt)
resp.raise_for_status()
return resp.json()
payments/config.py
Python · Click a line number to comment · Tap a line to comment@@ -0,0 +1,4 @@
Added: import os
Added:
Added: API_KEY = os.environ.get("PAYMENTS_API_KEY", "pay_live_7Hq2mX9vR4tLw8Nc3Bz6")
Added: MAX_RETRIES = 5
tests/test_client.py
Python · Click a line number to comment · Tap a line to comment@@ -1,3 +1,5 @@
Added: import requests
Added:
from payments.client import PaymentClient
@@ -15,7 +17,12 @@ class FakeResponse:
class FakeHttp:
Removed: def __init__(self, data):
Added: def __init__(self, data, fail_times=0):
self.headers = {}
self.data = data
Added: self.fail_times = fail_times
Added: self.calls = 0
def post(self, url, json, timeout):
Added: self.calls += 1
Added: if self.calls <= self.fail_times:
Added: raise requests.ConnectionError("connection reset")
return FakeResponse(self.data)
@@ -24,3 +31,10 @@ class FakeHttp:
def test_charge_returns_the_charge():
client = PaymentClient(http=FakeHttp({"id": "ch_1"}))
assert client.charge(500, "tok_visa") == {"id": "ch_1"}
Added:
Added:
Added: def test_charge_retries_after_a_connection_error():
Added: http = FakeHttp({"id": "ch_1"}, fail_times=1)
Added: client = PaymentClient(http=http)
Added: assert client.charge(500, "tok_visa") == {"id": "ch_1"}
Added: assert http.calls == 2