- FastAPI REST API with JWT auth - aiogram 3 Telegram bot with admin middleware - APScheduler daily tasks (expiry, reminders, revoke, sync) - SQLAlchemy 2 async ORM with Alembic migrations - Jinja2 admin panel (Dashboard, Users, Payments, Servers, Tariffs) - VPN provider abstraction with MockProvider - Stats service with revenue/subscription analytics - Docker Compose (PostgreSQL + Redis + app) - Healthcheck endpoint
46 lines
1.6 KiB
Python
46 lines
1.6 KiB
Python
import enum
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import DateTime, Enum, ForeignKey, Integer, Numeric, String, func
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from app.database import Base
|
|
|
|
|
|
class PaymentStatus(str, enum.Enum):
|
|
PENDING = "pending"
|
|
CONFIRMED = "confirmed"
|
|
FAILED = "failed"
|
|
REFUNDED = "refunded"
|
|
|
|
|
|
class Payment(Base):
|
|
__tablename__ = "payments"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
user_id: Mapped[int] = mapped_column(
|
|
Integer, ForeignKey("users.id"), nullable=False
|
|
)
|
|
tariff_id: Mapped[int] = mapped_column(
|
|
Integer, ForeignKey("tariffs.id"), nullable=False
|
|
)
|
|
amount: Mapped[float] = mapped_column(Numeric(10, 2), nullable=False)
|
|
currency: Mapped[str] = mapped_column(String(3), default="RUB")
|
|
status: Mapped[PaymentStatus] = mapped_column(
|
|
Enum(PaymentStatus, name="payment_status"), default=PaymentStatus.PENDING
|
|
)
|
|
provider: Mapped[str] = mapped_column(String(32), nullable=False)
|
|
external_id: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now()
|
|
)
|
|
paid_at: Mapped[datetime | None] = mapped_column(
|
|
DateTime(timezone=True), nullable=True
|
|
)
|
|
|
|
user: Mapped["User"] = relationship(back_populates="payments")
|
|
tariff: Mapped["Tariff"] = relationship(back_populates="payments")
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<Payment id={self.id} {self.amount}{self.currency} {self.status.value}>"
|