Files
smolkik-code 7c7c88621d Initial commit: VPN Control Panel
- 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
2026-07-05 17:50:11 +07:00

110 lines
3.6 KiB
Python

from datetime import datetime, timezone
from loguru import logger
from app.models.payment import Payment, PaymentStatus
from app.repositories.payment import PaymentRepository
from app.repositories.tariff import TariffRepository
from app.repositories.user import UserRepository
class PaymentService:
def __init__(
self,
payment_repo: PaymentRepository,
user_repo: UserRepository,
tariff_repo: TariffRepository,
):
self.payment_repo = payment_repo
self.user_repo = user_repo
self.tariff_repo = tariff_repo
async def create(
self,
user_id: int,
tariff_id: int,
provider: str,
amount: float,
currency: str = "RUB",
external_id: str | None = None,
) -> Payment:
user = await self.user_repo.get(user_id)
if user is None:
raise ValueError(f"User not found: {user_id}")
tariff = await self.tariff_repo.get(tariff_id)
if tariff is None:
raise ValueError(f"Tariff not found: {tariff_id}")
payment = await self.payment_repo.create(
user_id=user_id,
tariff_id=tariff_id,
amount=amount,
currency=currency,
status=PaymentStatus.PENDING,
provider=provider,
external_id=external_id,
)
logger.info("Payment created: id={} user={} amount={}", payment.id, user_id, amount)
return payment
async def confirm(
self, payment_id: int, external_id: str | None = None
) -> Payment:
payment = await self.payment_repo.get(payment_id)
if payment is None:
raise ValueError(f"Payment not found: {payment_id}")
if payment.status != PaymentStatus.PENDING:
raise ValueError(
f"Cannot confirm payment {payment_id}: "
f"current status is {payment.status.value}"
)
payment = await self.payment_repo.update(
payment_id,
status=PaymentStatus.CONFIRMED,
paid_at=datetime.now(timezone.utc),
external_id=external_id or payment.external_id,
)
logger.info("Payment confirmed: id={} user={}", payment_id, payment.user_id)
return payment
async def fail(self, payment_id: int) -> Payment:
payment = await self.payment_repo.get(payment_id)
if payment is None:
raise ValueError(f"Payment not found: {payment_id}")
payment = await self.payment_repo.update(
payment_id, status=PaymentStatus.FAILED
)
logger.info("Payment failed: id={} user={}", payment_id, payment.user_id)
return payment
async def refund(self, payment_id: int) -> Payment:
payment = await self.payment_repo.get(payment_id)
if payment is None:
raise ValueError(f"Payment not found: {payment_id}")
payment = await self.payment_repo.update(
payment_id, status=PaymentStatus.REFUNDED
)
logger.info("Payment refunded: id={} user={}", payment_id, payment.user_id)
return payment
async def get_by_id(self, payment_id: int) -> Payment | None:
return await self.payment_repo.get(payment_id)
async def get_by_external_id(self, external_id: str) -> Payment | None:
return await self.payment_repo.get_by_external_id(external_id)
async def get_user_payments(self, user_id: int) -> list[Payment]:
return await self.payment_repo.get_by_user_id(user_id)
async def get_pending(self) -> list[Payment]:
return await self.payment_repo.get_pending_payments()