from __future__ import annotations
from collections.abc import Awaitable, Callable, Mapping, Sequence
from dataclasses import dataclass, field
from decimal import Decimal
from enum import StrEnum
from typing import Protocol, TypeAlias, TypedDict
type Metadata = Mapping[str, object]
WebhookPayload: TypeAlias = dict[str, object]
class PaymentStatus(StrEnum):
PENDING = "pending"
CAPTURED = "captured"
class PaymentEvent(TypedDict, total=False):
id: str
amount: str
metadata: Metadata
@dataclass(slots=True)
class Payment:
id: str
amount: Decimal
status: PaymentStatus
metadata: Metadata = field(default_factory=dict)
class PaymentGateway(Protocol):
async def authorize(self, payment: Payment) -> PaymentEvent: ...
async def capture(self, payment_id: str) -> PaymentEvent: ...
AuditSink: TypeAlias = Callable[[PaymentEvent], Awaitable[None]]
class PaymentService:
def __init__(self, gateway: PaymentGateway, audit: AuditSink) -> None:
self._gateway = gateway
self._audit = audit
async def authorize_order(
self,
order_id: str,
amount: Decimal,
tags: Sequence[str],
) -> Payment:
payment = Payment(
id=order_id,
amount=amount,
status=PaymentStatus.PENDING,
metadata={"tags": list(tags)},
)
event = await self._gateway.authorize(payment)
await self._audit(event)
return payment
async def capture(self, payment: Payment) -> PaymentEvent:
if payment.status is PaymentStatus.CAPTURED:
return {"id": payment.id}
return await self._gateway.capture(payment.id)