feat: add status and payment capability contracts

This commit is contained in:
2026-08-19 12:33:34 +02:00
parent 8a925782ab
commit 5a2e99f496
5 changed files with 332 additions and 0 deletions
@@ -0,0 +1,81 @@
from __future__ import annotations
from collections.abc import Mapping
from datetime import datetime
from typing import Protocol, runtime_checkable
CAPABILITY_APPLICATION_STATUS_PROJECTION = "application_status.projection"
@runtime_checkable
class ApplicationStatusProjectionProvider(Protocol):
"""Bounded applicant-status access without exposing the owning module's data."""
def tenant_id_for_tracking_id(
self,
session: object,
*,
tracking_id: str,
) -> str | None:
...
def public_access_challenge(
self,
session: object,
*,
tracking_id: str,
) -> Mapping[str, object]:
...
def get_authenticated_projection(
self,
session: object,
principal: object,
*,
tracking_id: str,
observed_at: datetime,
) -> Mapping[str, object]:
...
def get_public_projection(
self,
session: object,
*,
tracking_id: str,
token: str | None,
observed_at: datetime,
) -> Mapping[str, object]:
...
def request_email_link(
self,
session: object,
*,
tracking_id: str,
email: str,
requested_at: datetime,
) -> bool:
...
def application_status_projection_provider(
registry: object | None,
) -> ApplicationStatusProjectionProvider | None:
if registry is None or not hasattr(registry, "has_capability"):
return None
if not registry.has_capability(CAPABILITY_APPLICATION_STATUS_PROJECTION):
return None
capability = registry.capability(CAPABILITY_APPLICATION_STATUS_PROJECTION)
return (
capability
if isinstance(capability, ApplicationStatusProjectionProvider)
else None
)
__all__ = [
"ApplicationStatusProjectionProvider",
"CAPABILITY_APPLICATION_STATUS_PROJECTION",
"application_status_projection_provider",
]
+87
View File
@@ -0,0 +1,87 @@
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass, field
from datetime import datetime
from typing import Protocol, runtime_checkable
from govoplan_core.core.institutional import EvidenceReference
CAPABILITY_PAYMENT_REQUESTS = "payments.requests"
@dataclass(frozen=True, slots=True)
class PaymentRequestCommand:
tenant_id: str
source_module: str
source_resource_type: str
source_resource_id: str
amount_minor: int
currency: str
subject: str
idempotency_key: str
requested_at: datetime
requested_by_ref: str
due_at: datetime | None = None
context_refs: Mapping[str, str] = field(default_factory=dict)
metadata: Mapping[str, object] = field(default_factory=dict)
@dataclass(frozen=True, slots=True)
class ManualPaymentReconciliationCommand:
tenant_id: str
payment_id: str
amount_minor: int
currency: str
transaction_reference: str
evidence_ref: EvidenceReference
idempotency_key: str
received_at: datetime
recorded_at: datetime
recorded_by_ref: str
metadata: Mapping[str, object] = field(default_factory=dict)
@runtime_checkable
class PaymentRequestProvider(Protocol):
def request_payment(
self,
session: object,
command: PaymentRequestCommand,
) -> Mapping[str, object]:
...
def get_payment(
self,
session: object,
*,
tenant_id: str,
payment_id: str,
) -> Mapping[str, object] | None:
...
def reconcile_manual_payment(
self,
session: object,
command: ManualPaymentReconciliationCommand,
) -> Mapping[str, object]:
...
def payment_request_provider(registry: object | None) -> PaymentRequestProvider | None:
if registry is None or not hasattr(registry, "has_capability"):
return None
if not registry.has_capability(CAPABILITY_PAYMENT_REQUESTS):
return None
capability = registry.capability(CAPABILITY_PAYMENT_REQUESTS)
return capability if isinstance(capability, PaymentRequestProvider) else None
__all__ = [
"CAPABILITY_PAYMENT_REQUESTS",
"ManualPaymentReconciliationCommand",
"PaymentRequestCommand",
"PaymentRequestProvider",
"payment_request_provider",
]