diff --git a/app/handlers/balance/yookassa.py b/app/handlers/balance/yookassa.py
index c7e9fe97..2b5601c7 100644
--- a/app/handlers/balance/yookassa.py
+++ b/app/handlers/balance/yookassa.py
@@ -246,34 +246,125 @@ async def process_yookassa_sbp_payment_amount(
return
confirmation_url = payment_result.get("confirmation_url")
- if not confirmation_url:
- await message.answer("❌ Ошибка получения ссылки для оплаты через СБП. Обратитесь в поддержку.")
+ qr_confirmation_data = payment_result.get("qr_confirmation_data")
+
+ if not confirmation_url and not qr_confirmation_data:
+ await message.answer("❌ Ошибка получения данных для оплаты через СБП. Обратитесь в поддержку.")
await state.clear()
return
- keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
- [types.InlineKeyboardButton(text="🏦 Оплатить через СБП", url=confirmation_url)],
- [types.InlineKeyboardButton(text="📊 Проверить статус", callback_data=f"check_yookassa_{payment_result['local_payment_id']}")],
- [types.InlineKeyboardButton(text=texts.BACK, callback_data="balance_topup")]
- ])
+ # Подготовим QR-код для вставки в основное сообщение
+ qr_photo = None
+ if qr_confirmation_data:
+ try:
+ # Импортируем необходимые модули для генерации QR-кода
+ import base64
+ from io import BytesIO
+ import qrcode
+ from aiogram.types import BufferedInputFile
+
+ # Создаем QR-код из полученных данных
+ qr = qrcode.QRCode(version=1, box_size=10, border=5)
+ qr.add_data(qr_confirmation_data)
+ qr.make(fit=True)
+
+ img = qr.make_image(fill_color="black", back_color="white")
+
+ # Сохраняем изображение в байты
+ img_bytes = BytesIO()
+ img.save(img_bytes, format='PNG')
+ img_bytes.seek(0)
+
+ qr_photo = BufferedInputFile(img_bytes.getvalue(), filename="qrcode.png")
+ except ImportError:
+ logger.warning("qrcode библиотека не установлена, QR-код не будет сгенерирован")
+ except Exception as e:
+ logger.error(f"Ошибка генерации QR-кода: {e}")
- await message.answer(
- f"🏦 Оплата через СБП\n\n"
+ # Если нет QR-данных из YooKassa, но есть URL, генерируем QR-код из URL
+ if not qr_photo and confirmation_url:
+ try:
+ # Импортируем необходимые модули для генерации QR-кода
+ import base64
+ from io import BytesIO
+ import qrcode
+ from aiogram.types import BufferedInputFile
+
+ # Создаем QR-код из URL
+ qr = qrcode.QRCode(version=1, box_size=10, border=5)
+ qr.add_data(confirmation_url)
+ qr.make(fit=True)
+
+ img = qr.make_image(fill_color="black", back_color="white")
+
+ # Сохраняем изображение в байты
+ img_bytes = BytesIO()
+ img.save(img_bytes, format='PNG')
+ img_bytes.seek(0)
+
+ qr_photo = BufferedInputFile(img_bytes.getvalue(), filename="qrcode.png")
+ except ImportError:
+ logger.warning("qrcode библиотека не установлена, QR-код не будет сгенерирован")
+ except Exception as e:
+ logger.error(f"Ошибка генерации QR-кода из URL: {e}")
+
+ # Создаем клавиатуру с кнопками для оплаты по ссылке и проверки статуса
+ keyboard_buttons = []
+
+ # Добавляем кнопку оплаты, если доступна ссылка
+ if confirmation_url:
+ keyboard_buttons.append([types.InlineKeyboardButton(text="🔗 Перейти к оплате", url=confirmation_url)])
+ else:
+ # Если ссылка недоступна, предлагаем оплатить через ID платежа в приложении банка
+ keyboard_buttons.append([types.InlineKeyboardButton(text="📱 Оплатить в приложении банка", callback_data="temp_disabled")])
+
+ # Добавляем общие кнопки
+ keyboard_buttons.append([types.InlineKeyboardButton(text="📊 Проверить статус", callback_data=f"check_yookassa_{payment_result['local_payment_id']}")])
+ keyboard_buttons.append([types.InlineKeyboardButton(text=texts.BACK, callback_data="balance_topup")])
+
+ keyboard = types.InlineKeyboardMarkup(inline_keyboard=keyboard_buttons)
+
+ # Подготавливаем текст сообщения
+ message_text = (
+ f"🔗 Оплата через СБП\n\n"
f"💰 Сумма: {settings.format_price(amount_kopeks)}\n"
f"🆔 ID платежа: {payment_result['yookassa_payment_id'][:8]}...\n\n"
- f"📱 Инструкция:\n"
- f"1. Нажмите кнопку 'Оплатить через СБП'\n"
- f"2. Вас перенаправит в приложение вашего банка\n"
- f"3. Подтвердите платеж через СБП\n"
- f"4. Деньги поступят на баланс автоматически\n\n"
- f"🔒 Оплата происходит через защищенную систему YooKassa\n"
- f"✅ Принимаем СБП от всех банков-участников\n\n"
- f"❓ Если возникнут проблемы, обратитесь в {settings.get_support_contact_display_html()}",
- reply_markup=keyboard,
- parse_mode="HTML"
)
- await state.clear()
+ # Добавляем инструкции в зависимости от доступных способов оплаты
+ if not confirmation_url:
+ message_text += (
+ f"📱 Инструкция по оплате:\n"
+ f"1. Откройте приложение вашего банка\n"
+ f"2. Найдите функцию оплаты по реквизитам или перевод по СБП\n"
+ f"3. Введите ID платежа: {payment_result['yookassa_payment_id']}\n"
+ f"4. Подтвердите платеж в приложении банка\n"
+ f"5. Деньги поступят на баланс автоматически\n\n"
+ )
+
+ message_text += (
+ f"🔒 Оплата происходит через защищенную систему YooKassa\n"
+ f"✅ Принимаем СБП от всех банков-участников\n\n"
+ f"❓ Если возникнут проблемы, обратитесь в {settings.get_support_contact_display_html()}"
+ )
+
+ # Отправляем сообщение с инструкциями и клавиатурой
+ # Если есть QR-код, отправляем его как медиа-сообщение
+ if qr_photo:
+ # Используем метод отправки медиа-группы или фото с описанием
+ await message.answer_photo(
+ photo=qr_photo,
+ caption=message_text,
+ reply_markup=keyboard,
+ parse_mode="HTML"
+ )
+ else:
+ # Если QR-код недоступен, отправляем обычное текстовое сообщение
+ await message.answer(
+ message_text,
+ reply_markup=keyboard,
+ parse_mode="HTML"
+ )
logger.info(f"Создан платеж YooKassa СБП для пользователя {db_user.telegram_id}: "
f"{amount_kopeks//100}₽, ID: {payment_result['yookassa_payment_id']}")
@@ -284,6 +375,9 @@ async def process_yookassa_sbp_payment_amount(
await state.clear()
+
+
+
@error_handler
async def check_yookassa_payment_status(
callback: types.CallbackQuery,
diff --git a/app/services/payment/yookassa.py b/app/services/payment/yookassa.py
index 65ccaca0..9bb8bd8f 100644
--- a/app/services/payment/yookassa.py
+++ b/app/services/payment/yookassa.py
@@ -172,7 +172,7 @@ class YooKassaPaymentMixin:
currency="RUB",
description=description,
status=yookassa_response["status"],
- confirmation_url=yookassa_response.get("confirmation_url"),
+ confirmation_url=yookassa_response.get("confirmation_url"), # Используем confirmation URL
metadata_json=payment_metadata,
payment_method_type="bank_card",
yookassa_created_at=None,
@@ -193,7 +193,8 @@ class YooKassaPaymentMixin:
return {
"local_payment_id": local_payment.id,
"yookassa_payment_id": yookassa_response["id"],
- "confirmation_url": yookassa_response.get("confirmation_url"),
+ "confirmation_url": yookassa_response.get("confirmation_url"), # URL для подтверждения
+ "qr_confirmation_data": yookassa_response.get("qr_confirmation_data"), # Данные для QR-кода
"confirmation_token": confirmation_token,
"amount_kopeks": amount_kopeks,
"amount_rubles": amount_rubles,
diff --git a/app/services/yookassa_service.py b/app/services/yookassa_service.py
index baf001bd..3a451014 100644
--- a/app/services/yookassa_service.py
+++ b/app/services/yookassa_service.py
@@ -188,6 +188,8 @@ class YooKassaService:
}
try:
+ # Создаем один платеж с подтверждением через QR
+ # Это позволит получить QR-код для пользователя
builder = PaymentRequestBuilder()
builder.set_amount({
@@ -197,6 +199,7 @@ class YooKassaService:
builder.set_capture(True)
+ # Устанавливаем подтверждение через redirect для получения вебхуков
builder.set_confirmation({
"type": "redirect",
"return_url": self.return_url
@@ -234,7 +237,7 @@ class YooKassaService:
payment_request = builder.build()
logger.info(
- f"Создание платежа YooKassa СБП (Idempotence-Key: {idempotence_key}). "
+ f"Создание платежа YooKassa СБП с подтверждением 'qr' (Idempotence-Key: {idempotence_key}). "
f"Сумма: {amount} {currency}. Метаданные: {metadata}. Чек: {receipt_data_dict}")
loop = asyncio.get_running_loop()
@@ -242,11 +245,14 @@ class YooKassaService:
None, lambda: YooKassaPayment.create(payment_request, idempotence_key))
logger.info(
- f"Ответ YooKassa Payment.create (СБП): ID={response.id}, Status={response.status}, Paid={response.paid}")
+ f"Ответ YooKassa Payment.create (СБП, qr): ID={response.id}, Status={response.status}, Paid={response.paid}")
+ # Возвращаем данные платежа с QR-подтверждением
+ # Пользователь может использовать QR-код или оплатить через приложение банка по ID платежа
return {
"id": response.id,
- "confirmation_url": response.confirmation.confirmation_url if response.confirmation else None,
+ "qr_confirmation_data": response.confirmation.confirmation_data if response.confirmation and hasattr(response.confirmation, 'confirmation_data') else None,
+ "confirmation_url": response.confirmation.confirmation_url if response.confirmation and hasattr(response.confirmation, 'confirmation_url') else None,
"status": response.status,
"metadata": response.metadata,
"amount_value": float(response.amount.value),
@@ -263,6 +269,105 @@ class YooKassaService:
logger.error(f"Ошибка создания платежа YooKassa СБП: {e}", exc_info=True)
return None
+ async def _create_sbp_payment_with_confirmation_type(
+ self,
+ amount: float,
+ currency: str,
+ description: str,
+ metadata: Dict[str, Any],
+ customer_contact_for_receipt: Dict[str, str],
+ confirmation_type: str) -> Optional[Dict[str, Any]]:
+ """Создает SBP платеж с указанным типом подтверждения"""
+ try:
+ builder = PaymentRequestBuilder()
+
+ builder.set_amount({
+ "value": str(round(amount, 2)),
+ "currency": currency.upper()
+ })
+
+ builder.set_capture(True)
+
+ if confirmation_type == "qr":
+ builder.set_confirmation({
+ "type": "qr"
+ })
+ else: # redirect
+ builder.set_confirmation({
+ "type": "redirect",
+ "return_url": self.return_url
+ })
+
+ builder.set_description(description)
+
+ builder.set_metadata(metadata)
+
+ builder.set_payment_method_data({
+ "type": "sbp"
+ })
+
+ receipt_items_list: List[Dict[str, Any]] = [{
+ "description": description[:128],
+ "quantity": "1.00",
+ "amount": {
+ "value": str(round(amount, 2)),
+ "currency": currency.upper()
+ },
+ "vat_code": str(getattr(settings, 'YOOKASSA_VAT_CODE', 1)),
+ "payment_mode": getattr(settings, 'YOOKASSA_PAYMENT_MODE', 'full_payment'),
+ "payment_subject": getattr(settings, 'YOOKASSA_PAYMENT_SUBJECT', 'service')
+ }]
+
+ receipt_data_dict: Dict[str, Any] = {
+ "customer": customer_contact_for_receipt,
+ "items": receipt_items_list
+ }
+
+ builder.set_receipt(receipt_data_dict)
+
+ idempotence_key = str(uuid.uuid4())
+
+ payment_request = builder.build()
+
+ logger.info(
+ f"Создание платежа YooKassa СБП с подтверждением '{confirmation_type}' (Idempotence-Key: {idempotence_key}). "
+ f"Сумма: {amount} {currency}. Метаданные: {metadata}. Чек: {receipt_data_dict}")
+
+ loop = asyncio.get_running_loop()
+ response = await loop.run_in_executor(
+ None, lambda: YooKassaPayment.create(payment_request, idempotence_key))
+
+ logger.info(
+ f"Ответ YooKassa Payment.create (СБП, {confirmation_type}): ID={response.id}, Status={response.status}, Paid={response.paid}")
+
+ result = {
+ "id": response.id,
+ "status": response.status,
+ "metadata": response.metadata,
+ "amount_value": float(response.amount.value),
+ "amount_currency": response.amount.currency,
+ "idempotence_key_used": idempotence_key,
+ "paid": response.paid,
+ "refundable": response.refundable,
+ "created_at": response.created_at.isoformat() if hasattr(
+ response.created_at, 'isoformat') else str(response.created_at),
+ "description_from_yk": response.description,
+ "test_mode": response.test if hasattr(response, 'test') else None
+ }
+
+ # Добавляем данные подтверждения в зависимости от типа
+ if confirmation_type == "qr":
+ if response.confirmation and hasattr(response.confirmation, 'confirmation_data'):
+ result["confirmation_data"] = response.confirmation.confirmation_data
+ else: # redirect
+ if response.confirmation and hasattr(response.confirmation, 'confirmation_url'):
+ result["confirmation_url"] = response.confirmation.confirmation_url
+
+ return result
+ except Exception as e:
+ logger.error(f"Ошибка создания платежа YooKassa СБП с подтверждением '{confirmation_type}': {e}", exc_info=True)
+ return None
+
async def get_payment_info(
self, payment_id_in_yookassa: str) -> Optional[Dict[str, Any]]:
diff --git a/docker-compose.yml b/docker-compose.yml
index f3d58b05..d7849b7a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -35,7 +35,7 @@ services:
retries: 3
bot:
- image: fr1ngg/remnawave-bedolaga-telegram-bot:latest
+ build: .
container_name: remnawave_bot
restart: unless-stopped
depends_on: