Files
remnawave-bedolaga-telegram…/app/database/crud/freekassa.py
T
Fringg 1f0fef114b refactor: complete structlog migration with contextvars, kwargs, and logging hardening
- Add ContextVarsMiddleware for automatic user_id/chat_id/username binding
  via structlog contextvars (aiogram) and http_method/http_path (FastAPI)
- Use bound_contextvars() context manager instead of clear_contextvars()
  to safely restore previous state instead of wiping all context
- Register ContextVarsMiddleware as outermost middleware (before GlobalError)
  so all error logs include user context
- Replace structlog.get_logger() with structlog.get_logger(__name__) across
  270 calls in 265 files for meaningful logger names
- Switch wrapper_class from BoundLogger to make_filtering_bound_logger()
  for pre-processor level filtering (performance optimization)
- Migrate 1411 %-style positional arg logger calls to structlog kwargs
  style across 161 files via AST script
- Migrate log_rotation_service.py from stdlib logging to structlog
- Add payment module prefixes to TelegramNotifierProcessor.IGNORED_LOGGER_PREFIXES
  and ExcludePaymentFilter.PAYMENT_MODULES to prevent payment data leaking
  to Telegram notifications and general log files
- Fix LoggingMiddleware: add from_user null-safety for channel posts,
  switch time.time() to time.monotonic() for duration measurement
- Remove duplicate logger assignments in purchase.py, config.py,
  inline.py, and admin/payments.py
2026-02-16 09:18:12 +03:00

146 lines
4.7 KiB
Python

"""CRUD операции для платежей Freekassa."""
import json
from datetime import datetime
import structlog
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database.models import FreekassaPayment
logger = structlog.get_logger(__name__)
async def create_freekassa_payment(
db: AsyncSession,
*,
user_id: int,
order_id: str,
amount_kopeks: int,
currency: str = 'RUB',
description: str | None = None,
payment_url: str | None = None,
expires_at: datetime | None = None,
metadata_json: str | None = None,
) -> FreekassaPayment:
"""Создает запись о платеже Freekassa."""
payment = FreekassaPayment(
user_id=user_id,
order_id=order_id,
amount_kopeks=amount_kopeks,
currency=currency,
description=description,
payment_url=payment_url,
expires_at=expires_at,
metadata_json=json.loads(metadata_json) if metadata_json else None,
status='pending',
is_paid=False,
)
db.add(payment)
await db.commit()
await db.refresh(payment)
logger.info('Создан платеж Freekassa: order_id=, user_id', order_id=order_id, user_id=user_id)
return payment
async def get_freekassa_payment_by_order_id(db: AsyncSession, order_id: str) -> FreekassaPayment | None:
"""Получает платеж по order_id."""
result = await db.execute(select(FreekassaPayment).where(FreekassaPayment.order_id == order_id))
return result.scalar_one_or_none()
async def get_freekassa_payment_by_fk_order_id(db: AsyncSession, freekassa_order_id: str) -> FreekassaPayment | None:
"""Получает платеж по ID от Freekassa (intid)."""
result = await db.execute(select(FreekassaPayment).where(FreekassaPayment.freekassa_order_id == freekassa_order_id))
return result.scalar_one_or_none()
async def get_freekassa_payment_by_id(db: AsyncSession, payment_id: int) -> FreekassaPayment | None:
"""Получает платеж по ID."""
result = await db.execute(select(FreekassaPayment).where(FreekassaPayment.id == payment_id))
return result.scalar_one_or_none()
async def update_freekassa_payment_status(
db: AsyncSession,
payment: FreekassaPayment,
*,
status: str,
is_paid: bool = False,
freekassa_order_id: str | None = None,
payment_system_id: int | None = None,
callback_payload: dict | None = None,
transaction_id: int | None = None,
) -> FreekassaPayment:
"""Обновляет статус платежа."""
payment.status = status
payment.is_paid = is_paid
payment.updated_at = datetime.utcnow()
if is_paid:
payment.paid_at = datetime.utcnow()
if freekassa_order_id:
payment.freekassa_order_id = freekassa_order_id
if payment_system_id is not None:
payment.payment_system_id = payment_system_id
if callback_payload:
payment.callback_payload = callback_payload
if transaction_id:
payment.transaction_id = transaction_id
await db.commit()
await db.refresh(payment)
logger.info(
'Обновлен статус платежа Freekassa: order_id=, status=, is_paid',
order_id=payment.order_id,
status=status,
is_paid=is_paid,
)
return payment
async def get_pending_freekassa_payments(db: AsyncSession, user_id: int) -> list[FreekassaPayment]:
"""Получает незавершенные платежи пользователя."""
result = await db.execute(
select(FreekassaPayment).where(
FreekassaPayment.user_id == user_id,
FreekassaPayment.status == 'pending',
FreekassaPayment.is_paid == False,
)
)
return list(result.scalars().all())
async def get_user_freekassa_payments(
db: AsyncSession,
user_id: int,
limit: int = 10,
offset: int = 0,
) -> list[FreekassaPayment]:
"""Получает платежи пользователя с пагинацией."""
result = await db.execute(
select(FreekassaPayment)
.where(FreekassaPayment.user_id == user_id)
.order_by(FreekassaPayment.created_at.desc())
.limit(limit)
.offset(offset)
)
return list(result.scalars().all())
async def get_expired_pending_payments(
db: AsyncSession,
) -> list[FreekassaPayment]:
"""Получает просроченные платежи в статусе pending."""
now = datetime.utcnow()
result = await db.execute(
select(FreekassaPayment).where(
FreekassaPayment.status == 'pending',
FreekassaPayment.is_paid == False,
FreekassaPayment.expires_at < now,
)
)
return list(result.scalars().all())