feat(withdraw): add user withdrawal flow using CryptoPay

This commit is contained in:
2026-07-20 20:29:36 +05:00
parent 8d4d75f7fd
commit 8ae14f415f
11 changed files with 269 additions and 137 deletions
+74 -29
View File
@@ -1,11 +1,12 @@
from aiogram import F, Router
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery
from aiogram.types import LinkPreviewOptions, CallbackQuery, Message
from aiogram.fsm.state import State, StatesGroup
from config import ADMIN_ID, BOT_USERNAME
from db.storage import load_config, load_stats
from db.storage import load_config, load_stats, save_config
from db.users import get, register, save
from keyboards.user import back_kb
from keyboards.user import back_kb, activate_kb
from runtime import get_bot, get_log_bot
from services.withdrawals import create_crypto_check
from utils.logging import log_admin, safe_edit
@@ -13,6 +14,10 @@ from utils.logging import log_admin, safe_edit
router = Router(name="user")
class WithdrawState(StatesGroup):
waiting_for_amount = State()
def _get_profile(callback: CallbackQuery):
profile = get(callback.from_user.id)
if profile is None:
@@ -77,6 +82,7 @@ async def cb_withdraw(callback: CallbackQuery, state: FSMContext):
await state.clear()
profile = _get_profile(callback)
cfg = load_config()
if profile.balance < cfg.min_withdraw:
await safe_edit(
callback,
@@ -87,37 +93,76 @@ async def cb_withdraw(callback: CallbackQuery, state: FSMContext):
)
await callback.answer()
return
if profile.balance > cfg.treasury:
await safe_edit(
callback,
f"⚠️ Недостаточно средств в казне: <b>{cfg.treasury:.2f} ₽</b>.",
reply_markup=back_kb(),
)
await callback.answer()
return
await callback.answer("⏳ Создаю чек...")
await safe_edit(callback, "⏳ Создаю CryptoBot-чек, подождите...")
amount = profile.balance
check_url = await create_crypto_check(amount)
if not check_url:
await safe_edit(callback, "❌ Не удалось создать чек. Попробуйте позже.", reply_markup=back_kb())
return
profile.balance = 0.0
save(profile.user_id, profile)
cfg.treasury = max(0.0, cfg.treasury - amount)
from db.storage import save_config
save_config(cfg)
await safe_edit(
callback,
f"✅ Чек создан на сумму <b>{amount:.2f} ₽</b>.\n"
f"<a href='{check_url}'>Активировать чек</a>",
f"💳 <b>Вывод средств</b>\n\n"
f"Ваш баланс: <b>{profile.balance:.2f}</b>\n"
f"Минимум: <b>{cfg.min_withdraw:.2f} ₽</b>\n\n"
f"Введите сумму для вывода:",
reply_markup=back_kb(),
disable_web_page_preview=True,
)
bot = get_bot() or callback.message.bot
await state.set_state(WithdrawState.waiting_for_amount)
await callback.answer()
@router.message(WithdrawState.waiting_for_amount)
async def process_withdraw_amount(message: Message, state: FSMContext):
profile = _get_profile(message)
cfg = load_config()
try:
amount = float(message.text)
except ValueError:
await message.answer("❌ Введите корректную сумму числом.")
return
if amount <= 0:
await message.answer("❌ Сумма должна быть больше нуля.")
return
if amount < cfg.min_withdraw:
await message.answer(f"❌ Минимум для вывода: <b>{cfg.min_withdraw:.2f} ₽</b>.")
return
if amount > profile.balance:
await message.answer(
f"❌ Недостаточно средств. Ваш баланс: <b>{profile.balance:.2f} ₽</b>."
)
return
if amount > cfg.treasury:
await message.answer(
f"❌ Недостаточно средств в казне: <b>{cfg.treasury:.2f} ₽</b>."
)
return
await state.clear()
await message.answer("⏳ Создаю CryptoBot-чек, подождите...")
check_url, check_image_url = await create_crypto_check(amount)
if not check_url:
await message.answer(
"❌ Не удалось создать чек. Попробуйте позже.",
reply_markup=back_kb(),
)
return
profile.balance -= amount
save(profile.user_id, profile)
cfg.treasury = max(0.0, cfg.treasury - amount)
save_config(cfg)
await message.answer(
text=f"🦋 Чек на <b>{amount:.2f} ₽</b>.",
reply_markup=activate_kb(url=check_url, amount=amount),
link_preview_options=LinkPreviewOptions(
url=check_image_url, show_above_text=True
),
)
bot = get_bot() or message.bot
await log_admin(
bot,
ADMIN_ID,