From 84d8024d084926f5d8b0e457cdee21def4068c68 Mon Sep 17 00:00:00 2001 From: Max Shalenkov Date: Mon, 10 Nov 2025 23:38:05 +0400 Subject: [PATCH] remove WATA payments --- handlers/payments/__init__.py | 2 - handlers/payments/pay.py | 14 - handlers/payments/providers.py | 15 - handlers/payments/wata/__init__.py | 0 handlers/payments/wata/wata.py | 421 ----------------------------- web/__init__.py | 5 - web/wata_payment.py | 72 ----- 7 files changed, 529 deletions(-) delete mode 100644 handlers/payments/wata/__init__.py delete mode 100644 handlers/payments/wata/wata.py delete mode 100644 web/wata_payment.py diff --git a/handlers/payments/__init__.py b/handlers/payments/__init__.py index 4658e668..691782cf 100644 --- a/handlers/payments/__init__.py +++ b/handlers/payments/__init__.py @@ -15,7 +15,6 @@ from .pay import router as pay_router from .robokassa import router as robokassa_router from .stars import router as stars_router from .tribute import router as tribute_router -from .wata.wata import router as wata_router from .yookassa import router as yookassa_router from .yoomoney import router as yoomoney_router @@ -42,7 +41,6 @@ if PROVIDERS.get("HELEKET", {}).get("enabled"): router.include_router(heleket_router) router.include_router(tribute_router) -router.include_router(wata_router) router.include_router(gift_router) router.include_router(pay_router) router.include_router(fast_payment_flow_router) diff --git a/handlers/payments/pay.py b/handlers/payments/pay.py index d2cd8ea2..8aaa34a1 100644 --- a/handlers/payments/pay.py +++ b/handlers/payments/pay.py @@ -17,7 +17,6 @@ from handlers import buttons as btn from handlers.payments.stars.handlers import process_callback_pay_stars from handlers.payments.tribute.handlers import process_callback_pay_tribute -from handlers.payments.wata.wata import process_callback_pay_wata from hooks.hook_buttons import insert_hook_buttons from hooks.hooks import run_hooks from handlers.payments.currency_rates import format_for_user @@ -177,19 +176,6 @@ async def back_to_pay(callback_query: CallbackQuery, state: FSMContext, session: return await balance_handler(callback_query, session) -@router.callback_query(F.data == "pay_wata_ru") -async def handle_pay_wata_ru(callback_query: CallbackQuery, state: FSMContext, session: AsyncSession): - await process_callback_pay_wata(callback_query, state, session, cassa_name="ru") - - -@router.callback_query(F.data == "pay_wata_sbp") -async def handle_pay_wata_sbp(callback_query: CallbackQuery, state: FSMContext, session: AsyncSession): - await process_callback_pay_wata(callback_query, state, session, cassa_name="sbp") - - -@router.callback_query(F.data == "pay_wata_int") -async def handle_pay_wata_int(callback_query: CallbackQuery, state: FSMContext, session: AsyncSession): - await process_callback_pay_wata(callback_query, state, session, cassa_name="int") @router.callback_query(F.data == "pay_tribute") async def handle_pay_tribute(callback_query: CallbackQuery, state: FSMContext, session: AsyncSession): diff --git a/handlers/payments/providers.py b/handlers/payments/providers.py index f44e7e73..05b686a9 100644 --- a/handlers/payments/providers.py +++ b/handlers/payments/providers.py @@ -29,16 +29,6 @@ PROVIDERS_BASE: Dict[str, dict] = { "fast": "handle_custom_amount_input_kassai_sbp", "module": "kassai", }, - "WATA_RU": { - "currency": "RUB", - "value": "pay_wata_ru", - "fast": None, - }, - "WATA_SBP": { - "currency": "RUB", - "value": "pay_wata_sbp", - "fast": None, - }, "TRIBUTE": { "currency": "RUB", "value": "pay_tribute", @@ -59,11 +49,6 @@ PROVIDERS_BASE: Dict[str, dict] = { "value": "pay_freekassa", "fast": None, }, - "WATA_INT": { - "currency": "USD", - "value": "pay_wata_int", - "fast": None, - }, "STARS": { "currency": "STARS", "value": "pay_stars", diff --git a/handlers/payments/wata/__init__.py b/handlers/payments/wata/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/handlers/payments/wata/wata.py b/handlers/payments/wata/wata.py deleted file mode 100644 index 9057b01d..00000000 --- a/handlers/payments/wata/wata.py +++ /dev/null @@ -1,421 +0,0 @@ -import aiohttp - -from aiogram import F, Router, types -from aiogram.fsm.context import FSMContext -from aiogram.fsm.state import State, StatesGroup -from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup -from aiogram.utils.keyboard import InlineKeyboardBuilder -from sqlalchemy.ext.asyncio import AsyncSession - -from config import ( - FAIL_REDIRECT_LINK, - REDIRECT_LINK, - WATA_INT_TOKEN, - WATA_RU_TOKEN, - WATA_SBP_TOKEN, - PROVIDERS_ENABLED, -) -from handlers.payments.providers import get_providers -from handlers.buttons import BACK, PAY_2, WATA_INT, WATA_RU, WATA_SBP -from handlers.texts import ( - ENTER_SUM, - PAYMENT_OPTIONS, - WATA_INT_DESCRIPTION, - WATA_PAYMENT_MESSAGE, - WATA_PAYMENT_TITLE, - WATA_RU_DESCRIPTION, - WATA_SBP_DESCRIPTION, -) -from handlers.utils import edit_or_send_message -from logger import logger - - -router = Router() - - -class ReplenishBalanceWataState(StatesGroup): - choosing_cassa = State() - choosing_amount = State() - waiting_for_payment_confirmation = State() - entering_custom_amount = State() - - -PROVIDERS = get_providers(PROVIDERS_ENABLED) -WATA_CASSA_CONFIG = [ - { - "enable": bool(PROVIDERS.get("WATA_RU", {}).get("enabled")), - "token": WATA_RU_TOKEN, - "name": "ru", - "button": WATA_RU, - "desc": WATA_RU_DESCRIPTION, - }, - { - "enable": bool(PROVIDERS.get("WATA_SBP", {}).get("enabled")), - "token": WATA_SBP_TOKEN, - "name": "sbp", - "button": WATA_SBP, - "desc": WATA_SBP_DESCRIPTION, - }, - { - "enable": bool(PROVIDERS.get("WATA_INT", {}).get("enabled")), - "token": WATA_INT_TOKEN, - "name": "int", - "button": WATA_INT, - "desc": WATA_INT_DESCRIPTION, - }, -] - - -@router.callback_query(F.data == "pay_wata") -async def process_callback_pay_wata( - callback_query: types.CallbackQuery, state: FSMContext, session: AsyncSession, cassa_name: str = None -): - tg_id = callback_query.message.chat.id - logger.info(f"User {tg_id} initiated WATA payment.") - - if cassa_name: - cassa = next((c for c in WATA_CASSA_CONFIG if c["name"] == cassa_name and c["enable"]), None) - if not cassa: - await edit_or_send_message( - target_message=callback_query.message, - text="Ошибка: выбранная касса недоступна.", - reply_markup=InlineKeyboardMarkup(inline_keyboard=[]), - force_text=True, - ) - return - - builder = InlineKeyboardBuilder() - for i in range(0, len(PAYMENT_OPTIONS), 2): - if i + 1 < len(PAYMENT_OPTIONS): - builder.row( - InlineKeyboardButton( - text=PAYMENT_OPTIONS[i]["text"], - callback_data=f"wata_amount|{cassa_name}|{PAYMENT_OPTIONS[i]['callback_data']}", - ), - InlineKeyboardButton( - text=PAYMENT_OPTIONS[i + 1]["text"], - callback_data=f"wata_amount|{cassa_name}|{PAYMENT_OPTIONS[i + 1]['callback_data']}", - ), - ) - else: - builder.row( - InlineKeyboardButton( - text=PAYMENT_OPTIONS[i]["text"], - callback_data=f"wata_amount|{cassa_name}|{PAYMENT_OPTIONS[i]['callback_data']}", - ) - ) - - builder.row(InlineKeyboardButton(text="Ввести сумму", callback_data=f"wata_custom_amount|{cassa_name}")) - builder.row(InlineKeyboardButton(text=BACK, callback_data="pay")) - - try: - await callback_query.message.delete() - except Exception: - pass - - new_message = await callback_query.message.answer( - text=cassa["desc"], - reply_markup=builder.as_markup(), - ) - await state.update_data(message_id=new_message.message_id, chat_id=new_message.chat.id, wata_cassa=cassa_name) - await state.set_state(ReplenishBalanceWataState.choosing_amount) - return - - builder = InlineKeyboardBuilder() - for cassa in WATA_CASSA_CONFIG: - if cassa["enable"]: - builder.row(InlineKeyboardButton(text=cassa["button"], callback_data=f"wata_cassa|{cassa['name']}")) - builder.row(InlineKeyboardButton(text=BACK, callback_data="balance")) - - try: - await callback_query.message.delete() - except Exception: - pass - - new_message = await callback_query.message.answer( - text="Выберите способ оплаты через WATA:", - reply_markup=builder.as_markup(), - ) - await state.update_data(message_id=new_message.message_id, chat_id=new_message.chat.id) - await state.set_state(ReplenishBalanceWataState.choosing_cassa) - - -@router.callback_query(F.data.startswith("wata_cassa|")) -async def process_cassa_selection(callback_query: types.CallbackQuery, state: FSMContext): - cassa_name = callback_query.data.split("|")[1] - cassa = next((c for c in WATA_CASSA_CONFIG if c["name"] == cassa_name and c["enable"]), None) - - if not cassa: - await edit_or_send_message( - target_message=callback_query.message, - text="Ошибка: выбранная касса недоступна.", - reply_markup=InlineKeyboardMarkup(inline_keyboard=[]), - force_text=True, - ) - return - - await state.update_data(wata_cassa=cassa_name) - - builder = InlineKeyboardBuilder() - for i in range(0, len(PAYMENT_OPTIONS), 2): - if i + 1 < len(PAYMENT_OPTIONS): - builder.row( - InlineKeyboardButton( - text=PAYMENT_OPTIONS[i]["text"], - callback_data=f"wata_amount|{cassa_name}|{PAYMENT_OPTIONS[i]['callback_data']}", - ), - InlineKeyboardButton( - text=PAYMENT_OPTIONS[i + 1]["text"], - callback_data=f"wata_amount|{cassa_name}|{PAYMENT_OPTIONS[i + 1]['callback_data']}", - ), - ) - else: - builder.row( - InlineKeyboardButton( - text=PAYMENT_OPTIONS[i]["text"], - callback_data=f"wata_amount|{cassa_name}|{PAYMENT_OPTIONS[i]['callback_data']}", - ) - ) - builder.row(InlineKeyboardButton(text=BACK, callback_data="balance")) - - try: - await callback_query.message.delete() - except Exception: - pass - - new_message = await callback_query.message.answer( - text=cassa["desc"], - reply_markup=builder.as_markup(), - ) - await state.update_data(message_id=new_message.message_id, chat_id=new_message.chat.id) - await state.set_state(ReplenishBalanceWataState.choosing_amount) - - -@router.message(ReplenishBalanceWataState.entering_custom_amount) -async def handle_custom_amount_text_input(message: types.Message, state: FSMContext): - text = message.text.strip() - - try: - amount = int(text) - if amount <= 0: - raise ValueError - - await state.update_data(required_amount=amount) - await handle_custom_amount_input(message, state) - - except ValueError: - await message.answer("❌ Введите корректную сумму: только цифры, без символов.") - - -@router.callback_query(F.data.startswith("wata_custom_amount|")) -async def process_custom_amount_button(callback_query: types.CallbackQuery, state: FSMContext): - cassa_name = callback_query.data.split("|")[1] - await state.update_data(wata_cassa=cassa_name) - - builder = InlineKeyboardBuilder() - builder.row(InlineKeyboardButton(text=BACK, callback_data=f"pay_wata_{cassa_name}")) - - await edit_or_send_message( - target_message=callback_query.message, - text=ENTER_SUM, - reply_markup=builder.as_markup(), - force_text=True, - ) - await state.set_state(ReplenishBalanceWataState.entering_custom_amount) - - -async def handle_custom_amount_input(event: types.Message | types.CallbackQuery, state: FSMContext): - tg_id = event.from_user.id - target_message = getattr(event, "message", None) or event - - try: - data = await state.get_data() - required_amount = data.get("required_amount", 0) - cassa_name = data.get("wata_cassa", "sbp") - - if not required_amount or required_amount <= 0: - await edit_or_send_message( - target_message=target_message, - text="❌ Недостаточная сумма для оплаты.", - reply_markup=InlineKeyboardMarkup(inline_keyboard=[]), - ) - return - - cassa = next((c for c in WATA_CASSA_CONFIG if c["name"] == cassa_name and c["enable"]), None) - if not cassa: - await edit_or_send_message( - target_message=target_message, - text="❌ Выбранная касса WATA недоступна.", - reply_markup=InlineKeyboardMarkup(inline_keyboard=[]), - ) - return - - payment_url = await generate_wata_payment_link(required_amount, tg_id, cassa) - - keyboard = InlineKeyboardMarkup( - inline_keyboard=[ - [InlineKeyboardButton(text=PAY_2, url=payment_url)], - [InlineKeyboardButton(text=BACK, callback_data="balance")], - ] - ) - - message_text = f"💰 Вы выбрали пополнение на {required_amount}₽. Перейдите по ссылке для оплаты:" - await edit_or_send_message( - target_message=target_message, - text=message_text, - reply_markup=keyboard, - disable_web_page_preview=True, - ) - - await state.clear() - - except Exception as e: - logger.error(f"[WATA] Ошибка при создании ссылки для оплаты: {e}", exc_info=True) - await edit_or_send_message( - target_message=target_message, - text="⚠️ Произошла ошибка при создании ссылки на оплату. Попробуйте позже.", - reply_markup=InlineKeyboardMarkup(inline_keyboard=[]), - ) - - -@router.callback_query(F.data.startswith("wata_amount|")) -async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext): - parts = callback_query.data.split("|") - cassa_name = parts[1] - amount_str = parts[-1] - - cassa = next((c for c in WATA_CASSA_CONFIG if c["name"] == cassa_name and c["enable"]), None) - if not cassa: - await edit_or_send_message( - target_message=callback_query.message, - text="Ошибка: выбранная касса недоступна.", - reply_markup=InlineKeyboardMarkup(inline_keyboard=[]), - force_text=True, - ) - return - - try: - amount = int(amount_str) - if amount <= 0: - raise ValueError - except Exception: - await edit_or_send_message( - target_message=callback_query.message, - text="Некорректная сумма.", - reply_markup=InlineKeyboardMarkup(inline_keyboard=[]), - force_text=True, - ) - return - - await state.update_data(amount=amount) - payment_url = await generate_wata_payment_link(amount, callback_query.message.chat.id, cassa) - - confirm_keyboard = InlineKeyboardMarkup( - inline_keyboard=[ - [InlineKeyboardButton(text=PAY_2, url=payment_url)], - [InlineKeyboardButton(text=BACK, callback_data="balance")], - ] - ) - - await edit_or_send_message( - target_message=callback_query.message, - text=WATA_PAYMENT_MESSAGE.format(amount=amount), - reply_markup=confirm_keyboard, - force_text=True, - ) - - await state.set_state(ReplenishBalanceWataState.waiting_for_payment_confirmation) - - -async def generate_wata_payment_link(amount, tg_id, cassa): - url = "https://api.wata.pro/api/h2h/links" - headers = { - "Authorization": f"Bearer {cassa['token']}", - "Content-Type": "application/json", - } - data = { - "amount": float(amount), - "currency": "RUB", - "orderId": str(tg_id), - "orderDescription": WATA_PAYMENT_TITLE, - "successUrl": f"{REDIRECT_LINK}", - "failUrl": f"{FAIL_REDIRECT_LINK}", - } - - if cassa["name"] == "int": - import xml.etree.ElementTree as ET - from datetime import datetime - - async def get_usd_rate(): - today = datetime.now().strftime("%d/%m/%Y") - url = f"http://www.cbr.ru/scripts/XML_daily.asp?date_req={today}" - - try: - async with aiohttp.ClientSession() as session: - async with session.get(url, timeout=15) as resp: - if resp.status == 200: - xml_content = await resp.text() - root = ET.fromstring(xml_content) - - for valute in root.findall("Valute"): - char_code = valute.find("CharCode") - if char_code is not None and char_code.text == "USD": - value_elem = valute.find("Value") - if value_elem is not None: - usd_rub_rate = float(value_elem.text.replace(",", ".")) - rub_usd_rate = 1 / usd_rub_rate - logger.info( - f"CBR USD rate: 1 USD = {usd_rub_rate} RUB, 1 RUB = {rub_usd_rate} USD" - ) - return rub_usd_rate - - logger.warning("USD rate not found in CBR response") - - except Exception as e: - logger.error(f"Failed to get USD rate from CBR: {e}") - - fallback_rate = 0.0105 - logger.warning(f"Using fallback USD rate: {fallback_rate}") - return fallback_rate - - try: - usd_rate = await get_usd_rate() - rub_per_usd = 1 / usd_rate - rub_per_usd_plus_5 = rub_per_usd + 5 - new_usd_rate = 1 / rub_per_usd_plus_5 - amount_usd = round(float(amount) * new_usd_rate, 2) - data["amount"] = amount_usd - data["currency"] = "USD" - except Exception as e: - logger.error(f"Failed to convert RUB to USD: {e}") - fallback_usd_rate = 0.0105 - rub_per_usd = 1 / fallback_usd_rate - rub_per_usd_plus_5 = rub_per_usd + 5 - new_usd_rate = 1 / rub_per_usd_plus_5 - amount_usd = round(float(amount) * new_usd_rate, 2) - data["amount"] = amount_usd - data["currency"] = "USD" - - async with aiohttp.ClientSession() as session: - async with session.post(url, headers=headers, json=data, timeout=60) as resp: - if resp.status == 200: - try: - resp_json = await resp.json() - except Exception: - text = await resp.text() - logger.error(f"Ошибка при разборе JSON ответа WATA: статус={resp.status}, ответ={text}") - return "https://wata.pro/" - - if "url" in resp_json: - return resp_json["url"] - - logger.error(f"Ответ WATA без url: {resp_json}") - return "https://wata.pro/" - - try: - error_json = await resp.json() - logger.error(f"Ошибка WATA API: статус={resp.status}, ответ={error_json}") - except Exception: - text = await resp.text() - logger.error(f"Ошибка WATA API: статус={resp.status}, не-JSON ответ: {text}") - return "https://wata.pro/" diff --git a/web/__init__.py b/web/__init__.py index cc39dc13..792b3ad5 100644 --- a/web/__init__.py +++ b/web/__init__.py @@ -4,16 +4,11 @@ from handlers.payments.heleket.webhook import heleket_webhook from handlers.payments.kassai.webhook import kassai_webhook from utils.modules_loader import load_module_webhooks -from .wata_payment import wata_payment_webhook - - -WATA_WEBHOOK_PATH = "/wata/webhook" KASSAI_WEBHOOK_PATH = "/kassai/webhook" HELEKET_WEBHOOK_PATH = "/heleket/webhook" async def register_web_routes(router: UrlDispatcher) -> None: - router.add_post(WATA_WEBHOOK_PATH, wata_payment_webhook) router.add_post(KASSAI_WEBHOOK_PATH, kassai_webhook) router.add_post(HELEKET_WEBHOOK_PATH, heleket_webhook) diff --git a/web/wata_payment.py b/web/wata_payment.py deleted file mode 100644 index 17510347..00000000 --- a/web/wata_payment.py +++ /dev/null @@ -1,72 +0,0 @@ -import base64 -import json - -import aiohttp - -from aiohttp import web -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import padding - -from database import add_payment, async_session_maker, update_balance -from handlers.payments.utils import send_payment_success_notification -from logger import logger - - -PUBLIC_KEY_URL = "https://api.wata.pro/api/h2h/public-key" - - -async def get_wata_public_key(): - async with aiohttp.ClientSession() as session: - async with session.get(PUBLIC_KEY_URL) as resp: - data = await resp.json() - return data["value"].encode() - - -async def verify_signature(raw_json: bytes, signature: str, public_key_pem: bytes) -> bool: - try: - public_key = serialization.load_pem_public_key(public_key_pem, backend=default_backend()) - signature_bytes = base64.b64decode(signature) - public_key.verify(signature_bytes, raw_json, padding.PKCS1v15(), hashes.SHA512()) - return True - except Exception as e: - logger.error(f"Ошибка проверки подписи WATA: {e}") - return False - - -async def wata_payment_webhook(request: web.Request): - try: - raw_json = await request.read() - data = json.loads(raw_json) - signature = request.headers.get("X-Signature") - if not signature: - logger.error("Нет подписи X-Signature в заголовке!") - return web.Response(status=400) - public_key_pem = await get_wata_public_key() - if not await verify_signature(raw_json, signature, public_key_pem): - logger.error("Подпись WATA не прошла проверку!") - return web.Response(status=400) - - logger.info(f"WATA webhook: {json.dumps(data, ensure_ascii=False)}") - logger.info( - f"transactionId={data.get('transactionId')}, status={data.get('transactionStatus')}, orderId={data.get('orderId')}, amount={data.get('amount')}, currency={data.get('currency')}, errorCode={data.get('errorCode')}, errorDescription={data.get('errorDescription')}" - ) - if data.get("transactionStatus") == "Paid": - tg_id = data.get("orderId") - amount = data.get("amount") - if not tg_id or not amount: - logger.error(f"WATA: отсутствует orderId или amount: {data}") - return web.Response(status=400) - async with async_session_maker() as session: - await update_balance(session, int(tg_id), float(amount)) - await add_payment(session, int(tg_id), float(amount), "wata") - await send_payment_success_notification(tg_id, float(amount), session) - logger.info(f"✅ WATA: баланс пополнен для пользователя {tg_id} на {amount}") - elif data.get("transactionStatus") == "Declined": - logger.warning(f"WATA: транзакция отклонена: {data}") - else: - logger.warning(f"WATA: неизвестный статус транзакции: {data.get('transactionStatus')}") - return web.Response(status=200) - except Exception as e: - logger.error(f"Ошибка в webhook WATA: {e}") - return web.Response(status=500)