"""Client de l'API SumUp — paiement de la cotisation (voir CLAUDE.md § 2, `FormTemplate`, et `app/api/forms.py`). `create_checkout`/`get_checkout` sont les points d'injection uniques pour les tests (remplacés comme `app.forms.captcha.verify_hcaptcha`). Best effort, comme l'envoi d'emails : toute erreur réseau/HTTP est journalisée (code/ classe d'exception seulement, jamais de PII) et renvoyée comme `None` — l'inscription au formulaire public n'échoue jamais pour autant. """ import logging from dataclasses import dataclass import httpx logger = logging.getLogger("app.sumup") _BASE_URL = "https://api.sumup.com" _TIMEOUT_SECONDS = 10.0 @dataclass(frozen=True) class SumUpCheckout: id: str status: str # PENDING / PAID / FAILED / EXPIRED (voir FeePaymentStatus) hosted_checkout_url: str | None def _call(method: str, path: str, api_key: str, json: dict | None = None) -> dict | None: try: resp = httpx.request( method, f"{_BASE_URL}{path}", json=json, headers={"Authorization": f"Bearer {api_key}"}, timeout=_TIMEOUT_SECONDS, ) resp.raise_for_status() return resp.json() except httpx.HTTPError as exc: logger.warning( "Échec de l'appel API SumUp (%s %s) : %s", method, path, exc.__class__.__name__ ) return None def _to_checkout(data: dict) -> SumUpCheckout: return SumUpCheckout( id=data["id"], status=data["status"], hosted_checkout_url=data.get("hosted_checkout_url"), ) def create_checkout( *, api_key: str, merchant_code: str, amount: float, currency: str, checkout_reference: str, description: str, return_url: str, redirect_url: str, ) -> SumUpCheckout | None: """Crée un checkout hébergé (`hosted_checkout.enabled=true`) : la page de paiement SumUp est à `hosted_checkout_url`. `None` en cas d'échec (réseau, identifiants invalides…).""" data = _call( "POST", "/v0.1/checkouts", api_key, json={ "checkout_reference": checkout_reference, "amount": amount, "currency": currency, "merchant_code": merchant_code, "description": description, "return_url": return_url, "redirect_url": redirect_url, "hosted_checkout": {"enabled": True}, }, ) return _to_checkout(data) if data is not None else None def get_checkout(*, api_key: str, checkout_id: str) -> SumUpCheckout | None: """Relit l'état réel d'un checkout — un webhook SumUp ne donne qu'un identifiant, jamais un statut de confiance en lui-même (voir la doc SumUp sur les webhooks et `app/api/forms.py:sumup_webhook`).""" data = _call("GET", f"/v0.1/checkouts/{checkout_id}", api_key) return _to_checkout(data) if data is not None else None