remove WATA payments

This commit is contained in:
Max Shalenkov
2025-11-10 23:38:05 +04:00
parent 617cdbad10
commit 84d8024d08
7 changed files with 0 additions and 529 deletions
-2
View File
@@ -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)
-14
View File
@@ -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):
-15
View File
@@ -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",
View File
-421
View File
@@ -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"💰 Вы выбрали пополнение на <b>{required_amount}₽</b>. Перейдите по ссылке для оплаты:"
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/"
-5
View File
@@ -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)
-72
View File
@@ -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)