From f32ebc95cd8b0c34a92fd5a7f01882c05b22544a Mon Sep 17 00:00:00 2001 From: Vladless Date: Fri, 26 Dec 2025 18:53:23 +0300 Subject: [PATCH] Export user payments to CSV --- database/payments.py | 31 +++++++++++++ handlers/admin/users/keyboard.py | 6 +++ handlers/admin/users/users_balance.py | 16 +++++++ utils/csv_export.py | 65 +++++++++++++++++++++++++++ 4 files changed, 118 insertions(+) diff --git a/database/payments.py b/database/payments.py index 07c4b44a..ceb380e3 100644 --- a/database/payments.py +++ b/database/payments.py @@ -180,3 +180,34 @@ async def cancel_expired_pending_payments(session: AsyncSession) -> int: await session.commit() affected = res.rowcount or 0 return affected + + +async def get_all_payments( + session: AsyncSession, + tg_id: int, + statuses: list[str] | None = None, +) -> list[dict]: + query = select(Payment).where(Payment.tg_id == tg_id) + + if statuses: + query = query.where(Payment.status.in_(statuses)) + + query = query.order_by(Payment.created_at.desc()) + + result = await session.execute(query) + payments = result.scalars().all() + return [ + { + "id": p.id, + "tg_id": p.tg_id, + "amount": p.amount, + "currency": p.currency, + "status": p.status, + "payment_system": p.payment_system, + "payment_id": p.payment_id, + "created_at": p.created_at, + "metadata": p.metadata_, + "original_amount": p.original_amount, + } + for p in payments + ] diff --git a/handlers/admin/users/keyboard.py b/handlers/admin/users/keyboard.py index f49bf5bd..792dea99 100644 --- a/handlers/admin/users/keyboard.py +++ b/handlers/admin/users/keyboard.py @@ -142,6 +142,12 @@ async def build_users_balance_kb(session: AsyncSession, tg_id: int) -> InlineKey callback_data=AdminUserEditorCallback(action="users_balance_set", tg_id=tg_id).pack(), ) ) + builder.row( + InlineKeyboardButton( + text="Выгрузить платежи", + callback_data=AdminUserEditorCallback(action="users_balance_export", tg_id=tg_id).pack(), + ) + ) builder.row(build_editor_back_btn(tg_id, True)) diff --git a/handlers/admin/users/users_balance.py b/handlers/admin/users/users_balance.py index 161f86fa..edf3fbdb 100644 --- a/handlers/admin/users/users_balance.py +++ b/handlers/admin/users/users_balance.py @@ -7,6 +7,7 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from database import get_balance, set_user_balance, update_balance +from utils.csv_export import export_user_all_payments_csv from database.models import Payment from database.payments import add_payment from filters.admin import IsAdminFilter @@ -92,6 +93,21 @@ async def handle_balance_change( ) +@router.callback_query( + AdminUserEditorCallback.filter(F.action == "users_balance_export"), + IsAdminFilter(), +) +async def handle_balance_export( + callback_query: CallbackQuery, + callback_data: AdminUserEditorCallback, + session: AsyncSession, +): + tg_id = callback_data.tg_id + csv_file = await export_user_all_payments_csv(tg_id=tg_id, session=session) + await callback_query.message.answer_document(csv_file) + await callback_query.answer() + + @router.callback_query( AdminUserEditorCallback.filter(F.action == "users_balance_add"), IsAdminFilter(), diff --git a/utils/csv_export.py b/utils/csv_export.py index 6bb6d51c..c7a79b43 100644 --- a/utils/csv_export.py +++ b/utils/csv_export.py @@ -6,6 +6,7 @@ from io import StringIO from aiogram.types import BufferedInputFile from sqlalchemy import exists, func, join, not_, select from sqlalchemy.ext.asyncio import AsyncSession +import json from core.constants import PAYMENT_SYSTEMS_EXCLUDED from database.models import Key, Payment, Referral, Tariff, User @@ -258,3 +259,67 @@ async def export_keys_csv(session: AsyncSession) -> BufferedInputFile: buffer.seek(0) return BufferedInputFile(file=buffer.getvalue().encode("utf-8-sig"), filename="keys_export.csv") + + +async def export_user_all_payments_csv(tg_id: int, session: AsyncSession) -> BufferedInputFile: + query = ( + select( + Payment.id, + Payment.tg_id, + Payment.payment_id, + Payment.amount, + Payment.currency, + Payment.payment_system, + Payment.status, + Payment.original_amount, + Payment.created_at, + ) + .where(Payment.tg_id == tg_id) + .order_by(Payment.created_at.asc()) + ) + + result = await session.execute(query) + rows = result.all() + + buffer = StringIO() + writer = csv.writer(buffer) + writer.writerow([ + "id", + "tg_id", + "payment_id", + "amount", + "currency", + "payment_system", + "status", + "original_amount", + "created_at", + ]) + + for ( + internal_id, + user_tg_id, + external_payment_id, + amount, + currency, + payment_system, + status, + original_amount, + created_at, + ) in rows: + writer.writerow([ + internal_id, + user_tg_id, + external_payment_id or "", + amount, + currency, + payment_system, + status, + original_amount if original_amount is not None else "", + created_at, + ]) + + buffer.seek(0) + return BufferedInputFile( + file=buffer.getvalue().encode("utf-8-sig"), + filename=f"user_{tg_id}_payments_full.csv", + )