robokassa_beta
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
from aiogram import Bot, Dispatcher, Router
|
from aiogram import Bot, Dispatcher, Router
|
||||||
from aiogram.fsm.storage.memory import MemoryStorage
|
from aiogram.fsm.storage.memory import MemoryStorage
|
||||||
|
|
||||||
from config import API_TOKEN, CRYPTO_BOT_ENABLE, FREEKASSA_ENABLE, STARS_ENABLE, YOOKASSA_ENABLE
|
from config import API_TOKEN, CRYPTO_BOT_ENABLE, FREEKASSA_ENABLE, ROBOKASSA_ENABLE, STARS_ENABLE, YOOKASSA_ENABLE
|
||||||
from middlewares.database import DatabaseMiddleware
|
from middlewares.database import DatabaseMiddleware
|
||||||
from middlewares.logging import LoggingMiddleware
|
from middlewares.logging import LoggingMiddleware
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ router = Router()
|
|||||||
from handlers import commands, coupons, notifications, pay, profile, start
|
from handlers import commands, coupons, notifications, pay, profile, start
|
||||||
from handlers.admin import admin_commands, admin_coupons, admin_panel, admin_user_editor
|
from handlers.admin import admin_commands, admin_coupons, admin_panel, admin_user_editor
|
||||||
from handlers.keys import key_management, keys
|
from handlers.keys import key_management, keys
|
||||||
from handlers.payments import cryprobot_pay, freekassa_pay, stars_pay, yookassa_pay
|
from handlers.payments import cryprobot_pay, freekassa_pay, robokassa_pay, stars_pay, yookassa_pay
|
||||||
|
|
||||||
dp.include_router(admin_commands.router)
|
dp.include_router(admin_commands.router)
|
||||||
dp.include_router(admin_coupons.router)
|
dp.include_router(admin_coupons.router)
|
||||||
@@ -34,6 +34,8 @@ if CRYPTO_BOT_ENABLE:
|
|||||||
dp.include_router(cryprobot_pay.router)
|
dp.include_router(cryprobot_pay.router)
|
||||||
if STARS_ENABLE:
|
if STARS_ENABLE:
|
||||||
dp.include_router(stars_pay.router)
|
dp.include_router(stars_pay.router)
|
||||||
|
if ROBOKASSA_ENABLE:
|
||||||
|
dp.include_router(robokassa_pay.router)
|
||||||
dp.include_router(notifications.router)
|
dp.include_router(notifications.router)
|
||||||
|
|
||||||
dp.message.middleware(LoggingMiddleware())
|
dp.message.middleware(LoggingMiddleware())
|
||||||
|
|||||||
+8
-1
@@ -2,7 +2,7 @@ from aiogram import F, Router
|
|||||||
from aiogram.types import CallbackQuery, InlineKeyboardButton
|
from aiogram.types import CallbackQuery, InlineKeyboardButton
|
||||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
|
|
||||||
from config import CRYPTO_BOT_ENABLE, FREEKASSA_ENABLE, STARS_ENABLE, YOOKASSA_ENABLE
|
from config import CRYPTO_BOT_ENABLE, FREEKASSA_ENABLE, ROBOKASSA_ENABLE, STARS_ENABLE, YOOKASSA_ENABLE
|
||||||
from database import get_trial
|
from database import get_trial
|
||||||
from handlers.start import send_welcome_message
|
from handlers.start import send_welcome_message
|
||||||
|
|
||||||
@@ -42,6 +42,13 @@ async def handle_pay(callback_query: CallbackQuery):
|
|||||||
callback_data="pay_stars",
|
callback_data="pay_stars",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if ROBOKASSA_ENABLE:
|
||||||
|
builder.row(
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="⭐ RoboKassa",
|
||||||
|
callback_data="pay_robokassa",
|
||||||
|
)
|
||||||
|
)
|
||||||
builder.row(
|
builder.row(
|
||||||
InlineKeyboardButton(
|
InlineKeyboardButton(
|
||||||
text="🎟️ Активировать купон", callback_data="activate_coupon"
|
text="🎟️ Активировать купон", callback_data="activate_coupon"
|
||||||
|
|||||||
@@ -170,7 +170,6 @@ async def process_amount_selection(
|
|||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def send_payment_success_notification(user_id: int, amount: float):
|
async def send_payment_success_notification(user_id: int, amount: float):
|
||||||
try:
|
try:
|
||||||
builder = InlineKeyboardBuilder()
|
builder = InlineKeyboardBuilder()
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
import uuid
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
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 aiohttp import web
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from bot import bot
|
||||||
|
from config import ROBOKASSA_LOGIN, ROBOKASSA_PASSWORD1, ROBOKASSA_PASSWORD2, ROBOKASSA_TEST_MODE
|
||||||
|
from database import add_connection, check_connection_exists, get_key_count, update_balance
|
||||||
|
from handlers.texts import PAYMENT_OPTIONS
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
class ReplenishBalanceState(StatesGroup):
|
||||||
|
choosing_amount_robokassa = State()
|
||||||
|
waiting_for_payment_confirmation_robokassa = State()
|
||||||
|
entering_custom_amount_robokassa = State()
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_signature(*args) -> str:
|
||||||
|
"""Создание подписи SHA-256 для Робокассы."""
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
data = ":".join(map(str, args))
|
||||||
|
return hashlib.sha256(data.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_payment_link(
|
||||||
|
merchant_login, password1, cost, inv_id, description, is_test
|
||||||
|
):
|
||||||
|
"""Генерация ссылки для оплаты через Робокассу."""
|
||||||
|
signature = calculate_signature(merchant_login, cost, inv_id, password1)
|
||||||
|
payment_url = "https://auth.robokassa.ru/Merchant/Index.aspx"
|
||||||
|
params = {
|
||||||
|
"MerchantLogin": merchant_login,
|
||||||
|
"OutSum": cost,
|
||||||
|
"InvId": inv_id,
|
||||||
|
"Description": description,
|
||||||
|
"SignatureValue": signature,
|
||||||
|
"IsTest": int(is_test),
|
||||||
|
}
|
||||||
|
return f"{payment_url}?{urlencode(params)}"
|
||||||
|
|
||||||
|
|
||||||
|
async def send_message_with_deletion(
|
||||||
|
chat_id, text, reply_markup=None, state=None, message_key="last_message_id"
|
||||||
|
):
|
||||||
|
"""Отправка сообщения с удалением предыдущего."""
|
||||||
|
if state:
|
||||||
|
try:
|
||||||
|
state_data = await state.get_data()
|
||||||
|
previous_message_id = state_data.get(message_key)
|
||||||
|
|
||||||
|
if previous_message_id:
|
||||||
|
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
|
||||||
|
)
|
||||||
|
await state.update_data({message_key: sent_message.message_id})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при удалении/отправке сообщения: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return sent_message
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "pay_robokassa")
|
||||||
|
async def process_callback_pay_robokassa(
|
||||||
|
callback_query: types.CallbackQuery, state: FSMContext
|
||||||
|
):
|
||||||
|
tg_id = callback_query.from_user.id
|
||||||
|
|
||||||
|
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'robokassa_{PAYMENT_OPTIONS[i]["callback_data"]}',
|
||||||
|
),
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=PAYMENT_OPTIONS[i + 1]["text"],
|
||||||
|
callback_data=f'robokassa_{PAYMENT_OPTIONS[i + 1]["callback_data"]}',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
builder.row(
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text=PAYMENT_OPTIONS[i]["text"],
|
||||||
|
callback_data=f'robokassa_{PAYMENT_OPTIONS[i]["callback_data"]}',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
builder.row(
|
||||||
|
InlineKeyboardButton(
|
||||||
|
text="💰 Ввести свою сумму", callback_data="enter_custom_amount_robokassa"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_profile"))
|
||||||
|
|
||||||
|
key_count = await get_key_count(tg_id)
|
||||||
|
|
||||||
|
if key_count == 0:
|
||||||
|
exists = await check_connection_exists(tg_id)
|
||||||
|
if not exists:
|
||||||
|
await add_connection(tg_id, balance=0.0, trial=0)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await bot.delete_message(
|
||||||
|
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Не удалось удалить сообщение: {e}")
|
||||||
|
|
||||||
|
await bot.send_message(
|
||||||
|
chat_id=tg_id,
|
||||||
|
text="Выберите сумму пополнения:",
|
||||||
|
reply_markup=builder.as_markup(),
|
||||||
|
)
|
||||||
|
await state.set_state(ReplenishBalanceState.choosing_amount_robokassa)
|
||||||
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("robokassa_amount|"))
|
||||||
|
async def process_amount_selection(
|
||||||
|
callback_query: types.CallbackQuery, state: FSMContext
|
||||||
|
):
|
||||||
|
data = callback_query.data.split("|", 1)
|
||||||
|
if len(data) != 2:
|
||||||
|
await send_message_with_deletion(
|
||||||
|
callback_query.from_user.id,
|
||||||
|
"Неверные данные для выбора суммы.",
|
||||||
|
state=state,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
amount_str = data[1]
|
||||||
|
try:
|
||||||
|
amount = int(amount_str)
|
||||||
|
except ValueError:
|
||||||
|
await send_message_with_deletion(
|
||||||
|
callback_query.from_user.id, "Некорректная сумма.", state=state
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await state.update_data(amount=amount)
|
||||||
|
inv_id = uuid.uuid4().hex[:8]
|
||||||
|
payment_url = generate_payment_link(
|
||||||
|
ROBOKASSA_LOGIN,
|
||||||
|
ROBOKASSA_PASSWORD1,
|
||||||
|
amount,
|
||||||
|
inv_id,
|
||||||
|
"Пополнение баланса",
|
||||||
|
ROBOKASSA_TEST_MODE,
|
||||||
|
)
|
||||||
|
|
||||||
|
confirm_keyboard = InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(text="Оплатить", url=payment_url)],
|
||||||
|
[InlineKeyboardButton(text="⬅️ Назад", callback_data="pay")],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
await callback_query.message.edit_text(
|
||||||
|
text=f"Вы выбрали пополнение на {amount} рублей. Для оплаты перейдите по ссылке ниже:",
|
||||||
|
reply_markup=confirm_keyboard,
|
||||||
|
)
|
||||||
|
await callback_query.answer()
|
||||||
|
|
||||||
|
|
||||||
|
async def robokassa_webhook(request):
|
||||||
|
"""Обработка уведомлений от Робокассы."""
|
||||||
|
params = dict(await request.post())
|
||||||
|
logger.debug(f"Webhook event received: {params}")
|
||||||
|
|
||||||
|
received_signature = params.get("SignatureValue")
|
||||||
|
amount = params.get("OutSum")
|
||||||
|
inv_id = params.get("InvId")
|
||||||
|
|
||||||
|
signature = calculate_signature(amount, inv_id, ROBOKASSA_PASSWORD2)
|
||||||
|
|
||||||
|
if signature.lower() == received_signature.lower():
|
||||||
|
user_id = params.get("Shp_user_id")
|
||||||
|
await update_balance(int(user_id), float(amount))
|
||||||
|
await send_payment_success_notification(user_id, float(amount))
|
||||||
|
return web.Response(text=f"OK{inv_id}")
|
||||||
|
else:
|
||||||
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
|
async def send_payment_success_notification(user_id: int, amount: float):
|
||||||
|
"""Уведомление о успешном пополнении."""
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.row(
|
||||||
|
InlineKeyboardButton(text="Перейти в профиль", callback_data="view_profile")
|
||||||
|
)
|
||||||
|
|
||||||
|
await bot.send_message(
|
||||||
|
chat_id=user_id,
|
||||||
|
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
|
||||||
|
reply_markup=builder.as_markup(),
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user