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(