"""Manual adapter — operator-initiated mints (comps, support replacements). There is no webhook to verify and no payload to parse: the operator hands us the buyer details directly via the CLI, and we construct a :class:`SaleEvent` from them. ``source='manual'`` separates these rows from storefront-driven mints in the DB. """ from __future__ import annotations from decimal import Decimal from typing import Any, Optional from app.adapters.base import RefundEvent, SaleEvent class ManualAdapter: source_name = "manual" def verify_webhook(self, *, body: bytes, headers: dict[str, str]) -> bool: return False # manual flows never come through webhooks def parse_sale(self, payload: dict[str, Any]) -> Optional[SaleEvent]: return self.build_sale(**payload) def parse_refund(self, payload: dict[str, Any]) -> Optional[RefundEvent]: return None def build_sale( self, *, name: str, email: str, tier: str, years: int = 1, promotion: Optional[str] = None, amount_paid: Optional[Decimal] = None, currency: Optional[str] = "USD", notes: Optional[str] = None, ) -> SaleEvent: return SaleEvent( source=self.source_name, source_order_id=None, buyer_name=name, buyer_email=email, tier=tier, years=years, promotion=promotion, amount_paid=amount_paid, currency=currency, notes=notes, )