import uuid from datetime import datetime from decimal import Decimal from enum import StrEnum from typing import Any from sqlalchemy import DateTime, Enum, Integer, Numeric, String, Text, func from sqlalchemy.dialects.postgresql import JSONB, UUID from sqlalchemy.orm import Mapped, mapped_column from app.db import Base class Currency(StrEnum): RUB = "RUB" USD = "USD" EUR = "EUR" class PaymentStatus(StrEnum): PENDING = "pending" SUCCEEDED = "succeeded" FAILED = "failed" class Payment(Base): __tablename__ = "payments" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) amount: Mapped[Decimal] = mapped_column(Numeric(18, 2), nullable=False) currency: Mapped[Currency] = mapped_column( Enum(Currency, name="currency", values_callable=lambda values: [v.value for v in values]), nullable=False, ) description: Mapped[str] = mapped_column(String(500), nullable=False) metadata_: Mapped[dict[str, Any]] = mapped_column( "metadata", JSONB, default=dict, nullable=False ) status: Mapped[PaymentStatus] = mapped_column( Enum( PaymentStatus, name="payment_status", values_callable=lambda values: [v.value for v in values], ), default=PaymentStatus.PENDING, nullable=False, ) idempotency_key: Mapped[str] = mapped_column(String(255), unique=True, nullable=False) webhook_url: Mapped[str] = mapped_column(Text, nullable=False) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), nullable=False ) processed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) webhook_delivered_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) class OutboxEvent(Base): __tablename__ = "outbox" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) aggregate_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), nullable=False, index=True) event_type: Mapped[str] = mapped_column(String(100), nullable=False) payload: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False) attempts: Mapped[int] = mapped_column(Integer, default=0, nullable=False) last_error: Mapped[str | None] = mapped_column(Text) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), nullable=False ) published_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), index=True)