""" Generated by the Lango developer portal. Do not edit by hand. Group: treasury (Treasury) APIs: Payouts v1.0.3, Ledger v2.1.0 Generated: 2026-09-28T10:58:53.456Z """ from __future__ import annotations import base64 import json import time import urllib.error import urllib.parse import urllib.request from typing import Any, Optional class LangoApiError(Exception): """Raised for any non-2xx response. Carries status, error and requestId, same as the gateway.""" def __init__(self, status: int, error: str, message: str, request_id: Optional[str] = None): super().__init__(message) self.status = status self.error = error self.request_id = request_id class TreasuryClient: def __init__(self, consumer_key: str, consumer_secret: str, base_url: str): self.consumer_key = consumer_key self.consumer_secret = consumer_secret self.base_url = base_url.rstrip("/") self._token: Optional[str] = None self._token_expires_at: float = 0 def _get_token(self) -> str: if self._token and time.time() < self._token_expires_at: return self._token data = urllib.parse.urlencode({"grant_type": "client_credentials"}).encode() req = urllib.request.Request(f"{self.base_url}/oauth/v1/token", data=data, method="POST") basic = base64.b64encode(f"{self.consumer_key}:{self.consumer_secret}".encode()).decode() req.add_header("Authorization", f"Basic {basic}") req.add_header("Content-Type", "application/x-www-form-urlencoded") try: with urllib.request.urlopen(req) as res: body = json.loads(res.read()) except urllib.error.HTTPError as e: payload = json.loads(e.read() or b"{}") raise LangoApiError(e.code, payload.get("error", "TOKEN_ERROR"), payload.get("message", "Could not get a sandbox token."), payload.get("requestId")) self._token = body["access_token"] self._token_expires_at = time.time() + body.get("expires_in", 3600) - 60 return self._token def _call(self, method: str, path: str, query: Optional[dict] = None, body: Any = None) -> Any: token = self._get_token() items = {k: v for k, v in (query or {}).items() if v is not None} qs = f"?{urllib.parse.urlencode(items)}" if items else "" req = urllib.request.Request(f"{self.base_url}{path}{qs}", method=method) req.add_header("Authorization", f"Bearer {token}") if body is not None: req.data = json.dumps(body).encode() req.add_header("Content-Type", "application/json") try: with urllib.request.urlopen(req) as res: raw = res.read() return json.loads(raw) if raw else None except urllib.error.HTTPError as e: payload = json.loads(e.read() or b"{}") raise LangoApiError(e.code, payload.get("error", "REQUEST_FAILED"), payload.get("message", str(e)), payload.get("requestId")) def send_payout(self, body: Optional[dict] = None) -> Any: """Send a payout""" return self._call("POST", f"/payouts/v1/send", query=None, body=body) def get_payout(self, payout_id: str) -> Any: """Check a payout""" return self._call("GET", f"/payouts/v1/{urllib.parse.quote(str(payout_id))}", query=None, body=None) def get_transaction(self, transaction_id: str) -> Any: """Look up a transaction""" return self._call("GET", f"/ledger/v2/transactions/{urllib.parse.quote(str(transaction_id))}", query=None, body=None) def reverse_transaction(self, transaction_id: str, body: Optional[dict] = None) -> Any: """Reverse a transaction""" return self._call("POST", f"/ledger/v2/transactions/{urllib.parse.quote(str(transaction_id))}/reversal", query=None, body=body) def get_balance(self, short_code: Optional[str] = None) -> Any: """Read your balances""" return self._call("GET", f"/ledger/v2/balance", query={"shortCode": short_code}, body=None)