@@ -97,3 +97,6 @@ WATA_INT = "🌍 WATA: Международные карты"
|
||||
KASSAI_CARDS = "💳 KassaAI: Карты РФ"
|
||||
KASSAI_SBP = "🏦 KassaAI: СБП"
|
||||
STARS_BOT_URL = "https://t.me/PremiumBot"
|
||||
|
||||
# Кнопки Heleket
|
||||
HELEKET_CRYPTO = "₿ Heleket: Crypto"
|
||||
|
||||
@@ -13,6 +13,7 @@ from config import (
|
||||
CRYPTO_BOT_ENABLE,
|
||||
DONATIONS_ENABLE,
|
||||
FREEKASSA_ENABLE,
|
||||
HELEKET_ENABLE,
|
||||
KASSAI_ENABLE,
|
||||
ROBOKASSA_ENABLE,
|
||||
STARS_ENABLE,
|
||||
@@ -29,6 +30,7 @@ from handlers.buttons import (
|
||||
COUPON,
|
||||
CRYPTOBOT,
|
||||
FREEKASSA,
|
||||
HELEKET_CRYPTO,
|
||||
KASSAI_CARDS,
|
||||
KASSAI_SBP,
|
||||
MAIN_MENU,
|
||||
@@ -43,6 +45,7 @@ from handlers.buttons import (
|
||||
)
|
||||
from handlers.payments.cryprobot_pay import process_callback_pay_cryptobot
|
||||
from handlers.payments.freekassa_pay import process_callback_pay_freekassa
|
||||
from handlers.payments.heleket import process_callback_pay_heleket
|
||||
from handlers.payments.kassai import process_callback_pay_kassai
|
||||
from handlers.payments.robokassa_pay import process_callback_pay_robokassa
|
||||
from handlers.payments.stars_pay import process_callback_pay_stars
|
||||
@@ -67,6 +70,8 @@ async def handle_pay(callback_query: CallbackQuery, state: FSMContext, session:
|
||||
payment_handlers.append(process_callback_pay_yoomoney)
|
||||
if KASSAI_ENABLE:
|
||||
payment_handlers.append(process_callback_pay_kassai)
|
||||
if HELEKET_ENABLE:
|
||||
payment_handlers.append(process_callback_pay_heleket)
|
||||
if WATA_RU_ENABLE or WATA_SBP_ENABLE or WATA_INT_ENABLE:
|
||||
payment_handlers.append(process_callback_pay_wata)
|
||||
if CRYPTO_BOT_ENABLE:
|
||||
@@ -91,6 +96,8 @@ async def handle_pay(callback_query: CallbackQuery, state: FSMContext, session:
|
||||
if KASSAI_ENABLE:
|
||||
builder.row(InlineKeyboardButton(text=KASSAI_CARDS, callback_data="pay_kassai_cards"))
|
||||
builder.row(InlineKeyboardButton(text=KASSAI_SBP, callback_data="pay_kassai_sbp"))
|
||||
if HELEKET_ENABLE:
|
||||
builder.row(InlineKeyboardButton(text=HELEKET_CRYPTO, callback_data="pay_heleket_crypto"))
|
||||
if CRYPTO_BOT_ENABLE:
|
||||
builder.row(InlineKeyboardButton(text=CRYPTOBOT, callback_data="pay_cryptobot"))
|
||||
if STARS_ENABLE:
|
||||
@@ -194,3 +201,8 @@ async def handle_pay_kassai_cards(callback_query: CallbackQuery, state: FSMConte
|
||||
@router.callback_query(F.data == "pay_kassai_sbp")
|
||||
async def handle_pay_kassai_sbp(callback_query: CallbackQuery, state: FSMContext, session: AsyncSession):
|
||||
await process_callback_pay_kassai(callback_query, state, session, method_name="sbp")
|
||||
|
||||
|
||||
@router.callback_query(F.data == "pay_heleket_crypto")
|
||||
async def handle_pay_heleket_crypto(callback_query: CallbackQuery, state: FSMContext, session: AsyncSession):
|
||||
await process_callback_pay_heleket(callback_query, state, session, method_name="crypto")
|
||||
|
||||
@@ -10,6 +10,7 @@ from config import (
|
||||
STARS_ENABLE,
|
||||
YOOKASSA_ENABLE,
|
||||
YOOMONEY_ENABLE,
|
||||
HELEKET_ENABLE,
|
||||
)
|
||||
|
||||
from .cryprobot_pay import router as cryprobot_router
|
||||
@@ -21,6 +22,7 @@ from .stars_pay import router as stars_router
|
||||
from .yookassa_pay import router as yookassa_router
|
||||
from .yoomoney_pay import router as yoomoney_router
|
||||
from .wata import router as wata_router
|
||||
from .heleket import router as heleket_router
|
||||
|
||||
router = Router(name="payments_main_router")
|
||||
|
||||
@@ -38,6 +40,10 @@ if STARS_ENABLE:
|
||||
router.include_router(stars_router)
|
||||
if KASSAI_ENABLE:
|
||||
router.include_router(kassai_router)
|
||||
if YOOMONEY_ENABLE:
|
||||
router.include_router(yoomoney_router)
|
||||
if HELEKET_ENABLE:
|
||||
router.include_router(heleket_router)
|
||||
|
||||
router.include_router(wata_router)
|
||||
router.include_router(gift_router)
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
import aiohttp
|
||||
import hashlib
|
||||
import base64
|
||||
import time
|
||||
import json
|
||||
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 (
|
||||
HELEKET_ENABLE, HELEKET_MERCHANT_ID, HELEKET_API_KEY,
|
||||
HELEKET_SUCCESS_URL, HELEKET_RETURN_URL, HELEKET_CALLBACK_URL,
|
||||
HELEKET_CURRENCY_RATE
|
||||
)
|
||||
|
||||
from handlers.buttons import BACK, PAY_2, HELEKET_CRYPTO
|
||||
from handlers.texts import (
|
||||
HELEKET_CRYPTO_DESCRIPTION, HELEKET_PAYMENT_MESSAGE, ENTER_SUM, PAYMENT_OPTIONS, HELEKET_PAYMENT_TITLE
|
||||
)
|
||||
from handlers.utils import edit_or_send_message
|
||||
from logger import logger
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
class ReplenishBalanceHeleket(StatesGroup):
|
||||
choosing_method = State()
|
||||
choosing_amount = State()
|
||||
waiting_for_payment_confirmation = State()
|
||||
entering_custom_amount = State()
|
||||
|
||||
|
||||
HELEKET_PAYMENT_METHODS = [
|
||||
{"enable": HELEKET_ENABLE, "currency": "USD", "to_currency": None, "name": "crypto", "button": HELEKET_CRYPTO, "desc": HELEKET_CRYPTO_DESCRIPTION},
|
||||
]
|
||||
|
||||
|
||||
@router.callback_query(F.data == "pay_heleket")
|
||||
async def process_callback_pay_heleket(callback_query: types.CallbackQuery, state: FSMContext, session: AsyncSession, method_name: str = None):
|
||||
try:
|
||||
tg_id = callback_query.message.chat.id
|
||||
logger.info(f"User {tg_id} initiated Heleket payment.")
|
||||
|
||||
await state.clear()
|
||||
|
||||
if method_name:
|
||||
method = next((m for m in HELEKET_PAYMENT_METHODS if m["name"] == method_name and m["enable"]), None)
|
||||
if not method:
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text="Ошибка: выбранный способ оплаты недоступен.",
|
||||
reply_markup=types.InlineKeyboardMarkup(),
|
||||
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'heleket_amount|{method_name}|{PAYMENT_OPTIONS[i]["callback_data"].split("|")[1]}',
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i + 1]["text"],
|
||||
callback_data=f'heleket_amount|{method_name}|{PAYMENT_OPTIONS[i + 1]["callback_data"].split("|")[1]}',
|
||||
),
|
||||
)
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i]["text"],
|
||||
callback_data=f'heleket_amount|{method_name}|{PAYMENT_OPTIONS[i]["callback_data"].split("|")[1]}',
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="Ввести сумму", callback_data=f"heleket_custom_amount|{method_name}"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="balance"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=method["desc"],
|
||||
reply_markup=builder.as_markup(),
|
||||
force_text=True,
|
||||
)
|
||||
await state.update_data(heleket_method=method_name)
|
||||
await state.set_state(ReplenishBalanceHeleket.choosing_amount)
|
||||
return
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
for method in HELEKET_PAYMENT_METHODS:
|
||||
if method["enable"]:
|
||||
builder.row(InlineKeyboardButton(text=method["button"], callback_data=f'heleket_method|{method["name"]}'))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="balance"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text="Выберите способ оплаты через Heleket:",
|
||||
reply_markup=builder.as_markup(),
|
||||
force_text=True,
|
||||
)
|
||||
await state.update_data(message_id=callback_query.message.message_id, chat_id=callback_query.message.chat.id)
|
||||
await state.set_state(ReplenishBalanceHeleket.choosing_method)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in process_callback_pay_heleket for user {callback_query.message.chat.id}: {e}")
|
||||
await callback_query.answer("Произошла ошибка при инициализации платежа. Попробуйте позже.", show_alert=True)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("heleket_method|"))
|
||||
async def process_method_selection(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
method_name = callback_query.data.split("|")[1]
|
||||
method = next((m for m in HELEKET_PAYMENT_METHODS if m["name"] == method_name), None)
|
||||
|
||||
if not method or not method["enable"]:
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text="Ошибка: выбранный способ оплаты недоступен.",
|
||||
reply_markup=types.InlineKeyboardMarkup(),
|
||||
force_text=True,
|
||||
)
|
||||
return
|
||||
|
||||
await state.update_data(heleket_method=method_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'heleket_amount|{method_name}|{PAYMENT_OPTIONS[i]["callback_data"].split("|")[1]}',
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i + 1]["text"],
|
||||
callback_data=f'heleket_amount|{method_name}|{PAYMENT_OPTIONS[i + 1]["callback_data"].split("|")[1]}',
|
||||
),
|
||||
)
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i]["text"],
|
||||
callback_data=f'heleket_amount|{method_name}|{PAYMENT_OPTIONS[i]["callback_data"].split("|")[1]}',
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="Ввести сумму", callback_data=f"heleket_custom_amount|{method_name}"))
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data="pay_heleket"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=method["desc"],
|
||||
reply_markup=builder.as_markup(),
|
||||
force_text=True,
|
||||
)
|
||||
await state.update_data(message_id=callback_query.message.message_id, chat_id=callback_query.message.chat.id)
|
||||
await state.set_state(ReplenishBalanceHeleket.choosing_amount)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("heleket_custom_amount|"))
|
||||
async def process_custom_amount_button(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
method_name = callback_query.data.split("|")[1]
|
||||
await state.update_data(heleket_method=method_name)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text=BACK, callback_data=f"pay_heleket_{method_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(ReplenishBalanceHeleket.entering_custom_amount)
|
||||
|
||||
|
||||
@router.message(ReplenishBalanceHeleket.entering_custom_amount)
|
||||
async def handle_custom_amount_input(message: types.Message, state: FSMContext):
|
||||
data = await state.get_data()
|
||||
method_name = data.get("heleket_method")
|
||||
method = next((m for m in HELEKET_PAYMENT_METHODS if m["name"] == method_name), None)
|
||||
|
||||
if not method or not method["enable"]:
|
||||
await edit_or_send_message(
|
||||
target_message=message,
|
||||
text="Ошибка: выбранный способ оплаты недоступен.",
|
||||
reply_markup=types.InlineKeyboardMarkup(),
|
||||
force_text=True,
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
amount = int(message.text.strip())
|
||||
if amount <= 0:
|
||||
raise ValueError
|
||||
if amount < 10:
|
||||
await edit_or_send_message(
|
||||
target_message=message,
|
||||
text="Минимальная сумма для оплаты криптовалютой — 10 рублей.",
|
||||
reply_markup=types.InlineKeyboardMarkup(),
|
||||
force_text=True,
|
||||
)
|
||||
return
|
||||
except Exception:
|
||||
await edit_or_send_message(
|
||||
target_message=message,
|
||||
text="Некорректная сумма. Введите целое число больше 0.",
|
||||
reply_markup=types.InlineKeyboardMarkup(),
|
||||
force_text=True,
|
||||
)
|
||||
return
|
||||
|
||||
await state.update_data(amount=amount)
|
||||
payment_url = await generate_heleket_payment_link(amount, message.chat.id, method)
|
||||
|
||||
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=message,
|
||||
text=HELEKET_PAYMENT_MESSAGE.format(amount=amount),
|
||||
reply_markup=confirm_keyboard,
|
||||
force_text=True,
|
||||
)
|
||||
|
||||
await state.set_state(ReplenishBalanceHeleket.waiting_for_payment_confirmation)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("heleket_amount|"))
|
||||
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
parts = callback_query.data.split("|")
|
||||
method_name = parts[1]
|
||||
amount_str = parts[2]
|
||||
|
||||
method = next((m for m in HELEKET_PAYMENT_METHODS if m["name"] == method_name), None)
|
||||
|
||||
if not method or not method["enable"]:
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text="Ошибка: выбранный способ оплаты недоступен.",
|
||||
reply_markup=types.InlineKeyboardMarkup(),
|
||||
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=types.InlineKeyboardMarkup(),
|
||||
force_text=True,
|
||||
)
|
||||
return
|
||||
|
||||
await state.update_data(amount=amount)
|
||||
payment_url = await generate_heleket_payment_link(amount, callback_query.message.chat.id, method)
|
||||
|
||||
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=HELEKET_PAYMENT_MESSAGE.format(amount=amount),
|
||||
reply_markup=confirm_keyboard,
|
||||
force_text=True,
|
||||
)
|
||||
|
||||
await state.set_state(ReplenishBalanceHeleket.waiting_for_payment_confirmation)
|
||||
|
||||
|
||||
async def generate_heleket_payment_link(amount: int, tg_id: int, method: dict) -> str:
|
||||
"""
|
||||
Создание платежа в Heleket и получение ссылки на оплату
|
||||
"""
|
||||
url = "https://api.heleket.com/v1/payment"
|
||||
|
||||
unique_order_id = f"{int(time.time())}_{tg_id}"
|
||||
|
||||
usd_amount = round(amount / HELEKET_CURRENCY_RATE, 2)
|
||||
|
||||
data = {
|
||||
"amount": str(usd_amount),
|
||||
"currency": method["currency"],
|
||||
"order_id": unique_order_id,
|
||||
"url_success": HELEKET_SUCCESS_URL,
|
||||
"url_return": HELEKET_RETURN_URL,
|
||||
"url_callback": HELEKET_CALLBACK_URL,
|
||||
"additional_data": f"tg_id:{tg_id},rub_amount:{amount}",
|
||||
}
|
||||
|
||||
if method.get("to_currency"):
|
||||
data["to_currency"] = method["to_currency"]
|
||||
|
||||
json_data = json.dumps(data, separators=(',', ':'))
|
||||
base64_data = base64.b64encode(json_data.encode('utf-8')).decode('utf-8')
|
||||
sign_string = base64_data + HELEKET_API_KEY
|
||||
signature = hashlib.md5(sign_string.encode('utf-8')).hexdigest()
|
||||
|
||||
headers = {
|
||||
"merchant": HELEKET_MERCHANT_ID,
|
||||
"sign": signature,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(url, headers=headers, data=json_data, timeout=60) as resp:
|
||||
if resp.status == 200:
|
||||
try:
|
||||
resp_json = await resp.json()
|
||||
if resp_json.get("state") == 0:
|
||||
payment_url = resp_json.get("result", {}).get("url")
|
||||
if payment_url:
|
||||
logger.info(f"Heleket payment URL created for user {tg_id}")
|
||||
return payment_url
|
||||
else:
|
||||
logger.error(f"Heleket: No URL in response: {resp_json}")
|
||||
return "https://heleket.com/"
|
||||
else:
|
||||
logger.error(f"Heleket: Unsuccessful response: {resp_json}")
|
||||
return "https://heleket.com/"
|
||||
except Exception as e:
|
||||
logger.error(f"Heleket: Error parsing JSON response: {e}")
|
||||
text = await resp.text()
|
||||
logger.error(f"Heleket: Response content: {text}")
|
||||
return "https://heleket.com/"
|
||||
else:
|
||||
try:
|
||||
error_json = await resp.json()
|
||||
logger.error(f"Heleket API error: status={resp.status}, response={error_json}")
|
||||
except Exception:
|
||||
text = await resp.text()
|
||||
logger.error(f"Heleket API error: status={resp.status}, non-JSON response: {text}")
|
||||
return "https://heleket.com/"
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating Heleket payment: {e}")
|
||||
return "https://heleket.com/"
|
||||
@@ -101,6 +101,7 @@ async def process_callback_pay_kassai(callback_query: types.CallbackQuery, state
|
||||
target_message=callback_query.message,
|
||||
text="Выберите способ оплаты через KassaAI:",
|
||||
reply_markup=builder.as_markup(),
|
||||
force_text=True,
|
||||
)
|
||||
await state.update_data(message_id=callback_query.message.message_id, chat_id=callback_query.message.chat.id)
|
||||
await state.set_state(ReplenishBalanceKassaiState.choosing_method)
|
||||
@@ -153,6 +154,7 @@ async def process_method_selection(callback_query: types.CallbackQuery, state: F
|
||||
target_message=callback_query.message,
|
||||
text=method["desc"],
|
||||
reply_markup=builder.as_markup(),
|
||||
force_text=True,
|
||||
)
|
||||
await state.update_data(message_id=callback_query.message.message_id, chat_id=callback_query.message.chat.id)
|
||||
await state.set_state(ReplenishBalanceKassaiState.choosing_amount)
|
||||
|
||||
@@ -7,13 +7,16 @@ from config import TBLOCKER_WEBHOOK_PATH
|
||||
from .tblocker import tblocker_webhook
|
||||
from .wata_payment import wata_payment_webhook
|
||||
from .kassai_payment import kassai_payment_webhook
|
||||
from .heleket_payment import heleket_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(TBLOCKER_WEBHOOK_PATH, tblocker_webhook)
|
||||
router.add_post(WATA_WEBHOOK_PATH, wata_payment_webhook)
|
||||
router.add_post(KASSAI_WEBHOOK_PATH, kassai_payment_webhook)
|
||||
router.add_post(HELEKET_WEBHOOK_PATH, heleket_payment_webhook)
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import hashlib
|
||||
import base64
|
||||
import json
|
||||
from aiohttp import web
|
||||
from database import add_payment, async_session_maker, update_balance
|
||||
from handlers.payments.utils import send_payment_success_notification
|
||||
from config import HELEKET_API_KEY, HELEKET_CURRENCY_RATE
|
||||
from logger import logger
|
||||
|
||||
processed_payments = set()
|
||||
|
||||
|
||||
async def heleket_payment_webhook(request: web.Request):
|
||||
"""
|
||||
Обработчик вебхука от Heleket для подтверждения оплаты
|
||||
"""
|
||||
try:
|
||||
data = await request.json()
|
||||
|
||||
logger.info(f"Heleket webhook received from {request.remote}")
|
||||
logger.info(f"Heleket webhook data: {data}")
|
||||
|
||||
signature = data.get("sign", "")
|
||||
if not verify_heleket_webhook_signature(data, signature):
|
||||
logger.error("Heleket: Invalid signature")
|
||||
return web.Response(status=400, text="Invalid signature")
|
||||
|
||||
uuid = data.get("uuid")
|
||||
order_id = data.get("order_id")
|
||||
payment_status = data.get("status")
|
||||
amount = data.get("amount")
|
||||
payment_amount = data.get("payment_amount")
|
||||
additional_data = data.get("additional_data", "")
|
||||
|
||||
logger.info(f"Heleket payment: uuid={uuid}, order_id={order_id}, status={payment_status}, amount={amount}, payment_amount={payment_amount}")
|
||||
|
||||
if payment_status != "paid":
|
||||
logger.info(f"Heleket: Payment not completed, status={payment_status}")
|
||||
return web.Response(status=200, text="OK")
|
||||
|
||||
if not amount or not uuid:
|
||||
logger.error(f"Heleket: Missing amount or uuid")
|
||||
return web.Response(status=400, text="Missing required fields")
|
||||
|
||||
if uuid in processed_payments:
|
||||
logger.warning(f"Heleket: Duplicate payment uuid={uuid}")
|
||||
return web.Response(status=200, text="OK")
|
||||
|
||||
if order_id and order_id in processed_payments:
|
||||
logger.warning(f"Heleket: Duplicate payment order_id={order_id}")
|
||||
return web.Response(status=200, text="OK")
|
||||
|
||||
tg_id = None
|
||||
rub_amount = None
|
||||
try:
|
||||
if additional_data and "tg_id:" in additional_data:
|
||||
parts = additional_data.split(",")
|
||||
for part in parts:
|
||||
if part.startswith("tg_id:"):
|
||||
tg_id = int(part.split("tg_id:")[1])
|
||||
elif part.startswith("rub_amount:"):
|
||||
rub_amount = float(part.split("rub_amount:")[1])
|
||||
|
||||
elif order_id and "_" in order_id:
|
||||
tg_id = int(order_id.split("_")[1])
|
||||
usd_amount = float(amount)
|
||||
rub_amount = usd_amount * HELEKET_CURRENCY_RATE
|
||||
else:
|
||||
logger.error(f"Heleket: Cannot extract tg_id from data")
|
||||
return web.Response(status=400, text="Cannot extract user ID")
|
||||
|
||||
except (ValueError, IndexError) as e:
|
||||
logger.error(f"Heleket: Error extracting tg_id or rub_amount: {e}")
|
||||
return web.Response(status=400, text="Invalid user ID or amount format")
|
||||
|
||||
if not rub_amount:
|
||||
logger.error(f"Heleket: Could not determine rub_amount")
|
||||
return web.Response(status=400, text="Cannot determine amount")
|
||||
|
||||
async with async_session_maker() as session:
|
||||
await update_balance(session, tg_id, rub_amount)
|
||||
await send_payment_success_notification(tg_id, rub_amount, session)
|
||||
await add_payment(session, tg_id, rub_amount, "heleket")
|
||||
|
||||
processed_payments.add(uuid)
|
||||
if order_id:
|
||||
processed_payments.add(order_id)
|
||||
|
||||
logger.info(f"✅ Heleket: Payment processed for user {tg_id}, amount {rub_amount} RUB (${amount}), uuid={uuid}")
|
||||
return web.Response(status=200, text="OK")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Heleket webhook error: {e}")
|
||||
return web.Response(status=500, text="Internal server error")
|
||||
|
||||
|
||||
def verify_heleket_webhook_signature(data: dict, signature: str) -> bool:
|
||||
"""
|
||||
Проверка подписи вебхука Heleket
|
||||
"""
|
||||
try:
|
||||
data_without_sign = {k: v for k, v in data.items() if k != "sign"}
|
||||
|
||||
json_data = json.dumps(data_without_sign, separators=(',', ':'))
|
||||
base64_data = base64.b64encode(json_data.encode('utf-8')).decode('utf-8')
|
||||
sign_string = base64_data + HELEKET_API_KEY
|
||||
expected_signature = hashlib.md5(sign_string.encode('utf-8')).hexdigest()
|
||||
|
||||
result = signature.upper() == expected_signature.upper()
|
||||
|
||||
if not result:
|
||||
logger.error(f"Heleket webhook signature mismatch")
|
||||
logger.error(f"Expected: {expected_signature}, Got: {signature}")
|
||||
logger.error(f"Base64 data: {base64_data}")
|
||||
logger.error(f"Sign string: {sign_string}")
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Heleket signature verification error: {e}")
|
||||
return False
|
||||
Reference in New Issue
Block a user