from __future__ import annotations import os from typing import Any, Dict import httpx from quart import current_app from config import config from models.order import Order SUMUP_BASE_URL = "https://api.sumup.com/v0.1" def _sumup_settings() -> Dict[str, str]: cfg = config() sumup_cfg = cfg.get("sumup", {}) or {} api_key_env = sumup_cfg.get("api_key_env", "SUMUP_API_KEY") api_key = os.getenv(api_key_env) if not api_key: raise RuntimeError(f"Missing SumUp API key in environment variable {api_key_env}") merchant_code = sumup_cfg.get("merchant_code") prefix = sumup_cfg.get("checkout_prefix", "") if not merchant_code: raise RuntimeError("Missing 'sumup.merchant_code' in app-config.yaml") currency = sumup_cfg.get("currency", "GBP") return { "api_key": api_key, "merchant_code": merchant_code, "currency": currency, "checkout_reference_prefix": prefix, } async def create_checkout( order: Order, redirect_url: str, webhook_url: str | None = None, description: str | None = None, ) -> Dict[str, Any]: settings = _sumup_settings() # Use stored reference if present, otherwise build it checkout_reference = order.sumup_reference or f"{settings['checkout_reference_prefix']}{order.id}" payload: Dict[str, Any] = { "checkout_reference": checkout_reference, "amount": float(order.total_amount), "currency": settings["currency"], "merchant_code": settings["merchant_code"], "description": description or f"Order {order.id} at {current_app.config.get('APP_TITLE', 'Rose Ash')}", "return_url": webhook_url or redirect_url, "redirect_url": redirect_url, "hosted_checkout": {"enabled": True}, } headers = { "Authorization": f"Bearer {settings['api_key']}", "Content-Type": "application/json", } # Optional: log for debugging current_app.logger.info( "Creating SumUp checkout %s for Order %s amount %.2f", checkout_reference, order.id, float(order.total_amount), ) async with httpx.AsyncClient(timeout=15.0) as client: resp = await client.post(f"{SUMUP_BASE_URL}/checkouts", json=payload, headers=headers) if resp.status_code == 409: # Duplicate checkout — retrieve the existing one by reference current_app.logger.warning( "SumUp duplicate checkout for ref %s order %s, fetching existing", checkout_reference, order.id, ) list_resp = await client.get( f"{SUMUP_BASE_URL}/checkouts", params={"checkout_reference": checkout_reference}, headers=headers, ) list_resp.raise_for_status() items = list_resp.json() if isinstance(items, list) and items: return items[0] if isinstance(items, dict) and items.get("items"): return items["items"][0] # Fallback: re-raise original error resp.raise_for_status() if resp.status_code >= 400: current_app.logger.error( "SumUp checkout error for ref %s order %s: %s", checkout_reference, order.id, resp.text, ) resp.raise_for_status() data = resp.json() return data async def get_checkout(checkout_id: str) -> Dict[str, Any]: """Fetch checkout status/details from SumUp.""" settings = _sumup_settings() headers = { "Authorization": f"Bearer {settings['api_key']}", "Content-Type": "application/json", } async with httpx.AsyncClient(timeout=10.0) as client: resp = await client.get(f"{SUMUP_BASE_URL}/checkouts/{checkout_id}", headers=headers) resp.raise_for_status() return resp.json()