Created
March 3, 2026 01:50
-
-
Save refet-crypto/c8e2804a4878a011d8ccb5eab4d6a6c2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| seen[crypto] = True | |
| text += ( | |
| f"🪙 *{crypto}*\n" | |
| f" Курс: `{float(d['current_course']):,.2f}` ₽\n" | |
| f" Комиссия: {d['comission_percent']}%\n" | |
| f" Мин: {d['min_amount']} ₽ | Макс: {d['max_amount']:,} ₽\n\n" | |
| ) | |
| kb = InlineKeyboardMarkup(inline_keyboard=[ | |
| [InlineKeyboardButton(text="💱 Начать обмен", callback_data="start_exchange")], | |
| [InlineKeyboardButton(text="🔙 Назад", callback_data="back_start")], | |
| ]) | |
| await call.message.edit_text(text, parse_mode="Markdown", reply_markup=kb) | |
| except Exception as e: | |
| await call.message.edit_text(f"❌ Ошибка: {e}") | |
| # ─── Пополнение баланса ────────────────────────────────────────────────────── | |
| @dp.callback_query(F.data == "deposit") | |
| async def deposit_start(call: CallbackQuery, state: FSMContext): | |
| await call.answer() | |
| buttons = [[InlineKeyboardButton(text=lbl, callback_data=f"dep_pay:{key}")] | |
| for key, lbl in PAYMENT_LABELS.items()] | |
| buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="back_start")]) | |
| await state.set_state(DepositStates.choosing_payment) | |
| await call.message.edit_text( | |
| "➕ *Пополнение баланса*\n\nВыбери способ оплаты:", | |
| parse_mode="Markdown", | |
| reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons) | |
| ) | |
| @dp.callback_query(DepositStates.choosing_payment, F.data.startswith("dep_pay:")) | |
| async def deposit_amount(call: CallbackQuery, state: FSMContext): | |
| await call.answer() | |
| pm = call.data.split(":", 1)[1] | |
| await state.update_data(payment_method=pm) | |
| label = PAYMENT_LABELS.get(pm, pm) | |
| await state.set_state(DepositStates.entering_amount) | |
| await call.message.edit_text( | |
| f"✅ Способ: *{label}*\n\nВведи сумму пополнения в рублях (мин. 300 ₽):", | |
| parse_mode="Markdown" | |
| ) | |
| @dp.message(DepositStates.entering_amount) | |
| async def deposit_create(message: Message, state: FSMContext): | |
| text = message.text.strip().replace(" ", "").replace(",", ".") | |
| try: | |
| amount = float(text) | |
| if amount < 300: | |
| await message.answer("❌ Минимум 300 ₽:") | |
| return | |
| except ValueError: | |
| await message.answer("❌ Введи число, например: 1000") | |
| return | |
| data = await state.get_data() | |
| await message.answer("⏳ Создаю заявку...") | |
| try: | |
| result = await create_order(data["payment_method"], DEPOSIT_WALLET, str(int(amount))) | |
| if result.get("response") != "success": | |
| await message.answer(f"❌ {result.get('message', 'Ошибка')}\n\n/start") | |
| await state.clear() | |
| return | |
| order = result["items"][0] | |
| order_id = order["order_id"] | |
| save_order(order_id, message.from_user.id, "deposit", | |
| float(order["amount_payable"]), "0", "RUB", | |
| data["payment_method"], DEPOSIT_WALLET) | |
| active_orders[order_id] = { | |
| "user_id": message.from_user.id, | |
| "type": "deposit", | |
| "status": "unconfirmed" | |
| } | |
| payment_info = order.get("wallet_payment", "—") | |
| bank = order.get("bank_name", "") | |
| fast_link = order.get("fast_link", "") | |
| msg = ( | |
| f"✅ *Заявка на пополнение создана!*\n\n" | |
| f"🆔 ID: `{order_id}`\n" | |
| f"💸 К оплате: *{order['amount_payable']} ₽*\n\n" | |
| ) | |
| if bank: msg += f"🏦 Банк: {bank}\n" | |
| msg += f"💳 Реквизиты:\n`{payment_info}`\n\n" | |
| if fast_link: msg += f"🔲 [QR-код]({fast_link})\n\n" | |
| msg += "⏳ После оплаты баланс пополнится автоматически." | |
| kb = InlineKeyboardMarkup(inline_keyboard=[ | |
| [InlineKeyboardButton(text="🔄 Проверить", callback_data=f"check:{order_id}")], | |
| [InlineKeyboardButton(text="❌ Отменить", callback_data=f"cancel:{order_id}")], | |
| ]) | |
| await message.answer(msg, parse_mode="Markdown", reply_markup=kb) | |
| await state.clear() | |
| except Exception as e: | |
| await message.answer(f"❌ Ошибка: {e}\n\n/start") | |
| await state.clear() | |
| # ─── Обмен → крипта ────────────────────────────────────────────────────────── | |
| @dp.callback_query(F.data == "start_exchange") | |
| async def exchange_start(call: CallbackQuery, state: FSMContext): | |
| await call.answer() | |
| await call.message.edit_text("⏳ Загружаю направления...") | |
| try: | |
| directions = await get_directions() | |
| cryptos = {} | |
| for d in directions: | |
| c = parse_crypto(d["fullname"]) | |
| if c not in cryptos: | |
| cryptos[c] = d["id"] | |
| buttons = [[InlineKeyboardButton(text=f"🪙 {c}", callback_data=f"crypto:{did}:{c}")] | |
| for c, did in cryptos.items()] | |
| buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="back_start")]) | |
| await state.set_state(ExchangeStates.choosing_direction) | |
| await call.message.edit_text( | |
| "💱 *Обмен → крипта*\n\nВыбери криптовалюту:", | |
| parse_mode="Markdown", | |
| reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons) | |
| ) | |
| except Exception as e: | |
| await call.message.edit_text(f"❌ Ошибка: {e}") | |
| @dp.callback_query(ExchangeStates.choosing_direction, F.data.startswith("crypto:")) | |
| async def exchange_pay(call: CallbackQuery, state: FSMContext): | |
| await call.answer() | |
| _, did, crypto = call.data.split(":", 2) | |
| await state.update_data(crypto=crypto, direction_id=did) | |
| buttons = [[InlineKeyboardButton(text=lbl, callback_data=f"xpay:{key}")] | |
| for key, lbl in PAYMENT_LABELS.items()] | |
| buttons.append([InlineKeyboardButton(text="🔙 Назад", callback_data="start_exchange")]) | |
| await state.set_state(ExchangeStates.choosing_payment) | |
| await call.message.edit_text( | |
| f"✅ Крипта: *{crypto}*\n\nВыбери способ оплаты:", | |
| parse_mode="Markdown", | |
| reply_markup=InlineKeyboardMarkup(inline_keyboard=buttons) | |
| ) | |
| @dp.callback_query(ExchangeStates.choosing_payment, F.data.startswith("xpay:")) | |
| async def exchange_amount(call: CallbackQuery, state: FSMContext): | |
| await call.answer() | |
| pm = call.data.split(":", 1)[1] | |
| await state.update_data(payment_method=pm, use_balance=False) | |
| label = PAYMENT_LABELS.get(pm, pm) | |
| bal = get_balance(call.from_user.id) | |
| await state.set_state(ExchangeStates.entering_amount) | |
| buttons = [] | |
| if bal >= 300: | |
| buttons.append([InlineKeyboardButton( | |
| text=f"💰 Списать с баланса ({bal:,.0f} ₽)", | |
| callback_data="use_balance" | |
| )]) | |
| kb = InlineKeyboardMarkup(inline_keyboard=buttons) if buttons else None | |
| await call.message.edit_text( | |
| f"✅ Способ: *{label}*\n" | |
| f"💰 Баланс: *{bal:,.2f} ₽*\n\n" | |
| f"Введи сумму в рублях или спиши с баланса:", | |
| parse_mode="Markdown", reply_markup=kb | |
| ) | |
| @dp.callback_query(ExchangeStates.entering_amount, F.data == "use_balance") | |
| async def use_balance_handler(call: CallbackQuery, state: FSMContext): | |
| await call.answer() | |
| bal = get_balance(call.from_user.id) | |
| if bal < 300: | |
| await call.message.answer("❌ Недостаточно средств (мин. 300 ₽).") | |
| return | |
| await state.update_data(amount=str(int(bal)), use_balance=True) | |
| data = await state.get_data() | |
| await state.set_state(ExchangeStates.entering_wallet) | |
| await call.message.edit_text( | |
| f"💰 Будет списано: *{bal:,.0f} ₽* с баланса\n\n" | |
| f"Введи адрес кошелька для *{data['crypto']}*:", | |
| parse_mode="Markdown" | |
| ) | |
| @dp.message(ExchangeStates.entering_amount) | |
| async def exchange_amount_typed(message: Message, state: FSMContext): | |
| text = message.text.strip().replace(" ", "").replace(",", ".") | |
| try: | |
| amount = float(text) | |
| if amount < 300: | |
| await message.answer("❌ Минимум 300 ₽:") | |
| return | |
| except ValueError: | |
| await message.answer("❌ Введи число:") | |
| return | |
| await state.update_data(amount=str(int(amount)), use_balance=False) | |
| data = await state.get_data() | |
| await state.set_state(ExchangeStates.entering_wallet) | |
| await message.answer( | |
| f"💰 Сумма: *{int(amount):,} ₽*\n\nВведи адрес кошелька для *{data['crypto']}*:", | |
| parse_mode="Markdown" | |
| ) | |
| @dp.message(ExchangeStates.entering_wallet) | |
| async def exchange_create(message: Message, state: FSMContext): | |
| wallet = message.text.strip() | |
| if len(wallet) < 10: | |
| await message.answer("❌ Адрес кошелька слишком короткий:") | |
| return | |
| data = await state.get_data() | |
| use_balance = data.get("use_balance", False) | |
| amount = int(data["amount"]) | |
| user_id = message.from_user.id | |
| if use_balance: | |
| bal = get_balance(user_id) | |
| if bal < amount: | |
| await message.answer(f"❌ Недостаточно средств. Баланс: {bal:,.2f} ₽\n\n/start") | |
| await state.clear() | |
| return | |
| # Временно списываем (вернём при ошибке или отмене) | |
| update_balance(user_id, -amount, "pending", "exchange", | |
| f"Обмен {amount} ₽ → {data['crypto']}") | |
| await message.answer("⏳ Создаю заявку...") | |
| try: | |
| result = await create_order(data["payment_method"], wallet, str(amount)) | |
| if result.get("response") != "success": | |
| if use_balance: | |
| update_balance(user_id, amount, "pending", "refund", "Ошибка создания") | |
| await message.answer(f"❌ {result.get('message', 'Ошибка')}\n\n/start") | |
| await state.clear() | |
| return | |
| order = result["items"][0] | |
| order_id = order["order_id"] | |
| order_type = "exchange_balance" if use_balance else "exchange" | |
| save_order(order_id, user_id, order_type, | |
| float(order["amount_payable"]), order["amount_receivable"], | |
| data["crypto"], data["payment_method"], wallet) | |
| active_orders[order_id] = { | |
| "user_id": user_id, | |
| "type": "exchange", | |
| "crypto": data["crypto"], | |
| "status": "unconfirmed" | |
| } | |
| payment_info = order.get("wallet_payment", "—") | |
| bank = order.get("bank_name", "") | |
| fast_link = order.get("fast_link", "") | |
| msg = ( | |
| f"✅ *Заявка создана!*\n\n" | |
| f"🆔 ID: `{order_id}`\n" | |
| f"💸 Сумма: *{order['amount_payable']} ₽*" | |
| ) | |
| if use_balance: msg += " *(с баланса)*" | |
| msg += ( | |
| f"\n📦 Получишь: *{order['amount_receivable']} {data['crypto']}*\n" | |
| f"📈 Курс: {float(order['exchange_rate']):,.2f} ₽\n\n" | |
| ) | |
| if not use_balance: | |
| if bank: msg += f"🏦 Банк: {bank}\n" | |
| msg += f"💳 Реквизиты:\n`{payment_info}`\n\n" | |
| if fast_link: msg += f"🔲 [QR-код]({fast_link})\n\n" | |
| msg += "⏳ Крипта придёт автоматически после оплаты." | |
| kb = InlineKeyboardMarkup(inline_keyboard=[ | |
| [InlineKeyboardButton(text="🔄 Проверить", callback_data=f"check:{order_id}")], | |
| [InlineKeyboardButton(text="❌ Отменить", callback_data=f"cancel:{order_id}")], | |
| ]) | |
| await message.answer(msg, parse_mode="Markdown", reply_markup=kb) | |
| await state.clear() | |
| except Exception as e: | |
| if use_balance: | |
| update_balance(user_id, amount, "pending", "refund", "Ошибка") | |
| await message.answer(f"❌ Ошибка: {e}\n\n/start") | |
| await state.clear() | |
| # ─── Статус заявки ─────────────────────────────────────────────────────────── | |
| @dp.callback_query(F.data.startswith("check:")) | |
| async def check_status(call: CallbackQuery): | |
| await call.answer("⏳") | |
| order_id = call.data.split(":", 1)[1] | |
| try: | |
| result = await check_order_api(order_id) | |
| orders = result.get("data", {}).get("orders", []) | |
| if not orders: | |
| await call.message.answer("❌ Заявка не найдена.") | |
| return | |
| order = orders[0] | |
| status = order["order_status"] | |
| label = STATUS_LABELS.get(status, status) | |
| text = ( | |
| f"📋 *Статус заявки* `{order_id}`\n\n" | |
| f"{label}\n" | |
| f"💸 {order['amount_payable']} ₽ → {order['amount_receivable']}\n" | |
| ) | |
| if status == "completed": | |
| await call.message.answer(text + "\n🎉 Готово!", parse_mode="Markdown") | |
| elif status in ("autocanceled", "canceled"): | |
| await call.message.answer(text + "\n/start", parse_mode="Markdown") | |
| else: | |
| kb = InlineKeyboardMarkup(inline_keyboard=[ | |
| [InlineKeyboardButton(text="🔄 Обновить", callback_data=f"check:{order_id}")], | |
| [InlineKeyboardButton(text="❌ Отменить", callback_data=f"cancel:{order_id}")], | |
| ]) | |
| await call.message.answer(text, parse_mode="Markdown", reply_markup=kb) | |
| except Exception as e: | |
| await call.message.answer(f"❌ Ошибка: {e}") | |
| # ─── Отмена заявки ─────────────────────────────────────────────────────────── | |
| @dp.callback_query(F.data.startswith("cancel:")) | |
| async def cancel_confirm(call: CallbackQuery): | |
| await call.answer() | |
| order_id = call.data.split(":", 1)[1] | |
| kb = InlineKeyboardMarkup(inline_keyboard=[ | |
| [InlineKeyboardButton(text="✅ Да, отменить", callback_data=f"do_cancel:{order_id}")], | |
| [InlineKeyboardButton(text="🔙 Нет", callback_data=f"check:{order_id}")], | |
| ]) | |
| await call.message.answer(f"❓ Отменить заявку `{order_id}`?", | |
| parse_mode="Markdown", reply_markup=kb) | |
| @dp.callback_query(F.data.startswith("do_cancel:")) | |
| async def do_cancel(call: CallbackQuery): | |
| await call.answer() | |
| order_id = call.data.split(":", 1)[1] | |
| try: | |
| await cancel_order_api(order_id) | |
| active_orders.pop(order_id, None) | |
| update_order_status(order_id, "canceled") | |
| db_order = get_order(order_id) | |
| if db_order and db_order["type"] == "exchange_balance": | |
| refund = db_order["amount_rub"] | |
| update_balance(call.from_user.id, refund, order_id, "refund", f"Возврат #{order_id}") | |
| bal = get_balance(call.from_user.id) | |
| await call.message.answer( | |
| f"🚫 Заявка отменена.\n💰 Возвращено: *{refund:,.0f} ₽*\nБаланс: *{bal:,.2f} ₽*", | |
| parse_mode="Markdown" | |
| ) | |
| else: | |
| await call.message.answer(f"🚫 Заявка `{order_id}` отменена.\n\n/start", | |
| parse_mode="Markdown") | |
| except Exception as e: | |
| await call.message.answer(f"❌ Ошибка: {e}") | |
| # ─── Запуск ────────────────────────────────────────────────────────────────── | |
| async def main(): | |
| db_init() | |
| logger.info("Бот запущен") | |
| loop = asyncio.get_event_loop() | |
| loop.create_task(poll_orders()) | |
| await dp.start_polling(bot) | |
| if __name__ == "__main__": | |
| asyncio.run(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment