- 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
108 lines
3.3 KiB
Python
108 lines
3.3 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, status
|
|
|
|
from app.api.deps import (
|
|
get_payment_service,
|
|
get_billing_service,
|
|
get_notification_service,
|
|
)
|
|
from app.schemas.payment import PaymentRead, PaymentCreate, PaymentConfirm
|
|
from app.services import PaymentService, BillingService, NotificationService
|
|
|
|
router = APIRouter(prefix="/payments", tags=["Payments"])
|
|
|
|
|
|
@router.post("", response_model=PaymentRead, status_code=status.HTTP_201_CREATED)
|
|
async def create_payment(
|
|
body: PaymentCreate,
|
|
payment_svc: PaymentService = Depends(get_payment_service),
|
|
):
|
|
try:
|
|
return await payment_svc.create(
|
|
user_id=body.user_id,
|
|
tariff_id=body.tariff_id,
|
|
provider=body.provider,
|
|
amount=body.amount,
|
|
currency=body.currency,
|
|
external_id=body.external_id,
|
|
)
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
@router.get("/{payment_id}", response_model=PaymentRead)
|
|
async def get_payment(
|
|
payment_id: int,
|
|
payment_svc: PaymentService = Depends(get_payment_service),
|
|
):
|
|
payment = await payment_svc.get_by_id(payment_id)
|
|
if payment is None:
|
|
raise HTTPException(status_code=404, detail="Payment not found")
|
|
return payment
|
|
|
|
|
|
@router.get("/by-external/{external_id}", response_model=PaymentRead)
|
|
async def get_payment_by_external(
|
|
external_id: str,
|
|
payment_svc: PaymentService = Depends(get_payment_service),
|
|
):
|
|
payment = await payment_svc.get_by_external_id(external_id)
|
|
if payment is None:
|
|
raise HTTPException(status_code=404, detail="Payment not found")
|
|
return payment
|
|
|
|
|
|
@router.post("/{payment_id}/confirm", response_model=PaymentRead)
|
|
async def confirm_payment(
|
|
payment_id: int,
|
|
body: PaymentConfirm = PaymentConfirm(),
|
|
payment_svc: PaymentService = Depends(get_payment_service),
|
|
billing_svc: BillingService = Depends(get_billing_service),
|
|
notification_svc: NotificationService = Depends(get_notification_service),
|
|
):
|
|
try:
|
|
payment = await payment_svc.confirm(payment_id, body.external_id)
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
await billing_svc.activate_subscription(
|
|
payment.user_id, payment.tariff_id, payment.id
|
|
)
|
|
|
|
await notification_svc.send_success(
|
|
payment.user_id,
|
|
"Payment confirmed",
|
|
f"Payment {payment.id} confirmed. Subscription activated.",
|
|
)
|
|
|
|
return payment
|
|
|
|
|
|
@router.post("/{payment_id}/fail", response_model=PaymentRead)
|
|
async def fail_payment(
|
|
payment_id: int,
|
|
payment_svc: PaymentService = Depends(get_payment_service),
|
|
):
|
|
try:
|
|
return await payment_svc.fail(payment_id)
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
@router.post("/{payment_id}/refund", response_model=PaymentRead)
|
|
async def refund_payment(
|
|
payment_id: int,
|
|
payment_svc: PaymentService = Depends(get_payment_service),
|
|
):
|
|
try:
|
|
return await payment_svc.refund(payment_id)
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
@router.get("/user/{user_id}", response_model=list[PaymentRead])
|
|
async def get_user_payments(
|
|
user_id: int,
|
|
payment_svc: PaymentService = Depends(get_payment_service),
|
|
):
|
|
return await payment_svc.get_user_payments(user_id)
|