robokassa_beta

This commit is contained in:
Vladless
2024-11-15 15:07:51 +03:00
parent 37110b230b
commit 1a85ff4ae8
5 changed files with 227 additions and 5 deletions
+4 -2
View File
@@ -1,7 +1,7 @@
from aiogram import Bot, Dispatcher, Router
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.logging import LoggingMiddleware
@@ -13,7 +13,7 @@ router = Router()
from handlers import commands, coupons, notifications, pay, profile, start
from handlers.admin import admin_commands, admin_coupons, admin_panel, admin_user_editor
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_coupons.router)
@@ -34,6 +34,8 @@ if CRYPTO_BOT_ENABLE:
dp.include_router(cryprobot_pay.router)
if STARS_ENABLE:
dp.include_router(stars_pay.router)
if ROBOKASSA_ENABLE:
dp.include_router(robokassa_pay.router)
dp.include_router(notifications.router)
dp.message.middleware(LoggingMiddleware())
+8 -1
View File
@@ -2,7 +2,7 @@ from aiogram import F, Router
from aiogram.types import CallbackQuery, InlineKeyboardButton
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 handlers.start import send_welcome_message
@@ -42,6 +42,13 @@ async def handle_pay(callback_query: CallbackQuery):
callback_data="pay_stars",
)
)
if ROBOKASSA_ENABLE:
builder.row(
InlineKeyboardButton(
text="⭐ RoboKassa",
callback_data="pay_robokassa",
)
)
builder.row(
InlineKeyboardButton(
text="🎟️ Активировать купон", callback_data="activate_coupon"
-1
View File
@@ -170,7 +170,6 @@ async def process_amount_selection(
await callback_query.answer()
async def send_payment_success_notification(user_id: int, amount: float):
try:
builder = InlineKeyboardBuilder()
+211
View File
@@ -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(),
)
+4 -1
View File
@@ -7,12 +7,13 @@ from loguru import logger
from backup import backup_database
from bot import bot, dp, router
from config import CRYPTO_BOT_ENABLE, DEV_MODE, FREEKASSA_ENABLE, SUB_PATH, WEBAPP_HOST, WEBAPP_PORT, WEBHOOK_PATH, WEBHOOK_URL, YOOKASSA_ENABLE
from config import CRYPTO_BOT_ENABLE, DEV_MODE, FREEKASSA_ENABLE, ROBOKASSA_ENABLE, SUB_PATH, WEBAPP_HOST, WEBAPP_PORT, WEBHOOK_PATH, WEBHOOK_URL, YOOKASSA_ENABLE
from database import init_db
from handlers.keys.subscriptions import handle_subscription
from handlers.notifications import notify_expiring_keys
from handlers.payments.cryprobot_pay import cryptobot_webhook
from handlers.payments.freekassa_pay import freekassa_webhook
from handlers.payments.robokassa_pay import robokassa_webhook
from handlers.payments.yookassa_pay import yookassa_webhook
@@ -71,6 +72,8 @@ async def main():
if CRYPTO_BOT_ENABLE:
app.router.add_post("/cryptobot/webhook", cryptobot_webhook)
app.router.add_get(f"{SUB_PATH}{{email}}", handle_subscription)
if ROBOKASSA_ENABLE:
app.router.add_post("/robokassa/webhook", robokassa_webhook)
SimpleRequestHandler(dispatcher=dp, bot=bot).register(app, path=WEBHOOK_PATH)
setup_application(app, dp, bot=bot)