"""Minimal {{APP_NAME}} API client for Python 3.10+ (pip install requests). gw = GatewayClient(os.environ["GATEWAY_API_KEY"]) user = gw.create_user("10025", "Asha Patel", "asha@example.com") print(user["deposit_wallet"]["deposit_address"]) Amounts are strings: convert with decimal.Decimal, never float(). """ import time from decimal import Decimal from urllib.parse import quote import requests class GatewayError(Exception): pass class GatewayClient: def __init__(self, api_key: str, base_url: str = "{{BASE_URL}}"): self.base_url = base_url self.session = requests.Session() self.session.headers.update({"Authorization": f"Bearer {api_key}", "Accept": "application/json"}) def create_user(self, external_id: str, name: str, email: str | None = None) -> dict: """Idempotent per external_id: safe to call every time the customer opens your payment page.""" body = {"external_id": external_id, "name": name} if email: body["email"] = email return self._request("POST", "/api/v1/users", body, idempotency_key=f"user-{external_id}") def deposit_address(self, external_id: str) -> str: return self._request("POST", f"/api/v1/users/{quote(external_id)}/deposit-wallet")["deposit_address"] def portal_link(self, external_id: str) -> str: return self._request("POST", f"/api/v1/users/{quote(external_id)}/portal-link")["url"] def deposits(self, since_id: int = 0, status: str = "CONFIRMED", limit: int = 100) -> dict: return self._request("GET", f"/api/v1/deposits?since_id={since_id}&status={status}&limit={limit}") def balance(self) -> Decimal: return Decimal(self._request("GET", "/api/v1/balance")["balance"]) def _request(self, method: str, path: str, body: dict | None = None, idempotency_key: str | None = None) -> dict: headers = {"Idempotency-Key": idempotency_key} if idempotency_key else {} for attempt in range(1, 5): try: r = self.session.request(method, self.base_url + path, json=body, headers=headers, timeout=20) except requests.RequestException: if attempt == 4: raise time.sleep(2 ** attempt) continue data = r.json() if r.content else {} # retry 429 / 5xx (POSTs are safe to retry thanks to the Idempotency-Key) if (r.status_code == 429 or r.status_code >= 500) and attempt < 4: time.sleep(data.get("error", {}).get("retry_after", 2 ** attempt)) continue if not r.ok: raise GatewayError(f"{r.status_code}: {data.get('error', {}).get('message', 'no response')}") return data raise GatewayError("unreachable")