diff --git a/handlers/pay.py b/handlers/pay.py index 7f42f840..6aadcf98 100644 --- a/handlers/pay.py +++ b/handlers/pay.py @@ -3,19 +3,32 @@ from aiogram import Router, types from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from yookassa import Configuration, Payment # Импортируем ЮKассу +from aiohttp import web +import logging +import uuid from bot import bot -from config import ADMIN_ID, DATABASE_URL +from config import ADMIN_ID, DATABASE_URL, YOOKASSA_SECRET_KEY, YOOKASSA_SHOP_ID from database import (add_connection, check_connection_exists, get_balance, get_key_count, update_balance) from handlers.profile import process_callback_view_profile router = Router() +logging.basicConfig(level=logging.DEBUG) + +# Настройка конфигурации ЮKассы +Configuration.account_id = YOOKASSA_SHOP_ID +Configuration.secret_key = YOOKASSA_SECRET_KEY + +logging.debug(f"Account ID: {YOOKASSA_SHOP_ID}") +logging.debug(f"Secret Key: {YOOKASSA_SECRET_KEY}") + class ReplenishBalanceState(StatesGroup): choosing_transfer_method = State() choosing_amount = State() - waiting_for_admin_confirmation = State() + waiting_for_payment_confirmation = State() async def send_message_with_deletion(chat_id, text, reply_markup=None, state=None, message_key='last_message_id'): if state: @@ -27,8 +40,7 @@ async def send_message_with_deletion(chat_id, text, reply_markup=None, state=Non await bot.delete_message(chat_id=chat_id, message_id=previous_message_id) sent_message = await bot.send_message(chat_id=chat_id, text=text, reply_markup=reply_markup) - if state: - await state.update_data({message_key: sent_message.message_id}) + await state.update_data({message_key: sent_message.message_id}) except Exception as e: print(f"Ошибка при удалении/отправке сообщения: {e}") @@ -94,7 +106,6 @@ async def process_transfer_method_selection(callback_query: types.CallbackQuery, await send_message_with_deletion(callback_query.from_user.id, "Неверный метод перевода.", state=state, message_key='transfer_method_error_message_id') return - @router.callback_query(lambda c: c.data.startswith('amount_')) async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext): data = callback_query.data.split('_', 1) @@ -110,109 +121,79 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F await send_message_with_deletion(callback_query.from_user.id, "Некорректная сумма.", state=state, message_key='amount_error_message_id') return - state_data = await state.get_data() - amount_selection_message_id = state_data.get('amount_selection_message_id') - - if amount_selection_message_id: - try: - await bot.delete_message(chat_id=callback_query.from_user.id, message_id=amount_selection_message_id) - except Exception as e: - print(f"Ошибка при удалении сообщения: {e}") - await state.update_data(amount=amount) - await state.set_state(ReplenishBalanceState.waiting_for_admin_confirmation) + await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation) + state_data = await state.get_data() transfer_method = state_data.get('transfer_method') - message = ( - "Банк: Т-Банк\n" - "2200701036597224\n" - "Получатель: Лисицын В.Д.\n" - "\n" - "После перевода отправьте чек и дождитесь подтверждения." - ) - await callback_query.message.edit_text( - text=message, - reply_markup=None - ) + # Получаем имя клиента из Telegram профиля + customer_name = callback_query.from_user.full_name + customer_id = callback_query.from_user.id - admin_keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='Подтвердить', callback_data=f'admin_confirm_{callback_query.from_user.id}_{transfer_method}_{amount}')], - [InlineKeyboardButton(text='Отклонить', callback_data=f'admin_decline_{callback_query.from_user.id}_{transfer_method}_{amount}')] - ]) + # Создаем платеж с чеком для самозанятых + payment = Payment.create({ + "amount": { + "value": str(amount), + "currency": "RUB" + }, + "confirmation": { + "type": "redirect", + "return_url": "https://pocomacho.ru/" + }, + "capture": True, + "description": "Пополнение баланса", + "receipt": { + "customer": { + "full_name": customer_name, # Имя клиента + "email": "client@example.com", # Можно добавить email, если у вас есть + "phone": "79000000000" # Телефон клиента, если есть + }, + "items": [ + { + "description": "Пополнение баланса", # Описание услуги + "quantity": "1.00", + "amount": { + "value": str(amount), + "currency": "RUB" + }, + "vat_code": 6 # Код налога (для самозанятых 6) + } + ] + }, + "metadata": { + "user_id": customer_id # ID пользователя + } + }, uuid.uuid4()) # Уникальный идентификатор для транзакции - admin_message = ( - f"Пользователь {callback_query.from_user.full_name} запросил пополнение баланса.\n" - f"Метод перевода: {transfer_method}\n" - f"Сумма пополнения: {amount} RUB\n" - "Пожалуйста, подтвердите или отклоните запрос." - ) - - await send_message_with_deletion(ADMIN_ID, admin_message, reply_markup=admin_keyboard, state=state, message_key='admin_request_message_id') - await callback_query.answer() + # Отправляем пользователю ссылку для оплаты + if payment['status'] == 'pending': + await send_message_with_deletion( + callback_query.from_user.id, + f"Перейдите по ссылке для оплаты: {payment['confirmation']['confirmation_url']}", + state=state + ) + else: + await send_message_with_deletion(callback_query.from_user.id, "Ошибка при создании платежа.", state=state) -@router.callback_query(lambda c: c.data.startswith('admin_')) -async def process_admin_confirmation(callback_query: types.CallbackQuery, state: FSMContext): - if callback_query.from_user.id != ADMIN_ID: - await send_message_with_deletion(callback_query.from_user.id, "Вы не являетесь администратором.", state=state, message_key='admin_error_message_id') - return +async def payment_webhook(request): + event = await request.json() - data = callback_query.data.split('_', 4) - - if len(data) < 5: - await send_message_with_deletion(callback_query.from_user.id, "Неверные данные для обработки запроса.", state=state, message_key='admin_error_message_id') - return + logging.debug(f"Webhook event received: {event}") - action = data[1] - user_id_str = data[2] - transfer_method = data[3] - amount_str = data[4] + # Обработка успешного платежа + if event['event'] == 'payment.succeeded': + user_id_str = event['object']['metadata']['user_id'] + amount_str = event['object']['amount']['value'] + + try: + user_id = int(user_id_str) # Конвертируем user_id в int + amount = float(amount_str) # Конвертируем сумму из строки в float + + logging.debug(f"Payment succeeded for user_id: {user_id}, amount: {amount}") + await update_balance(user_id, amount) # Передаем число в функцию обновления баланса + except ValueError as e: + logging.error(f"Ошибка конвертации user_id или amount: {e}") + return web.Response(status=400) - try: - user_id = int(user_id_str) - amount = int(amount_str) - - state_data = await state.get_data() - requisites_message_id = state_data.get('requisites_message_id') - - if action == 'confirm': - conn = await asyncpg.connect(DATABASE_URL) - try: - await update_balance(user_id, amount) - balance = await get_balance(user_id) - - profile_button = InlineKeyboardButton(text='Профиль', callback_data='view_profile') - profile_keyboard = InlineKeyboardMarkup(inline_keyboard=[[profile_button]]) - - await send_message_with_deletion(callback_query.from_user.id, f"Баланс пользователя успешно пополнен на {amount} RUB.\nТекущий баланс: {balance}", state=state, message_key='admin_confirm_message_id') - - await bot.send_message( - user_id, - f"Ваш баланс был успешно пополнен на {amount} RUB.", - reply_markup=profile_keyboard - ) - - requisites_message_id = state_data.get('requisites_message_id') - if requisites_message_id: - try: - await bot.delete_message(chat_id=user_id, message_id=requisites_message_id) - except Exception as e: - print(f"Ошибка при удалении сообщения: {e}") - - finally: - await conn.close() - - elif action == 'decline': - await send_message_with_deletion(callback_query.from_user.id, "Пополнение баланса отклонено.", state=state, message_key='admin_decline_message_id') - await bot.send_message(user_id, "Ваш запрос на пополнение баланса был отклонен.") - - except ValueError: - await send_message_with_deletion(callback_query.from_user.id, "Некорректные данные суммы.", state=state, message_key='admin_error_message_id') - return - except Exception as e: - await send_message_with_deletion(callback_query.from_user.id, f"Ошибка при пополнении баланса: {e}", state=state, message_key='admin_error_message_id') - print(f"Ошибка при пополнении баланса: {e}") - - finally: - await state.clear() - await callback_query.answer() + return web.Response(status=200) diff --git a/main.py b/main.py index 8380f337..6a2b5339 100644 --- a/main.py +++ b/main.py @@ -1,28 +1,35 @@ +import asyncio +from aiohttp import web from aiogram import Bot, Dispatcher from aiogram.fsm.storage.memory import MemoryStorage from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application -from aiohttp import web +import logging from bot import bot, dp, router from config import API_TOKEN, WEBHOOK_URL, WEBHOOK_PATH, WEBAPP_HOST, WEBAPP_PORT -from database import init_db +from database import init_db, update_balance from handlers.notifications import notify_expiring_keys +from handlers.pay import payment_webhook # Импорт обработчика для вебхука ЮKассы -import asyncio +logging.basicConfig(level=logging.DEBUG) +# Функция для периодических уведомлений async def periodic_notifications(): while True: await notify_expiring_keys(bot) await asyncio.sleep(3600) # Проверяем каждый час +# Действия при старте приложения async def on_startup(app): await bot.set_webhook(WEBHOOK_URL) await init_db() asyncio.create_task(periodic_notifications()) +# Действия при завершении приложения async def on_shutdown(app): await bot.delete_webhook() +# Основная функция приложения async def main(): # Регистрация роутеров dp.include_router(router) @@ -31,11 +38,16 @@ async def main(): app = web.Application() app.on_startup.append(on_startup) app.on_shutdown.append(on_shutdown) - + + # Регистрация вебхука для ЮKассы + app.router.add_post('/yookassa/webhook', payment_webhook) + + # Регистрация вебхука для Telegram бота SimpleRequestHandler(dispatcher=dp, bot=bot).register(app, path=WEBHOOK_PATH) + # Настройка приложения для aiogram setup_application(app, dp, bot=bot) - + # Запуск веб-сервера runner = web.AppRunner(app) await runner.setup() @@ -45,5 +57,7 @@ async def main(): print(f"Webhook URL: {WEBHOOK_URL}") await asyncio.Event().wait() +# Запуск приложения if __name__ == '__main__': asyncio.run(main()) +