"""Source-adapter interface. The Mint API speaks only the normalized event types defined here. Each storefront has its own adapter that: - Verifies the storefront's webhook signature in its native format. - Parses the storefront's payload into a :class:`SaleEvent` or :class:`RefundEvent`. - Maps the storefront's product/variant IDs to a license tier via the per-source config in :mod:`app.adapters.config`. Adding a new source (Lemon Squeezy, Stripe, Paddle) is one new module that implements :class:`SourceAdapter`. The Mint API and DB do not change. """ from __future__ import annotations from dataclasses import dataclass, field from decimal import Decimal from typing import Any, Optional, Protocol @dataclass(frozen=True) class SaleEvent: """A storefront sale, normalized. The Mint API consumes this directly — it never reaches into the raw storefront payload. Anything storefront-specific that's worth keeping is preserved in :attr:`raw_payload` for audit. """ source: str # e.g. "gumroad", "manual" source_order_id: Optional[str] # storefront's order ID; None for manual mints buyer_name: str buyer_email: str tier: str # mapped from product/variant years: int = 1 promotion: Optional[str] = None amount_paid: Optional[Decimal] = None currency: Optional[str] = "USD" notes: Optional[str] = None raw_payload: dict = field(default_factory=dict) @dataclass(frozen=True) class RefundEvent: """A storefront refund — marks an existing license revoked.""" source: str source_order_id: str reason: Optional[str] = None raw_payload: dict = field(default_factory=dict) class SourceAdapter(Protocol): """Interface every storefront adapter implements.""" source_name: str def verify_webhook(self, *, body: bytes, headers: dict[str, str]) -> bool: """Return True iff the request came from the legitimate storefront.""" ... def parse_sale(self, payload: dict[str, Any]) -> Optional[SaleEvent]: """Return a :class:`SaleEvent` if *payload* is a sale, else None.""" ... def parse_refund(self, payload: dict[str, Any]) -> Optional[RefundEvent]: """Return a :class:`RefundEvent` if *payload* is a refund, else None.""" ...