diff --git a/app/config.py b/app/config.py index a3a5c6df..5e913589 100644 --- a/app/config.py +++ b/app/config.py @@ -212,6 +212,7 @@ class Settings(BaseSettings): MULENPAY_SHOP_ID: Optional[int] = None MULENPAY_BASE_URL: str = "https://mulenpay.ru/api" MULENPAY_WEBHOOK_PATH: str = "/mulenpay-webhook" + MULENPAY_DISPLAY_NAME: str = "Mulen Pay" MULENPAY_DESCRIPTION: str = "Пополнение баланса" MULENPAY_LANGUAGE: str = "ru" MULENPAY_VAT_CODE: int = 0 @@ -793,6 +794,15 @@ class Settings(BaseSettings): and self.MULENPAY_SHOP_ID is not None ) + def get_mulenpay_display_name(self) -> str: + name = (self.MULENPAY_DISPLAY_NAME or "").strip() + if not name: + return "Mulen Pay" + return name + + def get_mulenpay_display_name_html(self) -> str: + return html.escape(self.get_mulenpay_display_name()) + def is_pal24_enabled(self) -> bool: return ( self.PAL24_ENABLED diff --git a/app/database/crud/mulenpay.py b/app/database/crud/mulenpay.py index 245849da..17648f4a 100644 --- a/app/database/crud/mulenpay.py +++ b/app/database/crud/mulenpay.py @@ -5,6 +5,7 @@ from typing import Optional from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession +from app.config import settings from app.database.models import MulenPayPayment logger = logging.getLogger(__name__) @@ -40,7 +41,8 @@ async def create_mulenpay_payment( await db.refresh(payment) logger.info( - "Создан MulenPay платеж #%s (uuid=%s) на сумму %s копеек для пользователя %s", + "Создан %s платеж #%s (uuid=%s) на сумму %s копеек для пользователя %s", + settings.get_mulenpay_display_name(), payment.mulen_payment_id, uuid, amount_kopeks, diff --git a/app/database/universal_migration.py b/app/database/universal_migration.py index 8dbcbe05..db03e84d 100644 --- a/app/database/universal_migration.py +++ b/app/database/universal_migration.py @@ -3427,18 +3427,19 @@ async def run_universal_migration(): else: logger.warning("⚠️ Проблемы с таблицей CryptoBot payments") - logger.info("=== СОЗДАНИЕ ТАБЛИЦЫ MULEN PAY ===") + mulenpay_name = settings.get_mulenpay_display_name() + logger.info("=== СОЗДАНИЕ ТАБЛИЦЫ %s ===", mulenpay_name) mulenpay_created = await create_mulenpay_payments_table() if mulenpay_created: - logger.info("✅ Таблица Mulen Pay payments готова") + logger.info("✅ Таблица %s payments готова", mulenpay_name) else: - logger.warning("⚠️ Проблемы с таблицей Mulen Pay payments") + logger.warning("⚠️ Проблемы с таблицей %s payments", mulenpay_name) mulenpay_schema_ok = await ensure_mulenpay_payment_schema() if mulenpay_schema_ok: - logger.info("✅ Схема Mulen Pay payments актуальна") + logger.info("✅ Схема %s payments актуальна", mulenpay_name) else: - logger.warning("⚠️ Не удалось обновить схему Mulen Pay payments") + logger.warning("⚠️ Не удалось обновить схему %s payments", mulenpay_name) logger.info("=== СОЗДАНИЕ ТАБЛИЦЫ PAL24 ===") pal24_created = await create_pal24_payments_table() diff --git a/app/external/webhook_server.py b/app/external/webhook_server.py index 99531c59..ec366e6e 100644 --- a/app/external/webhook_server.py +++ b/app/external/webhook_server.py @@ -48,7 +48,12 @@ class WebhookServer: logger.info(f"Webhook сервер настроен:") logger.info(f" - Tribute webhook: POST {settings.TRIBUTE_WEBHOOK_PATH}") if settings.is_mulenpay_enabled(): - logger.info(f" - Mulen Pay webhook: POST {settings.MULENPAY_WEBHOOK_PATH}") + mulenpay_name = settings.get_mulenpay_display_name() + logger.info( + " - %s webhook: POST %s", + mulenpay_name, + settings.MULENPAY_WEBHOOK_PATH, + ) if settings.is_cryptobot_enabled(): logger.info(f" - CryptoBot webhook: POST {settings.CRYPTOBOT_WEBHOOK_PATH}") logger.info(f" - Health check: GET /health") @@ -84,8 +89,10 @@ class WebhookServer: settings.TRIBUTE_WEBHOOK_PATH, ) if settings.is_mulenpay_enabled(): + mulenpay_name = settings.get_mulenpay_display_name() logger.info( - "Mulen Pay webhook URL: http://%s:%s%s", + "%s webhook URL: http://%s:%s%s", + mulenpay_name, settings.TRIBUTE_WEBHOOK_HOST, settings.TRIBUTE_WEBHOOK_PORT, settings.MULENPAY_WEBHOOK_PATH, @@ -128,24 +135,37 @@ class WebhookServer: async def _mulenpay_webhook_handler(self, request: web.Request) -> web.Response: try: - logger.info(f"Получен Mulen Pay webhook: {request.method} {request.path}") - logger.info(f"MulenPay webhook headers: {dict(request.headers)}") + mulenpay_name = settings.get_mulenpay_display_name() + logger.info( + "%s webhook: %s %s", + mulenpay_name, + request.method, + request.path, + ) + logger.info( + "%s webhook headers: %s", + mulenpay_name, + dict(request.headers), + ) raw_body = await request.read() if not raw_body: - logger.warning("Пустой Mulen Pay webhook") + logger.warning("Пустой %s webhook", mulenpay_name) return web.json_response({"status": "error", "reason": "empty_body"}, status=400) # Временно отключаем проверку подписи для отладки # TODO: Включить обратно после настройки MulenPay if not self._verify_mulenpay_signature(request, raw_body): - logger.warning("MulenPay webhook signature verification failed, but processing anyway for debugging") + logger.warning( + "%s webhook signature verification failed, but processing anyway for debugging", + mulenpay_name, + ) # return web.json_response({"status": "error", "reason": "invalid_signature"}, status=401) try: payload = json.loads(raw_body.decode('utf-8')) except json.JSONDecodeError as error: - logger.error(f"Ошибка парсинга Mulen Pay webhook: {error}") + logger.error(f"Ошибка парсинга {mulenpay_name} webhook: {error}") return web.json_response({"status": "error", "reason": "invalid_json"}, status=400) payment_service = PaymentService(self.bot) @@ -160,7 +180,10 @@ class WebhookServer: return web.json_response({"status": "ok"}, status=200) return web.json_response({"status": "error", "reason": "processing_failed"}, status=400) except Exception as error: - logger.error(f"Ошибка обработки Mulen Pay webhook: {error}", exc_info=True) + logger.error( + f"Ошибка обработки {mulenpay_name} webhook: {error}", + exc_info=True, + ) return web.json_response({"status": "error", "reason": "internal_error"}, status=500) finally: try: @@ -169,7 +192,11 @@ class WebhookServer: pass except Exception as error: - logger.error(f"Критическая ошибка Mulen Pay webhook: {error}", exc_info=True) + mulenpay_name = settings.get_mulenpay_display_name() + logger.error( + f"Критическая ошибка {mulenpay_name} webhook: {error}", + exc_info=True, + ) return web.json_response({"status": "error", "reason": "internal_error", "message": str(error)}, status=500) @staticmethod @@ -183,12 +210,13 @@ class WebhookServer: @staticmethod def _verify_mulenpay_signature(request: web.Request, raw_body: bytes) -> bool: secret_key = settings.MULENPAY_SECRET_KEY + display_name = settings.get_mulenpay_display_name() if not secret_key: - logger.error("Mulen Pay secret key is not configured") + logger.error("%s secret key is not configured", display_name) return False # Логируем все заголовки для отладки - logger.info("MulenPay webhook headers for signature verification:") + logger.info("%s webhook headers for signature verification:", display_name) for header_name, header_value in request.headers.items(): if any(keyword in header_name.lower() for keyword in ['signature', 'sign', 'token', 'auth']): logger.info(f" {header_name}: {header_value}") @@ -239,7 +267,7 @@ class WebhookServer: if hmac.compare_digest(normalized_signature_no_padding, expected_urlsafe_base64_signature.rstrip('=')): return True - logger.error("Неверная подпись Mulen Pay webhook") + logger.error("Неверная подпись %s webhook", display_name) return False authorization_header = request.headers.get('Authorization') @@ -252,7 +280,7 @@ class WebhookServer: if hmac.compare_digest(token, secret_key): return True - logger.error("Неверный %s токен Mulen Pay webhook", scheme) + logger.error("Неверный %s токен %s webhook", scheme, display_name) return False if not value and hmac.compare_digest(token, secret_key): @@ -270,11 +298,12 @@ class WebhookServer: return True logger.info( - "Mulen Pay webhook headers received: %s", + "%s webhook headers received: %s", + display_name, {key: value for key, value in request.headers.items() if 'authorization' not in key.lower()} ) - logger.error("Отсутствует подпись Mulen Pay webhook") + logger.error("Отсутствует подпись %s webhook", display_name) return False async def _tribute_webhook_handler(self, request: web.Request) -> web.Response: diff --git a/app/handlers/admin/bot_configuration.py b/app/handlers/admin/bot_configuration.py index 85f88ab0..18ed7a0e 100644 --- a/app/handlers/admin/bot_configuration.py +++ b/app/handlers/admin/bot_configuration.py @@ -1303,7 +1303,10 @@ def _build_settings_keyboard( label = texts.t("PAYMENT_CARD_TRIBUTE", "💳 Банковская карта (Tribute)") test_payment_buttons.append([_test_button(f"{label} · тест", "tribute")]) elif category_key == "MULENPAY": - label = texts.t("PAYMENT_CARD_MULENPAY", "💳 Банковская карта (Mulen Pay)") + label = texts.t( + "PAYMENT_CARD_MULENPAY", + "💳 Банковская карта ({mulenpay_name})", + ).format(mulenpay_name=settings.get_mulenpay_display_name()) test_payment_buttons.append([_test_button(f"{label} · тест", "mulenpay")]) elif category_key == "WATA": label = texts.t("PAYMENT_CARD_WATA", "💳 Банковская карта (WATA)") @@ -1834,8 +1837,13 @@ async def test_payment_provider( return if method == "mulenpay": + mulenpay_name = settings.get_mulenpay_display_name() + mulenpay_name_html = settings.get_mulenpay_display_name_html() if not settings.is_mulenpay_enabled(): - await callback.answer("❌ MulenPay отключен", show_alert=True) + await callback.answer( + f"❌ {mulenpay_name} отключен", + show_alert=True, + ) return amount_kopeks = 1 * 100 @@ -1843,18 +1851,21 @@ async def test_payment_provider( db=db, user_id=db_user.id, amount_kopeks=amount_kopeks, - description="Тестовый платеж MulenPay (админ)", + description=f"Тестовый платеж {mulenpay_name} (админ)", language=language, ) if not payment_result or not payment_result.get("payment_url"): - await callback.answer("❌ Не удалось создать платеж MulenPay", show_alert=True) + await callback.answer( + f"❌ Не удалось создать платеж {mulenpay_name}", + show_alert=True, + ) await _refresh_markup() return payment_url = payment_result["payment_url"] message_text = ( - "🧪 Тестовый платеж MulenPay\n\n" + f"🧪 Тестовый платеж {mulenpay_name_html}\n\n" f"💰 Сумма: {texts.format_price(amount_kopeks)}\n" f"🆔 ID: {payment_result['mulen_payment_id']}" ) @@ -1875,7 +1886,10 @@ async def test_payment_provider( ] ) await callback.message.answer(message_text, reply_markup=reply_markup, parse_mode="HTML") - await callback.answer("✅ Ссылка на платеж MulenPay отправлена", show_alert=True) + await callback.answer( + f"✅ Ссылка на платеж {mulenpay_name} отправлена", + show_alert=True, + ) await _refresh_markup() return diff --git a/app/handlers/balance/mulenpay.py b/app/handlers/balance/mulenpay.py index f6fb5362..128de578 100644 --- a/app/handlers/balance/mulenpay.py +++ b/app/handlers/balance/mulenpay.py @@ -21,19 +21,28 @@ async def start_mulenpay_payment( state: FSMContext, ): texts = get_texts(db_user.language) + mulenpay_name = settings.get_mulenpay_display_name() + mulenpay_name_html = settings.get_mulenpay_display_name_html() if not settings.is_mulenpay_enabled(): - await callback.answer("❌ Оплата через Mulen Pay временно недоступна", show_alert=True) + await callback.answer( + f"❌ Оплата через {mulenpay_name} временно недоступна", + show_alert=True, + ) return - message_text = texts.t( + message_template = texts.t( "MULENPAY_TOPUP_PROMPT", ( - "💳 Оплата через Mulen Pay\n\n" + "💳 Оплата через {mulenpay_name_html}\n\n" "Введите сумму для пополнения от 100 до 100 000 ₽.\n" - "Оплата происходит через защищенную платформу Mulen Pay." + "Оплата происходит через защищенную платформу {mulenpay_name}." ), ) + message_text = message_template.format( + mulenpay_name=mulenpay_name, + mulenpay_name_html=mulenpay_name_html, + ) keyboard = get_back_keyboard(db_user.language) @@ -63,9 +72,11 @@ async def process_mulenpay_payment_amount( state: FSMContext, ): texts = get_texts(db_user.language) + mulenpay_name = settings.get_mulenpay_display_name() + mulenpay_name_html = settings.get_mulenpay_display_name_html() if not settings.is_mulenpay_enabled(): - await message.answer("❌ Оплата через Mulen Pay временно недоступна") + await message.answer(f"❌ Оплата через {mulenpay_name} временно недоступна") return if amount_kopeks < settings.MULENPAY_MIN_AMOUNT_KOPEKS: @@ -96,8 +107,8 @@ async def process_mulenpay_payment_amount( await message.answer( texts.t( "MULENPAY_PAYMENT_ERROR", - "❌ Ошибка создания платежа Mulen Pay. Попробуйте позже или обратитесь в поддержку.", - ) + "❌ Ошибка создания платежа {mulenpay_name}. Попробуйте позже или обратитесь в поддержку.", + ).format(mulenpay_name=mulenpay_name) ) await state.clear() return @@ -112,8 +123,8 @@ async def process_mulenpay_payment_amount( types.InlineKeyboardButton( text=texts.t( "MULENPAY_PAY_BUTTON", - "💳 Оплатить через Mulen Pay", - ), + "💳 Оплатить через {mulenpay_name}", + ).format(mulenpay_name=mulenpay_name), url=payment_url, ) ], @@ -132,11 +143,11 @@ async def process_mulenpay_payment_amount( message_template = texts.t( "MULENPAY_PAYMENT_INSTRUCTIONS", ( - "💳 Оплата через Mulen Pay\n\n" + "💳 Оплата через {mulenpay_name_html}\n\n" "💰 Сумма: {amount}\n" "🆔 ID платежа: {payment_id}\n\n" "📱 Инструкция:\n" - "1. Нажмите кнопку 'Оплатить через Mulen Pay'\n" + "1. Нажмите кнопку 'Оплатить через {mulenpay_name}'\n" "2. Следуйте подсказкам платежной системы\n" "3. Подтвердите перевод\n" "4. Средства зачислятся автоматически\n\n" @@ -148,6 +159,8 @@ async def process_mulenpay_payment_amount( amount=settings.format_price(amount_kopeks), payment_id=payment_id_display, support=settings.get_support_contact_display_html(), + mulenpay_name=mulenpay_name, + mulenpay_name_html=mulenpay_name_html, ) await message.answer( @@ -159,19 +172,20 @@ async def process_mulenpay_payment_amount( await state.clear() logger.info( - "Создан MulenPay платеж для пользователя %s: %s₽, ID: %s", + "Создан %s платеж для пользователя %s: %s₽, ID: %s", + mulenpay_name, db_user.telegram_id, amount_rubles, payment_id_display, ) except Exception as e: - logger.error(f"Ошибка создания MulenPay платежа: {e}") + logger.error(f"Ошибка создания {mulenpay_name} платежа: {e}") await message.answer( texts.t( "MULENPAY_PAYMENT_ERROR", - "❌ Ошибка создания платежа Mulen Pay. Попробуйте позже или обратитесь в поддержку.", - ) + "❌ Ошибка создания платежа {mulenpay_name}. Попробуйте позже или обратитесь в поддержку.", + ).format(mulenpay_name=mulenpay_name) ) await state.clear() @@ -204,8 +218,9 @@ async def check_mulenpay_payment_status( emoji, status_text = status_labels.get(payment.status, ("❓", "Неизвестно")) + mulenpay_name = settings.get_mulenpay_display_name() message_lines = [ - "💳 Статус платежа Mulen Pay:\n\n", + f"💳 Статус платежа {mulenpay_name}:\n\n", f"🆔 ID: {payment.mulen_payment_id or payment.id}\n", f"💰 Сумма: {settings.format_price(payment.amount_kopeks)}\n", f"📊 Статус: {emoji} {status_text}\n", @@ -234,5 +249,7 @@ async def check_mulenpay_payment_status( await callback.answer(message_text, show_alert=True) except Exception as e: - logger.error(f"Ошибка проверки статуса MulenPay: {e}") + logger.error( + f"Ошибка проверки статуса {settings.get_mulenpay_display_name()}: {e}" + ) await callback.answer("❌ Ошибка проверки статуса", show_alert=True) \ No newline at end of file diff --git a/app/handlers/simple_subscription.py b/app/handlers/simple_subscription.py index 8a055af0..14376000 100644 --- a/app/handlers/simple_subscription.py +++ b/app/handlers/simple_subscription.py @@ -186,8 +186,9 @@ def _get_simple_subscription_payment_keyboard(language: str) -> types.InlineKeyb )]) if settings.is_mulenpay_enabled(): + mulenpay_name = settings.get_mulenpay_display_name() keyboard.append([types.InlineKeyboardButton( - text="💳 MulenPay", + text=f"💳 {mulenpay_name}", callback_data="simple_subscription_mulenpay" )]) @@ -773,12 +774,19 @@ async def handle_simple_subscription_payment_method( elif payment_method == "mulenpay": # Оплата через MulenPay + mulenpay_name = settings.get_mulenpay_display_name() if not settings.is_mulenpay_enabled(): - await callback.answer("❌ Оплата через MulenPay временно недоступна", show_alert=True) + await callback.answer( + f"❌ Оплата через {mulenpay_name} временно недоступна", + show_alert=True, + ) return - + # Здесь должна быть реализация оплаты через MulenPay - await callback.answer("❌ Оплата через MulenPay пока не реализована", show_alert=True) + await callback.answer( + f"❌ Оплата через {mulenpay_name} пока не реализована", + show_alert=True, + ) elif payment_method == "pal24": # Оплата через PayPalych diff --git a/app/handlers/subscription/purchase.py b/app/handlers/subscription/purchase.py index cd188cd0..fa1b8bf2 100644 --- a/app/handlers/subscription/purchase.py +++ b/app/handlers/subscription/purchase.py @@ -2412,8 +2412,9 @@ def _get_simple_subscription_payment_keyboard(language: str) -> types.InlineKeyb )]) if settings.is_mulenpay_enabled(): + mulenpay_name = settings.get_mulenpay_display_name() keyboard.append([types.InlineKeyboardButton( - text="💳 MulenPay", + text=f"💳 {mulenpay_name}", callback_data="simple_subscription_mulenpay" )]) diff --git a/app/keyboards/inline.py b/app/keyboards/inline.py index bd38cabe..0ef929bd 100644 --- a/app/keyboards/inline.py +++ b/app/keyboards/inline.py @@ -1101,9 +1101,13 @@ def get_payment_methods_keyboard(amount_kopeks: int, language: str = DEFAULT_LAN ]) if settings.is_mulenpay_enabled(): + mulenpay_name = settings.get_mulenpay_display_name() keyboard.append([ InlineKeyboardButton( - text=texts.t("PAYMENT_CARD_MULENPAY", "💳 Банковская карта (Mulen Pay)"), + text=texts.t( + "PAYMENT_CARD_MULENPAY", + "💳 Банковская карта ({mulenpay_name})", + ).format(mulenpay_name=mulenpay_name), callback_data=_build_callback("mulenpay") ) ]) diff --git a/app/localization/locales/en.json b/app/localization/locales/en.json index 16eb50dc..fea1974f 100644 --- a/app/localization/locales/en.json +++ b/app/localization/locales/en.json @@ -922,10 +922,10 @@ "MINIAPP_PURCHASE_DISCOUNT_TRAFFIC": "Traffic discount: -{amount} ({percent}%)", "MINIAPP_PURCHASE_STATUS_INSUFFICIENT": "Not enough funds on balance", "MINIAPP_PURCHASE_SUMMARY_DISCOUNT": "You save {amount}", - "MULENPAY_PAYMENT_ERROR": "❌ Failed to create Mulen Pay payment. Please try again later or contact support.", - "MULENPAY_PAYMENT_INSTRUCTIONS": "💳 Mulen Pay payment\n\n💰 Amount: {amount}\n🆔 Payment ID: {payment_id}\n\n📱 How to pay:\n1. Press ‘Pay with Mulen Pay’\n2. Follow the instructions on the payment page\n3. Confirm the transfer\n4. Funds will be credited automatically\n\n❓ Need help? Contact {support}", - "MULENPAY_PAY_BUTTON": "💳 Pay with Mulen Pay", - "MULENPAY_TOPUP_PROMPT": "💳 Mulen Pay payment\n\nEnter an amount between 100 and 100,000 ₽.\nThe payment is processed by the secure Mulen Pay platform.", + "MULENPAY_PAYMENT_ERROR": "❌ Failed to create a {mulenpay_name} payment. Please try again later or contact support.", + "MULENPAY_PAYMENT_INSTRUCTIONS": "💳 {mulenpay_name_html} payment\n\n💰 Amount: {amount}\n🆔 Payment ID: {payment_id}\n\n📱 How to pay:\n1. Press ‘Pay with {mulenpay_name}’\n2. Follow the instructions on the payment page\n3. Confirm the transfer\n4. Funds will be credited automatically\n\n❓ Need help? Contact {support}", + "MULENPAY_PAY_BUTTON": "💳 Pay with {mulenpay_name}", + "MULENPAY_TOPUP_PROMPT": "💳 {mulenpay_name_html} payment\n\nEnter an amount between 100 and 100,000 ₽.\nThe payment is processed by the secure {mulenpay_name} platform.", "MY_BALANCE_BUTTON": "💰 My balance", "MY_SUBSCRIPTION_BUTTON": "📱 My subscription", "MY_TICKETS_BUTTON": "📋 My tickets", @@ -965,7 +965,7 @@ "PAL24_SELECT_PAYMENT_METHOD": "Choose a PayPalych payment method:", "PAL24_TOPUP_PROMPT": "🏦 PayPalych (SBP) payment\n\nEnter an amount between 100 and 1,000,000 ₽.\nThe payment is processed via the PayPalych Faster Payments System.", "PAYMENTS_TEMPORARILY_UNAVAILABLE": "⚠️ Payment methods are temporarily unavailable", - "PAYMENT_CARD_MULENPAY": "💳 Bank card (Mulen Pay)", + "PAYMENT_CARD_MULENPAY": "💳 Bank card ({mulenpay_name})", "PAYMENT_CARD_PAL24": "🏦 SBP (PayPalych)", "PAYMENT_CARD_TRIBUTE": "💳 Bank card (Tribute)", "PAYMENT_CARD_WATA": "💳 Bank card (WATA)", @@ -979,8 +979,8 @@ "PAYMENT_METHODS_UNAVAILABLE_ALERT": "⚠️ Automated payment methods are temporarily unavailable. Contact support to top up your balance.", "PAYMENT_METHOD_CRYPTOBOT_DESCRIPTION": "via CryptoBot", "PAYMENT_METHOD_CRYPTOBOT_NAME": "🪙 Cryptocurrency", - "PAYMENT_METHOD_MULENPAY_DESCRIPTION": "via Mulen Pay", - "PAYMENT_METHOD_MULENPAY_NAME": "💳 Bank card (Mulen Pay)", + "PAYMENT_METHOD_MULENPAY_DESCRIPTION": "via {mulenpay_name}", + "PAYMENT_METHOD_MULENPAY_NAME": "💳 Bank card ({mulenpay_name})", "PAYMENT_METHOD_PAL24_DESCRIPTION": "via Faster Payments System", "PAYMENT_METHOD_PAL24_NAME": "🏦 SBP (PayPalych)", "PAYMENT_METHOD_STARS_DESCRIPTION": "fast and convenient", diff --git a/app/localization/locales/ru.json b/app/localization/locales/ru.json index efc3367a..031867ec 100644 --- a/app/localization/locales/ru.json +++ b/app/localization/locales/ru.json @@ -922,10 +922,10 @@ "MINIAPP_PURCHASE_DISCOUNT_TRAFFIC": "Скидка на трафик: -{amount} ({percent}%)", "MINIAPP_PURCHASE_STATUS_INSUFFICIENT": "Недостаточно средств на балансе", "MINIAPP_PURCHASE_SUMMARY_DISCOUNT": "Экономия {amount}", - "MULENPAY_PAYMENT_ERROR": "❌ Ошибка создания платежа Mulen Pay. Попробуйте позже или обратитесь в поддержку.", - "MULENPAY_PAYMENT_INSTRUCTIONS": "💳 Оплата через Mulen Pay\n\n💰 Сумма: {amount}\n🆔 ID платежа: {payment_id}\n\n📱 Инструкция:\n1. Нажмите кнопку ‘Оплатить через Mulen Pay’\n2. Следуйте подсказкам платежной системы\n3. Подтвердите перевод\n4. Средства зачислятся автоматически\n\n❓ Если возникнут проблемы, обратитесь в {support}", - "MULENPAY_PAY_BUTTON": "💳 Оплатить через Mulen Pay", - "MULENPAY_TOPUP_PROMPT": "💳 Оплата через Mulen Pay\n\nВведите сумму для пополнения от 100 до 100 000 ₽.\nОплата происходит через защищенную платформу Mulen Pay.", + "MULENPAY_PAYMENT_ERROR": "❌ Ошибка создания платежа {mulenpay_name}. Попробуйте позже или обратитесь в поддержку.", + "MULENPAY_PAYMENT_INSTRUCTIONS": "💳 Оплата через {mulenpay_name_html}\n\n💰 Сумма: {amount}\n🆔 ID платежа: {payment_id}\n\n📱 Инструкция:\n1. Нажмите кнопку ‘Оплатить через {mulenpay_name}’\n2. Следуйте подсказкам платежной системы\n3. Подтвердите перевод\n4. Средства зачислятся автоматически\n\n❓ Если возникнут проблемы, обратитесь в {support}", + "MULENPAY_PAY_BUTTON": "💳 Оплатить через {mulenpay_name}", + "MULENPAY_TOPUP_PROMPT": "💳 Оплата через {mulenpay_name_html}\n\nВведите сумму для пополнения от 100 до 100 000 ₽.\nОплата происходит через защищенную платформу {mulenpay_name}.", "MY_BALANCE_BUTTON": "💰 Мой баланс", "MY_SUBSCRIPTION_BUTTON": "📱 Моя подписка", "MY_TICKETS_BUTTON": "📋 Мои тикеты", @@ -965,7 +965,7 @@ "PAL24_SELECT_PAYMENT_METHOD": "Выберите способ оплаты PayPalych:", "PAL24_TOPUP_PROMPT": "🏦 Оплата через PayPalych (СБП)\n\nВведите сумму для пополнения от 100 до 1 000 000 ₽.\nОплата проходит через систему быстрых платежей PayPalych.", "PAYMENTS_TEMPORARILY_UNAVAILABLE": "⚠️ Способы оплаты временно недоступны", - "PAYMENT_CARD_MULENPAY": "💳 Банковская карта (Mulen Pay)", + "PAYMENT_CARD_MULENPAY": "💳 Банковская карта ({mulenpay_name})", "PAYMENT_CARD_PAL24": "🏦 СБП (PayPalych)", "PAYMENT_CARD_TRIBUTE": "💳 Банковская карта (Tribute)", "PAYMENT_CARD_WATA": "💳 Банковская карта (WATA)", @@ -979,8 +979,8 @@ "PAYMENT_METHODS_UNAVAILABLE_ALERT": "⚠️ В данный момент автоматические способы оплаты временно недоступны. Для пополнения баланса обратитесь в техподдержку.", "PAYMENT_METHOD_CRYPTOBOT_DESCRIPTION": "через CryptoBot", "PAYMENT_METHOD_CRYPTOBOT_NAME": "🪙 Криптовалюта", - "PAYMENT_METHOD_MULENPAY_DESCRIPTION": "через Mulen Pay", - "PAYMENT_METHOD_MULENPAY_NAME": "💳 Банковская карта (Mulen Pay)", + "PAYMENT_METHOD_MULENPAY_DESCRIPTION": "через {mulenpay_name}", + "PAYMENT_METHOD_MULENPAY_NAME": "💳 Банковская карта ({mulenpay_name})", "PAYMENT_METHOD_PAL24_DESCRIPTION": "через систему быстрых платежей", "PAYMENT_METHOD_PAL24_NAME": "🏦 СБП (PayPalych)", "PAYMENT_METHOD_STARS_DESCRIPTION": "быстро и удобно", diff --git a/app/services/admin_notification_service.py b/app/services/admin_notification_service.py index fd3bad98..fb79756c 100644 --- a/app/services/admin_notification_service.py +++ b/app/services/admin_notification_service.py @@ -802,11 +802,12 @@ class AdminNotificationService: return self.enabled and bool(self.chat_id) def _get_payment_method_display(self, payment_method: Optional[str]) -> str: + mulenpay_name = settings.get_mulenpay_display_name() method_names = { 'telegram_stars': '⭐ Telegram Stars', 'yookassa': '💳 YooKassa (карта)', 'tribute': '💎 Tribute (карта)', - 'mulenpay': '💳 Mulen Pay (карта)', + 'mulenpay': f'💳 {mulenpay_name} (карта)', 'pal24': '🏦 PayPalych (СБП)', 'manual': '🛠️ Вручную (админ)', 'balance': '💰 С баланса' diff --git a/app/services/payment/mulenpay.py b/app/services/payment/mulenpay.py index cbe050c8..e733f657 100644 --- a/app/services/payment/mulenpay.py +++ b/app/services/payment/mulenpay.py @@ -28,13 +28,16 @@ class MulenPayPaymentMixin: language: Optional[str] = None, ) -> Optional[Dict[str, Any]]: """Создаёт локальный платеж и инициализирует сессию в MulenPay.""" + display_name = settings.get_mulenpay_display_name() + display_name_html = settings.get_mulenpay_display_name_html() if not getattr(self, "mulenpay_service", None): - logger.error("MulenPay сервис не инициализирован") + logger.error("%s сервис не инициализирован", display_name) return None if amount_kopeks < settings.MULENPAY_MIN_AMOUNT_KOPEKS: logger.warning( - "Сумма MulenPay меньше минимальной: %s < %s", + "Сумма %s меньше минимальной: %s < %s", + display_name, amount_kopeks, settings.MULENPAY_MIN_AMOUNT_KOPEKS, ) @@ -42,7 +45,8 @@ class MulenPayPaymentMixin: if amount_kopeks > settings.MULENPAY_MAX_AMOUNT_KOPEKS: logger.warning( - "Сумма MulenPay больше максимальной: %s > %s", + "Сумма %s больше максимальной: %s > %s", + display_name, amount_kopeks, settings.MULENPAY_MAX_AMOUNT_KOPEKS, ) @@ -74,7 +78,7 @@ class MulenPayPaymentMixin: ) if not response: - logger.error("Ошибка создания MulenPay платежа") + logger.error("Ошибка создания %s платежа", display_name) return None mulen_payment_id = response.get("id") @@ -100,7 +104,8 @@ class MulenPayPaymentMixin: ) logger.info( - "Создан MulenPay платеж %s на %s₽ для пользователя %s", + "Создан %s платеж %s на %s₽ для пользователя %s", + display_name, mulen_payment_id, amount_rubles, user_id, @@ -116,7 +121,7 @@ class MulenPayPaymentMixin: } except Exception as error: - logger.error("Ошибка создания MulenPay платежа: %s", error) + logger.error("Ошибка создания %s платежа: %s", display_name, error) return None async def process_mulenpay_callback( @@ -125,6 +130,7 @@ class MulenPayPaymentMixin: callback_data: Dict[str, Any], ) -> bool: """Обрабатывает callback от MulenPay, обновляет статус и начисляет баланс.""" + display_name = settings.get_mulenpay_display_name() try: payment_module = import_module("app.services.payment_service") uuid_value = callback_data.get("uuid") @@ -138,14 +144,15 @@ class MulenPayPaymentMixin: mulen_payment_id_int = None amount_value = callback_data.get("amount") logger.debug( - "MulenPay callback: uuid=%s, status=%s, amount=%s", + "%s callback: uuid=%s, status=%s, amount=%s", + display_name, uuid_value, payment_status, amount_value, ) if not uuid_value and mulen_payment_id_raw is None: - logger.error("MulenPay callback без uuid и id") + logger.error("%s callback без uuid и id", display_name) return False payment = None @@ -159,7 +166,8 @@ class MulenPayPaymentMixin: if not payment: logger.error( - "MulenPay платеж не найден (uuid=%s, id=%s)", + "%s платеж не найден (uuid=%s, id=%s)", + display_name, uuid_value, mulen_payment_id_raw, ) @@ -167,7 +175,8 @@ class MulenPayPaymentMixin: if payment.is_paid: logger.info( - "MulenPay платеж %s уже обработан, игнорируем повторный callback", + "%s платеж %s уже обработан, игнорируем повторный callback", + display_name, payment.uuid, ) return True @@ -183,7 +192,8 @@ class MulenPayPaymentMixin: if payment.transaction_id: logger.info( - "Для MulenPay платежа %s уже создана транзакция", + "Для %s платежа %s уже создана транзакция", + display_name, payment.uuid, ) return True @@ -199,7 +209,7 @@ class MulenPayPaymentMixin: user_id=payment.user_id, type=TransactionType.DEPOSIT, amount_kopeks=payment.amount_kopeks, - description=f"Пополнение через MulenPay: {payment_description}", + description=f"Пополнение через {display_name}: {payment_description}", payment_method=PaymentMethod.MULENPAY, external_id=payment.uuid, is_completed=True, @@ -214,8 +224,9 @@ class MulenPayPaymentMixin: user = await payment_module.get_user_by_id(db, payment.user_id) if not user: logger.error( - "Пользователь %s не найден при обработке MulenPay", + "Пользователь %s не найден при обработке %s", payment.user_id, + display_name, ) return False @@ -226,7 +237,7 @@ class MulenPayPaymentMixin: db, user, payment.amount_kopeks, - f"Пополнение MulenPay: {payment.amount_kopeks // 100}₽", + f"Пополнение {display_name}: {payment.amount_kopeks // 100}₽", ) if was_first_topup and not user.has_made_first_topup: @@ -261,7 +272,8 @@ class MulenPayPaymentMixin: ) except Exception as error: logger.error( - "Ошибка отправки уведомления о пополнении MulenPay: %s", + "Ошибка отправки уведомления о пополнении %s: %s", + display_name, error, ) @@ -273,7 +285,7 @@ class MulenPayPaymentMixin: ( "✅ Пополнение успешно!\n\n" f"💰 Сумма: {settings.format_price(payment.amount_kopeks)}\n" - "🦊 Способ: Mulen Pay\n" + f"🦊 Способ: {display_name_html}\n" f"🆔 Транзакция: {transaction.id}\n\n" "Баланс пополнен автоматически!" ), @@ -282,7 +294,8 @@ class MulenPayPaymentMixin: ) except Exception as error: logger.error( - "Ошибка отправки уведомления пользователю MulenPay: %s", + "Ошибка отправки уведомления пользователю %s: %s", + display_name, error, ) @@ -329,7 +342,8 @@ class MulenPayPaymentMixin: logger.error(f"Ошибка при работе с сохраненной корзиной для пользователя {user.id}: {e}", exc_info=True) logger.info( - "✅ Обработан MulenPay платеж %s для пользователя %s", + "✅ Обработан %s платеж %s для пользователя %s", + display_name, payment.uuid, payment.user_id, ) @@ -343,7 +357,7 @@ class MulenPayPaymentMixin: callback_payload=callback_data, mulen_payment_id=mulen_payment_id_int, ) - logger.info("MulenPay платеж %s отменен", payment.uuid) + logger.info("%s платеж %s отменен", display_name, payment.uuid) return True await payment_module.update_mulenpay_payment_status( @@ -354,14 +368,20 @@ class MulenPayPaymentMixin: mulen_payment_id=mulen_payment_id_int, ) logger.info( - "Получен MulenPay callback со статусом %s для платежа %s", + "Получен %s callback со статусом %s для платежа %s", + display_name, payment_status, payment.uuid, ) return True except Exception as error: - logger.error("Ошибка обработки MulenPay callback: %s", error, exc_info=True) + logger.error( + "Ошибка обработки %s callback: %s", + display_name, + error, + exc_info=True, + ) return False def _map_mulenpay_status(self, status_code: Optional[int]) -> str: @@ -383,6 +403,7 @@ class MulenPayPaymentMixin: local_payment_id: int, ) -> Optional[Dict[str, Any]]: """Возвращает текущее состояние платежа и при необходимости синхронизирует его.""" + display_name = settings.get_mulenpay_display_name() try: payment_module = import_module("app.services.payment_service") @@ -437,6 +458,9 @@ class MulenPayPaymentMixin: except Exception as error: logger.error( - "Ошибка получения статуса MulenPay: %s", error, exc_info=True + "Ошибка получения статуса %s: %s", + display_name, + error, + exc_info=True, ) return None diff --git a/app/services/payment_service.py b/app/services/payment_service.py index 235224a6..b65ca6ff 100644 --- a/app/services/payment_service.py +++ b/app/services/payment_service.py @@ -209,12 +209,13 @@ class PaymentService( ) self.wata_service = WataService() if settings.is_wata_enabled() else None + mulenpay_name = settings.get_mulenpay_display_name() logger.debug( - "PaymentService инициализирован (YooKassa=%s, Stars=%s, CryptoBot=%s, " - "MulenPay=%s, Pal24=%s, Wata=%s)", + "PaymentService инициализирован (YooKassa=%s, Stars=%s, CryptoBot=%s, %s=%s, Pal24=%s, Wata=%s)", bool(self.yookassa_service), bool(self.stars_service), bool(self.cryptobot_service), + mulenpay_name, bool(self.mulenpay_service), bool(self.pal24_service), bool(self.wata_service), diff --git a/app/services/system_settings_service.py b/app/services/system_settings_service.py index 9bceba8a..ee7d19a6 100644 --- a/app/services/system_settings_service.py +++ b/app/services/system_settings_service.py @@ -76,7 +76,7 @@ class BotConfigurationService: "CRYPTOBOT": "🪙 CryptoBot", "YOOKASSA": "🟣 YooKassa", "TRIBUTE": "🎁 Tribute", - "MULENPAY": "💰 MulenPay", + "MULENPAY": "💰 {mulenpay_name}", "PAL24": "🏦 PAL24 / PayPalych", "WATA": "💠 Wata", "EXTERNAL_ADMIN": "🛡️ Внешняя админка", @@ -124,7 +124,7 @@ class BotConfigurationService: "PAYMENT": "Общие тексты платежей, описания чеков и шаблоны.", "YOOKASSA": "Интеграция с YooKassa: идентификаторы магазина и вебхуки.", "CRYPTOBOT": "CryptoBot и криптоплатежи через Telegram.", - "MULENPAY": "Платежи MulenPay и параметры магазина.", + "MULENPAY": "Платежи {mulenpay_name} и параметры магазина.", "PAL24": "PAL24 / PayPalych подключения и лимиты.", "TRIBUTE": "Tribute и донат-сервисы.", "TELEGRAM": "Telegram Stars и их стоимость.", @@ -166,6 +166,14 @@ class BotConfigurationService: "MODERATION": "Настройки фильтров отображаемых имен и защиты от фишинга.", } + @staticmethod + def _format_dynamic_copy(category_key: Optional[str], value: str) -> str: + if not value: + return value + if category_key == "MULENPAY": + return value.format(mulenpay_name=settings.get_mulenpay_display_name()) + return value + CATEGORY_KEY_OVERRIDES: Dict[str, str] = { "DATABASE_URL": "DATABASE", "DATABASE_MODE": "DATABASE", @@ -459,7 +467,8 @@ class BotConfigurationService: @classmethod def get_category_description(cls, category_key: str) -> str: - return cls.CATEGORY_DESCRIPTIONS.get(category_key, "") + description = cls.CATEGORY_DESCRIPTIONS.get(category_key, "") + return cls._format_dynamic_copy(category_key, description) @classmethod def is_toggle(cls, key: str) -> bool: @@ -598,6 +607,7 @@ class BotConfigurationService: category_key, category_key.capitalize() if category_key else "Прочее", ) + category_label = cls._format_dynamic_copy(category_key, category_label) cls._definitions[key] = SettingDefinition( key=key, diff --git a/app/services/user_service.py b/app/services/user_service.py index a16e0b10..e52f9cca 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -672,7 +672,10 @@ class UserService: mulenpay_payments = mulenpay_result.scalars().all() if mulenpay_payments: - logger.info(f"🔄 Удаляем {len(mulenpay_payments)} MulenPay платежей") + mulenpay_name = settings.get_mulenpay_display_name() + logger.info( + f"🔄 Удаляем {len(mulenpay_payments)} {mulenpay_name} платежей" + ) await db.execute( update(MulenPayPayment) .where(MulenPayPayment.user_id == user_id) @@ -684,7 +687,9 @@ class UserService: ) await db.flush() except Exception as e: - logger.error(f"❌ Ошибка удаления MulenPay платежей: {e}") + logger.error( + f"❌ Ошибка удаления {settings.get_mulenpay_display_name()} платежей: {e}" + ) try: pal24_result = await db.execute( diff --git a/app/utils/payment_utils.py b/app/utils/payment_utils.py index 559a03fc..5668217e 100644 --- a/app/utils/payment_utils.py +++ b/app/utils/payment_utils.py @@ -46,11 +46,12 @@ def get_available_payment_methods() -> List[Dict[str, str]]: }) if settings.is_mulenpay_enabled(): + mulenpay_name = settings.get_mulenpay_display_name() methods.append({ "id": "mulenpay", "name": "Банковская карта", "icon": "💳", - "description": "через Mulen Pay", + "description": f"через {mulenpay_name}", "callback": "topup_mulenpay" }) @@ -129,6 +130,12 @@ def get_payment_methods_text(language: str) -> str: f"PAYMENT_METHOD_{method_id}_DESCRIPTION", method['description'], ) + if method_id == "MULENPAY": + mulenpay_name = settings.get_mulenpay_display_name() + mulenpay_name_html = settings.get_mulenpay_display_name_html() + name = name.format(mulenpay_name=mulenpay_name_html) + description = description.format(mulenpay_name=mulenpay_name) + text += f"{name} - {description}\n" text += "\n" + texts.t( diff --git a/app/webapi/routes/miniapp.py b/app/webapi/routes/miniapp.py index 85da7f3c..07c9fe51 100644 --- a/app/webapi/routes/miniapp.py +++ b/app/webapi/routes/miniapp.py @@ -668,6 +668,7 @@ async def get_payment_methods( methods.append( MiniAppPaymentMethod( id="mulenpay", + name=settings.get_mulenpay_display_name(), icon="💳", requires_amount=True, currency="RUB", diff --git a/app/webapi/schemas/miniapp.py b/app/webapi/schemas/miniapp.py index b7eca0a5..f1ce9f22 100644 --- a/app/webapi/schemas/miniapp.py +++ b/app/webapi/schemas/miniapp.py @@ -361,6 +361,7 @@ class MiniAppPaymentMethodsRequest(BaseModel): class MiniAppPaymentMethod(BaseModel): id: str + name: Optional[str] = None icon: Optional[str] = None requires_amount: bool = False currency: str = "RUB" diff --git a/main.py b/main.py index 5b718766..bdcdab14 100644 --- a/main.py +++ b/main.py @@ -248,7 +248,7 @@ async def main(): if settings.TRIBUTE_ENABLED: enabled_services.append("Tribute") if settings.is_mulenpay_enabled(): - enabled_services.append("Mulen Pay") + enabled_services.append(settings.get_mulenpay_display_name()) if settings.is_cryptobot_enabled(): enabled_services.append("CryptoBot") @@ -257,7 +257,11 @@ async def main(): stage.log(f"Активированы: {', '.join(enabled_services)}") stage.success("Webhook сервера запущены") else: - stage.skip("Tribute, Mulen Pay и CryptoBot отключены") + stage.skip( + "Tribute, " + f"{settings.get_mulenpay_display_name()}" + " и CryptoBot отключены" + ) async with timeline.stage( "YooKassa webhook", @@ -372,7 +376,8 @@ async def main(): ) if settings.is_mulenpay_enabled(): webhook_lines.append( - f"Mulen Pay: {settings.WEBHOOK_URL}:{settings.TRIBUTE_WEBHOOK_PORT}{settings.MULENPAY_WEBHOOK_PATH}" + f"{settings.get_mulenpay_display_name()}: " + f"{settings.WEBHOOK_URL}:{settings.TRIBUTE_WEBHOOK_PORT}{settings.MULENPAY_WEBHOOK_PATH}" ) if settings.is_cryptobot_enabled(): webhook_lines.append(