mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-02-11 06:30:54 +00:00
277 lines
8.0 KiB
Python
277 lines
8.0 KiB
Python
import logging
|
||
from typing import Optional, List
|
||
from datetime import datetime
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from sqlalchemy import select, update, and_
|
||
from sqlalchemy.orm import selectinload
|
||
|
||
from app.database.models import YooKassaPayment, User, Transaction
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
async def create_yookassa_payment(
|
||
db: AsyncSession,
|
||
user_id: int,
|
||
yookassa_payment_id: str,
|
||
amount_kopeks: int,
|
||
currency: str,
|
||
description: str,
|
||
status: str,
|
||
confirmation_url: Optional[str] = None,
|
||
metadata_json: Optional[dict] = None,
|
||
payment_method_type: Optional[str] = None,
|
||
yookassa_created_at: Optional[datetime] = None,
|
||
test_mode: bool = False
|
||
) -> YooKassaPayment:
|
||
|
||
payment = YooKassaPayment(
|
||
user_id=user_id,
|
||
yookassa_payment_id=yookassa_payment_id,
|
||
amount_kopeks=amount_kopeks,
|
||
currency=currency,
|
||
description=description,
|
||
status=status,
|
||
confirmation_url=confirmation_url,
|
||
metadata_json=metadata_json,
|
||
payment_method_type=payment_method_type,
|
||
yookassa_created_at=yookassa_created_at,
|
||
test_mode=test_mode
|
||
)
|
||
|
||
db.add(payment)
|
||
await db.commit()
|
||
await db.refresh(payment)
|
||
|
||
logger.info(f"Создан платеж YooKassa: {yookassa_payment_id} на {amount_kopeks/100}₽ для пользователя {user_id}")
|
||
return payment
|
||
|
||
|
||
async def get_yookassa_payment_by_id(
|
||
db: AsyncSession,
|
||
yookassa_payment_id: str
|
||
) -> Optional[YooKassaPayment]:
|
||
|
||
result = await db.execute(
|
||
select(YooKassaPayment)
|
||
.options(selectinload(YooKassaPayment.user))
|
||
.where(YooKassaPayment.yookassa_payment_id == yookassa_payment_id)
|
||
)
|
||
return result.scalar_one_or_none()
|
||
|
||
|
||
async def get_yookassa_payment_by_local_id(
|
||
db: AsyncSession,
|
||
local_id: int
|
||
) -> Optional[YooKassaPayment]:
|
||
|
||
result = await db.execute(
|
||
select(YooKassaPayment)
|
||
.options(selectinload(YooKassaPayment.user))
|
||
.where(YooKassaPayment.id == local_id)
|
||
)
|
||
return result.scalar_one_or_none()
|
||
|
||
|
||
async def update_yookassa_payment_status(
|
||
db: AsyncSession,
|
||
yookassa_payment_id: str,
|
||
status: str,
|
||
is_paid: bool = False,
|
||
is_captured: bool = False,
|
||
captured_at: Optional[datetime] = None,
|
||
payment_method_type: Optional[str] = None
|
||
) -> Optional[YooKassaPayment]:
|
||
|
||
update_data = {
|
||
"status": status,
|
||
"is_paid": is_paid,
|
||
"is_captured": is_captured,
|
||
"updated_at": datetime.utcnow()
|
||
}
|
||
|
||
if captured_at:
|
||
update_data["captured_at"] = captured_at
|
||
|
||
if payment_method_type:
|
||
update_data["payment_method_type"] = payment_method_type
|
||
|
||
await db.execute(
|
||
update(YooKassaPayment)
|
||
.where(YooKassaPayment.yookassa_payment_id == yookassa_payment_id)
|
||
.values(**update_data)
|
||
)
|
||
await db.commit()
|
||
|
||
result = await db.execute(
|
||
select(YooKassaPayment)
|
||
.options(selectinload(YooKassaPayment.user))
|
||
.where(YooKassaPayment.yookassa_payment_id == yookassa_payment_id)
|
||
)
|
||
payment = result.scalar_one_or_none()
|
||
|
||
if payment:
|
||
logger.info(f"Обновлен статус платежа YooKassa {yookassa_payment_id}: {status}, paid={is_paid}")
|
||
|
||
return payment
|
||
|
||
|
||
async def link_yookassa_payment_to_transaction(
|
||
db: AsyncSession,
|
||
yookassa_payment_id: str,
|
||
transaction_id: int
|
||
) -> Optional[YooKassaPayment]:
|
||
|
||
await db.execute(
|
||
update(YooKassaPayment)
|
||
.where(YooKassaPayment.yookassa_payment_id == yookassa_payment_id)
|
||
.values(transaction_id=transaction_id, updated_at=datetime.utcnow())
|
||
)
|
||
await db.commit()
|
||
|
||
result = await db.execute(
|
||
select(YooKassaPayment)
|
||
.options(selectinload(YooKassaPayment.user), selectinload(YooKassaPayment.transaction))
|
||
.where(YooKassaPayment.yookassa_payment_id == yookassa_payment_id)
|
||
)
|
||
payment = result.scalar_one_or_none()
|
||
|
||
if payment:
|
||
logger.info(f"Платеж YooKassa {yookassa_payment_id} связан с транзакцией {transaction_id}")
|
||
|
||
return payment
|
||
|
||
|
||
async def get_user_yookassa_payments(
|
||
db: AsyncSession,
|
||
user_id: int,
|
||
limit: int = 50,
|
||
offset: int = 0
|
||
) -> List[YooKassaPayment]:
|
||
|
||
result = await db.execute(
|
||
select(YooKassaPayment)
|
||
.options(selectinload(YooKassaPayment.transaction))
|
||
.where(YooKassaPayment.user_id == user_id)
|
||
.order_by(YooKassaPayment.created_at.desc())
|
||
.limit(limit)
|
||
.offset(offset)
|
||
)
|
||
return result.scalars().all()
|
||
|
||
|
||
async def get_pending_yookassa_payments(
|
||
db: AsyncSession,
|
||
user_id: Optional[int] = None,
|
||
limit: int = 100
|
||
) -> List[YooKassaPayment]:
|
||
|
||
query = select(YooKassaPayment).options(selectinload(YooKassaPayment.user))
|
||
|
||
conditions = [YooKassaPayment.status.in_(["pending", "waiting_for_capture"])]
|
||
if user_id:
|
||
conditions.append(YooKassaPayment.user_id == user_id)
|
||
|
||
result = await db.execute(
|
||
query.where(and_(*conditions))
|
||
.order_by(YooKassaPayment.created_at.desc())
|
||
.limit(limit)
|
||
)
|
||
return result.scalars().all()
|
||
|
||
|
||
async def get_succeeded_yookassa_payments_without_transaction(
|
||
db: AsyncSession,
|
||
limit: int = 50
|
||
) -> List[YooKassaPayment]:
|
||
|
||
result = await db.execute(
|
||
select(YooKassaPayment)
|
||
.options(selectinload(YooKassaPayment.user))
|
||
.where(
|
||
and_(
|
||
YooKassaPayment.status == "succeeded",
|
||
YooKassaPayment.is_paid == True,
|
||
YooKassaPayment.transaction_id == None
|
||
)
|
||
)
|
||
.order_by(YooKassaPayment.captured_at.desc())
|
||
.limit(limit)
|
||
)
|
||
return result.scalars().all()
|
||
|
||
|
||
async def delete_yookassa_payment(
|
||
db: AsyncSession,
|
||
yookassa_payment_id: str
|
||
) -> bool:
|
||
|
||
result = await db.execute(
|
||
select(YooKassaPayment)
|
||
.where(YooKassaPayment.yookassa_payment_id == yookassa_payment_id)
|
||
)
|
||
payment = result.scalar_one_or_none()
|
||
|
||
if payment:
|
||
await db.delete(payment)
|
||
await db.commit()
|
||
logger.info(f"Удален платеж YooKassa: {yookassa_payment_id}")
|
||
return True
|
||
|
||
return False
|
||
|
||
|
||
async def get_yookassa_payments_stats(
|
||
db: AsyncSession,
|
||
user_id: Optional[int] = None
|
||
) -> dict:
|
||
|
||
from sqlalchemy import func, case
|
||
|
||
query = select(
|
||
func.count(YooKassaPayment.id).label('total_payments'),
|
||
func.sum(YooKassaPayment.amount_kopeks).label('total_amount_kopeks'),
|
||
func.sum(
|
||
case(
|
||
(YooKassaPayment.status == 'succeeded', YooKassaPayment.amount_kopeks),
|
||
else_=0
|
||
)
|
||
).label('succeeded_amount_kopeks'),
|
||
func.count(
|
||
case(
|
||
(YooKassaPayment.status == 'succeeded', 1),
|
||
else_=None
|
||
)
|
||
).label('succeeded_count'),
|
||
func.count(
|
||
case(
|
||
(YooKassaPayment.status == 'pending', 1),
|
||
else_=None
|
||
)
|
||
).label('pending_count'),
|
||
func.count(
|
||
case(
|
||
(YooKassaPayment.status.in_(['canceled', 'failed']), 1),
|
||
else_=None
|
||
)
|
||
).label('failed_count')
|
||
).select_from(YooKassaPayment)
|
||
|
||
if user_id:
|
||
query = query.where(YooKassaPayment.user_id == user_id)
|
||
|
||
result = await db.execute(query)
|
||
stats = result.first()
|
||
|
||
return {
|
||
'total_payments': stats.total_payments or 0,
|
||
'total_amount_kopeks': stats.total_amount_kopeks or 0,
|
||
'total_amount_rubles': (stats.total_amount_kopeks or 0) / 100,
|
||
'succeeded_amount_kopeks': stats.succeeded_amount_kopeks or 0,
|
||
'succeeded_amount_rubles': (stats.succeeded_amount_kopeks or 0) / 100,
|
||
'succeeded_count': stats.succeeded_count or 0,
|
||
'pending_count': stats.pending_count or 0,
|
||
'failed_count': stats.failed_count or 0,
|
||
'success_rate': (stats.succeeded_count / stats.total_payments * 100) if stats.total_payments > 0 else 0
|
||
}
|