Merge pull request #100 from zerodata731/freekassa-codex

Freekassa codex
This commit is contained in:
machka pasla
2025-10-14 09:38:47 +03:00
committed by GitHub
13 changed files with 145 additions and 194 deletions
+7 -8
View File
@@ -24,6 +24,13 @@ DISABLE_WELCOME_MESSAGE= #
# Webhook Base URL (used for Telegram and payment providers) # Webhook Base URL (used for Telegram and payment providers)
WEBHOOK_BASE_URL=https://webhooks.yourdomain.tld WEBHOOK_BASE_URL=https://webhooks.yourdomain.tld
# Payment Method Toggles
YOOKASSA_ENABLED=True # Turn on YOOKASSA
FREEKASSA_ENABLED=True # Turn on FreeKassa
STARS_ENABLED=True # Turn on STARS
TRIBUTE_ENABLED=True # Turn on TRIBUTE
CRYPTOPAY_ENABLED=True # Turn on CRYPTOPAY
# YooKassa Payment Gateway Configuration # YooKassa Payment Gateway Configuration
YOOKASSA_SHOP_ID=your_shop_id # Your store ID in YooKassa YOOKASSA_SHOP_ID=your_shop_id # Your store ID in YooKassa
YOOKASSA_SECRET_KEY=your_secret_key # Your secret key for YooKassa YOOKASSA_SECRET_KEY=your_secret_key # Your secret key for YooKassa
@@ -33,11 +40,9 @@ YOOKASSA_VAT_CODE=1 #
YOOKASSA_AUTOPAYMENTS_ENABLED=False # Auto-renew toggle YOOKASSA_AUTOPAYMENTS_ENABLED=False # Auto-renew toggle
# FreeKassa Payment Gateway Configuration # FreeKassa Payment Gateway Configuration
FREEKASSA_ENABLED=True # Turn on FreeKassa
FREEKASSA_MERCHANT_ID=your_shop_id # Your shop ID in FreeKassa FREEKASSA_MERCHANT_ID=your_shop_id # Your shop ID in FreeKassa
FREEKASSA_API_KEY=your_api_key # API key for REST requests FREEKASSA_API_KEY=your_api_key # API key for REST requests
FREEKASSA_SECOND_SECRET=your_second_secret # Secret word #2 (used to verify notifications) FREEKASSA_SECOND_SECRET=your_second_secret # Secret word #2 (used to verify notifications)
FREEKASSA_CURRENCY=RUB # Default currency for orders
FREEKASSA_PAYMENT_IP= # Public IP address reported to FreeKassa FREEKASSA_PAYMENT_IP= # Public IP address reported to FreeKassa
# CryptoBot Payment Gateway Configuration # CryptoBot Payment Gateway Configuration
@@ -51,12 +56,6 @@ TRIBUTE_API_KEY= #
TRIBUTE_SKIP_NOTIFICATIONS=True # Skip renewal notifications for Tribute payments TRIBUTE_SKIP_NOTIFICATIONS=True # Skip renewal notifications for Tribute payments
TRIBUTE_SKIP_CANCELLATION_NOTIFICATIONS=False # Skip cancellation notifications for Tribute payments TRIBUTE_SKIP_CANCELLATION_NOTIFICATIONS=False # Skip cancellation notifications for Tribute payments
# Payment Method Toggles
YOOKASSA_ENABLED=True # Turn on YOOKASSA
STARS_ENABLED=True # Turn on STARS
TRIBUTE_ENABLED=True # Turn on TRIBUTE
CRYPTOPAY_ENABLED=True # Turn on CRYPTOPAY
# Subscription Options. Specify cost parameters or payment links here. # Subscription Options. Specify cost parameters or payment links here.
1_MONTH_ENABLED=True 1_MONTH_ENABLED=True
RUB_PRICE_1_MONTH=150 RUB_PRICE_1_MONTH=150
-1
View File
@@ -86,7 +86,6 @@
| `FREEKASSA_API_KEY` | API-ключ для запросов к FreeKassa REST API. | | `FREEKASSA_API_KEY` | API-ключ для запросов к FreeKassa REST API. |
| `FREEKASSA_SECOND_SECRET` | Секретное слово №2 — используется для проверки уведомлений от FreeKassa. | | `FREEKASSA_SECOND_SECRET` | Секретное слово №2 — используется для проверки уведомлений от FreeKassa. |
| `FREEKASSA_PAYMENT_URL` | (Опционально, legacy SCI) Базовый URL платёжной формы FreeKassa. По умолчанию `https://pay.freekassa.ru/`. | | `FREEKASSA_PAYMENT_URL` | (Опционально, legacy SCI) Базовый URL платёжной формы FreeKassa. По умолчанию `https://pay.freekassa.ru/`. |
| `FREEKASSA_CURRENCY` | Код валюты платежа (например, `RUB`). |
| `FREEKASSA_PAYMENT_IP` | Внешний IP вашего сервера, который будет передаваться в запрос оплаты. | | `FREEKASSA_PAYMENT_IP` | Внешний IP вашего сервера, который будет передаваться в запрос оплаты. |
| `STARS_ENABLED` | Включить/выключить Telegram Stars (`true`/`false`). | | `STARS_ENABLED` | Включить/выключить Telegram Stars (`true`/`false`). |
| `TRIBUTE_ENABLED`| Включить/выключить Tribute (`true`/`false`). | | `TRIBUTE_ENABLED`| Включить/выключить Tribute (`true`/`false`). |
+1 -1
View File
@@ -280,7 +280,7 @@ async def process_successful_payment(session: AsyncSession, bot: Bot,
details_message = _("payment_successful_error_details") details_message = _("payment_successful_error_details")
details_markup = get_connect_and_main_keyboard( details_markup = get_connect_and_main_keyboard(
user_lang, i18n, settings, config_link user_lang, i18n, settings, config_link, preserve_message=True
) )
try: try:
await bot.send_message( await bot.send_message(
+7
View File
@@ -427,6 +427,13 @@ async def main_action_callback_handler(
subscription_service, subscription_service,
session, session,
is_edit=True) is_edit=True)
elif action == "back_to_main_keep":
await send_main_menu(callback,
settings,
i18n_data,
subscription_service,
session,
is_edit=False)
else: else:
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
_ = lambda key, **kwargs: i18n.gettext( _ = lambda key, **kwargs: i18n.gettext(
+64 -130
View File
@@ -1,6 +1,7 @@
import logging import logging
from datetime import datetime
from aiogram import Router, F, types from aiogram import Router, F, types
from aiogram.utils.keyboard import InlineKeyboardBuilder from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from typing import Optional from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@@ -239,7 +240,13 @@ async def pay_yk_callback_handler(callback: types.CallbackQuery, settings: Setti
await callback.message.edit_text( await callback.message.edit_text(
get_text(key="payment_link_message", months=months), get_text(key="payment_link_message", months=months),
reply_markup=get_payment_url_keyboard(payment_response_yk["confirmation_url"], current_lang, i18n), reply_markup=get_payment_url_keyboard(
payment_response_yk["confirmation_url"],
current_lang,
i18n,
back_callback=f"subscribe_period:{months}",
back_text_key="back_to_payment_methods_button",
),
disable_web_page_preview=False, disable_web_page_preview=False,
) )
else: else:
@@ -340,139 +347,24 @@ async def pay_fk_callback_handler(
pass pass
return return
method_keyboard = InlineKeyboardBuilder()
method_keyboard.button(
text=get_text("freekassa_method_qr"),
callback_data=f"pay_fk_method:{payment_record.payment_id}:44",
)
method_keyboard.button(
text=get_text("freekassa_method_card"),
callback_data=f"pay_fk_method:{payment_record.payment_id}:36",
)
method_keyboard.button(
text=get_text("freekassa_method_sberpay"),
callback_data=f"pay_fk_method:{payment_record.payment_id}:43",
)
method_keyboard.button(
text=get_text("back_to_main_menu_button"),
callback_data="main_action:subscribe",
)
method_keyboard.adjust(1)
try:
await callback.message.edit_text(
get_text("freekassa_choose_method"),
reply_markup=method_keyboard.as_markup(),
)
except Exception as e_edit:
logging.warning(f"FreeKassa: failed to show method selector ({e_edit}), sending new message.")
try:
await callback.message.answer(
get_text("freekassa_choose_method"),
reply_markup=method_keyboard.as_markup(),
)
except Exception:
pass
try:
await callback.answer()
except Exception:
pass
@router.callback_query(F.data.startswith("pay_fk_method:"))
async def pay_fk_method_callback_handler(
callback: types.CallbackQuery,
settings: Settings,
i18n_data: dict,
freekassa_service: FreeKassaService,
session: AsyncSession,
):
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
get_text = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) if i18n else key
if not i18n or not callback.message:
try:
await callback.answer(get_text("error_occurred_try_again"), show_alert=True)
except Exception:
pass
return
if not freekassa_service or not freekassa_service.configured:
try:
await callback.answer(get_text("payment_service_unavailable_alert"), show_alert=True)
except Exception:
pass
try:
await callback.message.edit_text(get_text("payment_service_unavailable"))
except Exception:
pass
return
try:
_, payload = callback.data.split(":", 1)
payment_id_str, method_code = payload.split(":")
payment_id = int(payment_id_str)
except (ValueError, IndexError):
logging.error(f"FreeKassa: invalid method payload {callback.data}")
try:
await callback.answer(get_text("error_try_again"), show_alert=True)
except Exception:
pass
return
try:
payment_record = await payment_dal.get_payment_by_db_id(session, payment_id)
except Exception as e_db:
logging.error(f"FreeKassa: failed to load payment {payment_id}: {e_db}")
payment_record = None
if not payment_record:
try:
await callback.answer(get_text("error_payment_gateway"), show_alert=True)
except Exception:
pass
return
if payment_record.user_id != callback.from_user.id:
logging.warning(
f"FreeKassa: user {callback.from_user.id} attempted to access payment {payment_id} owned by {payment_record.user_id}"
)
try:
await callback.answer(get_text("error_payment_gateway"), show_alert=True)
except Exception:
pass
return
months = payment_record.subscription_duration_months or 1
amount = float(payment_record.amount)
try:
method_code_int = int(method_code)
except (TypeError, ValueError):
logging.error(f"FreeKassa: invalid method code {method_code} for payment {payment_record.payment_id}")
try:
await callback.answer(get_text("error_payment_gateway"), show_alert=True)
except Exception:
pass
return
success, response_data = await freekassa_service.create_order( success, response_data = await freekassa_service.create_order(
payment_db_id=payment_record.payment_id, payment_db_id=payment_record.payment_id,
user_id=payment_record.user_id, user_id=payment_record.user_id,
months=months, months=months,
amount=amount, amount=price_rub,
currency=settings.DEFAULT_CURRENCY_SYMBOL or "RUB", currency=freekassa_service.default_currency,
method_code=method_code_int, method_code=44,
ip_address=freekassa_service.server_ip, ip_address=freekassa_service.server_ip,
extra_params={ extra_params={
"us_method": method_code_int, "us_method": 44,
}, },
) )
if success: if success:
location = response_data.get("location") location = response_data.get("location")
provider_identifier = response_data.get("orderHash") or response_data.get("orderId") order_hash = response_data.get("orderHash")
order_id_api = response_data.get("orderId")
provider_identifier = order_hash or order_id_api
if provider_identifier: if provider_identifier:
try: try:
@@ -491,18 +383,36 @@ async def pay_fk_method_callback_handler(
) )
if location: if location:
order_identifier_display = str(order_id_api or provider_identifier or payment_record.payment_id)
order_info_text = get_text(
"free_kassa_order_info",
order_id=order_identifier_display,
date=datetime.now().strftime("%Y-%m-%d"),
)
try: try:
await callback.message.edit_text( await callback.message.edit_text(
get_text(key="payment_link_message", months=months), f"{order_info_text}\n\n" + get_text(key="payment_link_message", months=months),
reply_markup=get_payment_url_keyboard(location, current_lang, i18n), reply_markup=get_payment_url_keyboard(
location,
current_lang,
i18n,
back_callback=f"subscribe_period:{months}",
back_text_key="back_to_payment_methods_button",
),
disable_web_page_preview=False, disable_web_page_preview=False,
) )
except Exception as e_edit: except Exception as e_edit:
logging.warning(f"FreeKassa: failed to display payment link ({e_edit}), sending new message.") logging.warning(f"FreeKassa: failed to display payment link ({e_edit}), sending new message.")
try: try:
await callback.message.answer( await callback.message.answer(
get_text(key="payment_link_message", months=months), f"{order_info_text}\n\n" + get_text(key="payment_link_message", months=months),
reply_markup=get_payment_url_keyboard(location, current_lang, i18n), reply_markup=get_payment_url_keyboard(
location,
current_lang,
i18n,
back_callback=f"subscribe_period:{months}",
back_text_key="back_to_payment_methods_button",
),
disable_web_page_preview=False, disable_web_page_preview=False,
) )
except Exception: except Exception:
@@ -599,14 +509,26 @@ async def pay_crypto_callback_handler(
try: try:
await callback.message.edit_text( await callback.message.edit_text(
get_text(key="payment_link_message", months=months), get_text(key="payment_link_message", months=months),
reply_markup=get_payment_url_keyboard(invoice_url, current_lang, i18n), reply_markup=get_payment_url_keyboard(
invoice_url,
current_lang,
i18n,
back_callback=f"subscribe_period:{months}",
back_text_key="back_to_payment_methods_button",
),
disable_web_page_preview=False, disable_web_page_preview=False,
) )
except Exception: except Exception:
try: try:
await callback.message.answer( await callback.message.answer(
get_text(key="payment_link_message", months=months), get_text(key="payment_link_message", months=months),
reply_markup=get_payment_url_keyboard(invoice_url, current_lang, i18n), reply_markup=get_payment_url_keyboard(
invoice_url,
current_lang,
i18n,
back_callback=f"subscribe_period:{months}",
back_text_key="back_to_payment_methods_button",
),
disable_web_page_preview=False, disable_web_page_preview=False,
) )
except Exception: except Exception:
@@ -673,6 +595,18 @@ async def pay_stars_callback_handler(
) )
if payment_db_id: if payment_db_id:
try:
await callback.message.edit_text(
get_text("payment_invoice_sent_message", months=months),
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(
text=get_text("back_to_payment_methods_button"),
callback_data=f"subscribe_period:{months}",
)]
]),
)
except Exception as e_edit:
logging.warning(f"Stars payment: failed to show invoice info message ({e_edit})")
try: try:
await callback.answer() await callback.answer()
except Exception: except Exception:
+23 -14
View File
@@ -118,17 +118,17 @@ def get_payment_method_keyboard(months: int, price: float,
i18n_instance, settings: Settings) -> InlineKeyboardMarkup: i18n_instance, settings: Settings) -> InlineKeyboardMarkup:
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs) _ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
if settings.STARS_ENABLED and stars_price is not None: if settings.FREEKASSA_ENABLED:
builder.button(text=_("pay_with_stars_button"), builder.button(text=_("pay_with_sbp_button"),
callback_data=f"pay_stars:{months}:{stars_price}") callback_data=f"pay_fk:{months}:{price}")
if settings.TRIBUTE_ENABLED and tribute_url:
builder.button(text=_("pay_with_tribute_button"), url=tribute_url)
if settings.YOOKASSA_ENABLED: if settings.YOOKASSA_ENABLED:
builder.button(text=_("pay_with_yookassa_button"), builder.button(text=_("pay_with_yookassa_button"),
callback_data=f"pay_yk:{months}:{price}") callback_data=f"pay_yk:{months}:{price}")
if settings.FREEKASSA_ENABLED: if settings.TRIBUTE_ENABLED and tribute_url:
builder.button(text=_("pay_with_freekassa_button"), builder.button(text=_("pay_with_tribute_button"), url=tribute_url)
callback_data=f"pay_fk:{months}:{price}") if settings.STARS_ENABLED and stars_price is not None:
builder.button(text=_("pay_with_stars_button"),
callback_data=f"pay_stars:{months}:{stars_price}")
if settings.CRYPTOPAY_ENABLED: if settings.CRYPTOPAY_ENABLED:
builder.button(text=_("pay_with_cryptopay_button"), builder.button(text=_("pay_with_cryptopay_button"),
callback_data=f"pay_crypto:{months}:{price}") callback_data=f"pay_crypto:{months}:{price}")
@@ -138,13 +138,20 @@ def get_payment_method_keyboard(months: int, price: float,
return builder.as_markup() return builder.as_markup()
def get_payment_url_keyboard(payment_url: str, lang: str, def get_payment_url_keyboard(payment_url: str,
i18n_instance) -> InlineKeyboardMarkup: lang: str,
i18n_instance,
back_callback: Optional[str] = None,
back_text_key: str = "back_to_main_menu_button"
) -> InlineKeyboardMarkup:
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs) _ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
builder.button(text=_(key="pay_button"), url=payment_url) builder.button(text=_(key="pay_button"), url=payment_url)
builder.button(text=_(key="back_to_main_menu_button"), if back_callback:
callback_data="main_action:back_to_main") builder.button(text=_(key=back_text_key), callback_data=back_callback)
else:
builder.button(text=_(key="back_to_main_menu_button"),
callback_data="main_action:back_to_main")
builder.adjust(1) builder.adjust(1)
return builder.as_markup() return builder.as_markup()
@@ -192,7 +199,8 @@ def get_connect_and_main_keyboard(
lang: str, lang: str,
i18n_instance, i18n_instance,
settings: Settings, settings: Settings,
config_link: Optional[str]) -> InlineKeyboardMarkup: config_link: Optional[str],
preserve_message: bool = False) -> InlineKeyboardMarkup:
"""Keyboard with a connect button and a back to main menu button.""" """Keyboard with a connect button and a back to main menu button."""
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs) _ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
@@ -216,10 +224,11 @@ def get_connect_and_main_keyboard(
) )
) )
back_callback = "main_action:back_to_main_keep" if preserve_message else "main_action:back_to_main"
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text=_("back_to_main_menu_button"), text=_("back_to_main_menu_button"),
callback_data="main_action:back_to_main", callback_data=back_callback,
) )
) )
+3 -1
View File
@@ -209,7 +209,9 @@ class CryptoPayService:
end_date=final_end.strftime('%Y-%m-%d'), end_date=final_end.strftime('%Y-%m-%d'),
config_link=config_link) config_link=config_link)
markup = get_connect_and_main_keyboard(lang, i18n, settings, config_link) markup = get_connect_and_main_keyboard(
lang, i18n, settings, config_link, preserve_message=True
)
try: try:
await bot.send_message( await bot.send_message(
user_id, user_id,
+16 -4
View File
@@ -1,4 +1,5 @@
import asyncio import asyncio
from datetime import datetime
import hashlib import hashlib
import hmac import hmac
import json import json
@@ -42,9 +43,7 @@ class FreeKassaService:
self.shop_id: Optional[str] = settings.FREEKASSA_MERCHANT_ID self.shop_id: Optional[str] = settings.FREEKASSA_MERCHANT_ID
self.api_key: Optional[str] = settings.FREEKASSA_API_KEY self.api_key: Optional[str] = settings.FREEKASSA_API_KEY
self.second_secret: Optional[str] = settings.FREEKASSA_SECOND_SECRET self.second_secret: Optional[str] = settings.FREEKASSA_SECOND_SECRET
self.default_currency: str = ( self.default_currency: str = (settings.DEFAULT_CURRENCY_SYMBOL or "RUB").upper()
settings.FREEKASSA_CURRENCY or settings.DEFAULT_CURRENCY_SYMBOL or "RUB"
).upper()
self.server_ip: Optional[str] = settings.FREEKASSA_PAYMENT_IP self.server_ip: Optional[str] = settings.FREEKASSA_PAYMENT_IP
self.api_base_url: str = "https://api.fk.life/v1" self.api_base_url: str = "https://api.fk.life/v1"
@@ -360,8 +359,21 @@ class FreeKassaService:
end_date=end_date_str, end_date=end_date_str,
config_link=config_link, config_link=config_link,
) )
if provider_payment_id:
order_info_text = _(
"free_kassa_order_full",
order_id=provider_payment_id,
date=datetime.now().strftime("%Y-%m-%d"),
)
text = f"{order_info_text}\n{text}"
markup = get_connect_and_main_keyboard(lang, self.i18n, self.settings, config_link) markup = get_connect_and_main_keyboard(
lang,
self.i18n,
self.settings,
config_link,
preserve_message=True,
)
try: try:
await self.bot.send_message( await self.bot.send_message(
payment.user_id, payment.user_id,
+1 -1
View File
@@ -148,7 +148,7 @@ class StarsService:
config_link=config_link, config_link=config_link,
) )
markup = get_connect_and_main_keyboard( markup = get_connect_and_main_keyboard(
current_lang, i18n, self.settings, config_link current_lang, i18n, self.settings, config_link, preserve_message=True
) )
try: try:
await self.bot.send_message( await self.bot.send_message(
+5 -1
View File
@@ -208,7 +208,11 @@ class TributeService:
config_link=config_link, config_link=config_link,
) )
markup = get_connect_and_main_keyboard( markup = get_connect_and_main_keyboard(
lang, i18n, settings, config_link lang,
i18n,
settings,
config_link,
preserve_message=True,
) )
try: try:
+4 -1
View File
@@ -49,7 +49,6 @@ class Settings(BaseSettings):
FREEKASSA_FIRST_SECRET: Optional[str] = None FREEKASSA_FIRST_SECRET: Optional[str] = None
FREEKASSA_SECOND_SECRET: Optional[str] = None FREEKASSA_SECOND_SECRET: Optional[str] = None
FREEKASSA_PAYMENT_URL: str = Field(default="https://pay.freekassa.ru/") FREEKASSA_PAYMENT_URL: str = Field(default="https://pay.freekassa.ru/")
FREEKASSA_CURRENCY: str = Field(default="RUB")
FREEKASSA_API_KEY: Optional[str] = None FREEKASSA_API_KEY: Optional[str] = None
FREEKASSA_PAYMENT_IP: Optional[str] = None FREEKASSA_PAYMENT_IP: Optional[str] = None
@@ -400,6 +399,10 @@ def get_settings() -> Settings:
logging.warning( logging.warning(
"WARNING: FreeKassa second secret is not set. Incoming payment notifications cannot be verified." "WARNING: FreeKassa second secret is not set. Incoming payment notifications cannot be verified."
) )
if not _settings_instance.subscription_options:
logging.warning(
"CRITICAL: FreeKassa is enabled but no subscription prices are configured (RUB_PRICE_*). Users will not see payment buttons."
)
except ValidationError as e: except ValidationError as e:
logging.critical( logging.critical(
+7 -16
View File
@@ -1,7 +1,6 @@
{ {
"welcome": "Welcome, {user_name}!", "welcome": "Welcome, {user_name}!",
"main_menu_greeting": "Hi, {user_name}! 👋\nWhat would you like to do?", "main_menu_greeting": "Hi, {user_name}! 👋\nWhat would you like to do?",
"menu_activate_trial_button": "🆓 Free Trial", "menu_activate_trial_button": "🆓 Free Trial",
"menu_subscribe_inline": "🚀 Purchase", "menu_subscribe_inline": "🚀 Purchase",
"menu_my_subscription_inline": "🔐 My Subscription", "menu_my_subscription_inline": "🔐 My Subscription",
@@ -13,42 +12,35 @@
"menu_server_status_button": "📊 Status", "menu_server_status_button": "📊 Status",
"menu_support_button": "💬 Support", "menu_support_button": "💬 Support",
"menu_terms_button": "📄 Terms of Service", "menu_terms_button": "📄 Terms of Service",
"back_to_main_menu_button": "⬅️ Back", "back_to_main_menu_button": "⬅️ Back",
"choose_language": "Choose language:", "choose_language": "Choose language:",
"language_set_alert": "Language changed!", "language_set_alert": "Language changed!",
"error_occurred_try_again": "An error occurred, please try again.", "error_occurred_try_again": "An error occurred, please try again.",
"error_try_again": "Please try again.", "error_try_again": "Please try again.",
"error_displaying_menu": "Error displaying menu.", "error_displaying_menu": "Error displaying menu.",
"main_menu_unknown_action": "Unknown action.", "main_menu_unknown_action": "Unknown action.",
"select_subscription_period": "Select subscription period:", "select_subscription_period": "Select subscription period:",
"subscribe_for_months_button": "{months} mo. - {price} {currency_symbol}", "subscribe_for_months_button": "{months} mo. - {price} {currency_symbol}",
"choose_payment_method": "Choose payment method:", "choose_payment_method": "Choose payment method:",
"pay_button": "💳 Pay", "pay_button": "💳 Pay",
"pay_with_yookassa_button": "💳 YooKassa", "pay_with_yookassa_button": "💳 YooKassa",
"pay_with_freekassa_button": "💳 FreeKassa", "pay_with_sbp_button": "📱 SBP",
"back_to_payment_methods_button": "⬅️ Back to payment methods",
"pay_with_cryptopay_button": "💎 CryptoBot", "pay_with_cryptopay_button": "💎 CryptoBot",
"pay_with_tribute_button": "❤️ Tribute", "pay_with_tribute_button": "❤️ Tribute",
"pay_with_stars_button": "🌟 Telegram Stars", "pay_with_stars_button": "🌟 Telegram Stars",
"freekassa_choose_method": "Choose how to pay with FreeKassa:",
"freekassa_method_qr": "📱 SBP QR (i=44)",
"freekassa_method_card": "💳 Bank Card (i=36)",
"freekassa_method_sberpay": "🏦 SberPay (i=43)",
"connect_button": "🔗 Connect", "connect_button": "🔗 Connect",
"cancel_button": "❌ Cancel", "cancel_button": "❌ Cancel",
"payment_description_subscription": "Subscription payment for {months} mo.", "payment_description_subscription": "Subscription payment for {months} mo.",
"payment_link_message": "To pay for {months} mo. subscription, click the button below:", "payment_link_message": "To pay for {months} mo. subscription, click the button below:",
"free_kassa_order_info": "Order #{order_id} from {date}",
"payment_invoice_sent_message": "Telegram has sent the invoice above. Complete the payment or pick another method below.",
"payment_successful_error_details": "✅ Payment succeeded, but an error occurred displaying details. Your subscription is active. Contact support if anything is wrong.", "payment_successful_error_details": "✅ Payment succeeded, but an error occurred displaying details. Your subscription is active. Contact support if anything is wrong.",
"payment_successful_full": "✅ Payment successful!\nYour {months}-month subscription is active until {end_date}.\n\nConnection key:\n<code>{config_link}</code>\n\nTo connect, open the link and follow the instructions 👇", "payment_successful_full": "✅ Payment successful!\nYour {months}-month subscription is active until {end_date}.\n\nConnection key:\n<code>{config_link}</code>\n\nTo connect, open the link and follow the instructions 👇",
"payment_successful_with_referral_bonus_full": "✅ Payment successful!\nYour {months}-month subscription (base end date: {base_end_date}) has been extended by {bonus_days} bonus days for referral from {inviter_name} and is now active until {final_end_date}.\n\nConnection key:\n<code>{config_link}</code>\n\nTo connect, open the link and follow the instructions 👇", "payment_successful_with_referral_bonus_full": "✅ Payment successful!\nYour {months}-month subscription (base end date: {base_end_date}) has been extended by {bonus_days} bonus days for referral from {inviter_name} and is now active until {final_end_date}.\n\nConnection key:\n<code>{config_link}</code>\n\nTo connect, open the link and follow the instructions 👇",
"payment_failed": "❌ Payment failed or was cancelled. Please try again or contact support.", "payment_failed": "❌ Payment failed or was cancelled. Please try again or contact support.",
"config_link_not_available": "not available, contact support", "config_link_not_available": "not available, contact support",
"traffic_unlimited": "Unlimited", "traffic_unlimited": "Unlimited",
"promo_code_prompt": "Please enter your promo code:", "promo_code_prompt": "Please enter your promo code:",
"promo_code_not_found": "Promo code <code>{code}</code> not found, expired, or already used the maximum number of times.", "promo_code_not_found": "Promo code <code>{code}</code> not found, expired, or already used the maximum number of times.",
"promo_code_already_used_by_user": "You have already used promo code <code>{code}</code>.", "promo_code_already_used_by_user": "You have already used promo code <code>{code}</code>.",
@@ -70,7 +62,6 @@
"referral_bonus_inviter_notification_extended": "🎉 Congrats! Your friend {referee_name} paid for a subscription. You received {days} bonus days! Your subscription is now active until {new_end_date}.", "referral_bonus_inviter_notification_extended": "🎉 Congrats! Your friend {referee_name} paid for a subscription. You received {days} bonus days! Your subscription is now active until {new_end_date}.",
"referral_bonus_inviter_notification_new_sub": "🎉 Congrats! Your friend {referee_name} paid for a subscription. You received a {days}-day bonus subscription! It is active until {new_end_date}.", "referral_bonus_inviter_notification_new_sub": "🎉 Congrats! Your friend {referee_name} paid for a subscription. You received a {days}-day bonus subscription! It is active until {new_end_date}.",
"user_is_banned": "🚫 Your account is banned. Please contact support.", "user_is_banned": "🚫 Your account is banned. Please contact support.",
"admin_panel_title": "Admin Panel", "admin_panel_title": "Admin Panel",
"admin_stats_button": "📊 Statistics", "admin_stats_button": "📊 Statistics",
"admin_broadcast_button": "📢 Broadcast", "admin_broadcast_button": "📢 Broadcast",
@@ -327,7 +318,6 @@
"admin_user_subscription_active_until": "⏰ <b>Active until:</b>", "admin_user_subscription_active_until": "⏰ <b>Active until:</b>",
"admin_user_subscription_error": "Loading error", "admin_user_subscription_error": "Loading error",
"admin_promo_management_button": "🎟 Promo Management", "admin_promo_management_button": "🎟 Promo Management",
"admin_promo_management_title": "🎟 <b>Promo Code Management</b>\n\nSelect a promo code for detailed view:", "admin_promo_management_title": "🎟 <b>Promo Code Management</b>\n\nSelect a promo code for detailed view:",
"admin_promo_management_empty": "📭 No promo codes available", "admin_promo_management_empty": "📭 No promo codes available",
"admin_promo_card_title": "🎟 <b>Promo Code: {code}</b>", "admin_promo_card_title": "🎟 <b>Promo Code: {code}</b>",
@@ -440,5 +430,6 @@
"admin_ads_delete_button": "🗑 Delete campaign", "admin_ads_delete_button": "🗑 Delete campaign",
"admin_ads_delete_confirm": "Are you sure you want to delete campaign #{id}? This action is irreversible.", "admin_ads_delete_confirm": "Are you sure you want to delete campaign #{id}? This action is irreversible.",
"admin_ads_deleted_success": "Campaign deleted.", "admin_ads_deleted_success": "Campaign deleted.",
"admin_ads_not_found": "Campaign not found." "admin_ads_not_found": "Campaign not found.",
} "free_kassa_order_full": "Order #{order_id} from {date}\n\n"
}
+7 -16
View File
@@ -1,7 +1,6 @@
{ {
"welcome": "Добро пожаловать, {user_name}!", "welcome": "Добро пожаловать, {user_name}!",
"main_menu_greeting": "Привет, {user_name}! 👋\nЧто бы вы хотели сделать?", "main_menu_greeting": "Привет, {user_name}! 👋\nЧто бы вы хотели сделать?",
"menu_activate_trial_button": "🆓 Пробный период", "menu_activate_trial_button": "🆓 Пробный период",
"menu_subscribe_inline": "🚀 Купить", "menu_subscribe_inline": "🚀 Купить",
"menu_my_subscription_inline": "🔐 Моя подписка", "menu_my_subscription_inline": "🔐 Моя подписка",
@@ -13,42 +12,35 @@
"menu_server_status_button": "📊 Статус", "menu_server_status_button": "📊 Статус",
"menu_support_button": "💬 Поддержка", "menu_support_button": "💬 Поддержка",
"menu_terms_button": "📄 Условия сервиса", "menu_terms_button": "📄 Условия сервиса",
"back_to_main_menu_button": "⬅️ Назад", "back_to_main_menu_button": "⬅️ Назад",
"choose_language": "Выберите язык / Select language:", "choose_language": "Выберите язык / Select language:",
"language_set_alert": "Язык изменен!", "language_set_alert": "Язык изменен!",
"error_occurred_try_again": "Произошла ошибка, попробуйте снова.", "error_occurred_try_again": "Произошла ошибка, попробуйте снова.",
"error_try_again": "Попробуйте еще раз.", "error_try_again": "Попробуйте еще раз.",
"error_displaying_menu": "Ошибка отображения меню.", "error_displaying_menu": "Ошибка отображения меню.",
"main_menu_unknown_action": "Неизвестное действие.", "main_menu_unknown_action": "Неизвестное действие.",
"select_subscription_period": "Выберите срок подписки:", "select_subscription_period": "Выберите срок подписки:",
"subscribe_for_months_button": "{months} мес. - {price} {currency_symbol}", "subscribe_for_months_button": "{months} мес. - {price} {currency_symbol}",
"choose_payment_method": "Выберите способ оплаты:", "choose_payment_method": "Выберите способ оплаты:",
"pay_button": "💳 Оплатить", "pay_button": "💳 Оплатить",
"pay_with_yookassa_button": "💳 ЮKassa", "pay_with_yookassa_button": "💳 ЮKassa",
"pay_with_freekassa_button": "💳 FreeKassa", "pay_with_sbp_button": "📱 СБП",
"back_to_payment_methods_button": "⬅️ Назад к выбору оплаты",
"pay_with_cryptopay_button": "💎 CryptoBot", "pay_with_cryptopay_button": "💎 CryptoBot",
"pay_with_tribute_button": "❤️ Tribute", "pay_with_tribute_button": "❤️ Tribute",
"pay_with_stars_button": "🌟 Звезды Telegram", "pay_with_stars_button": "🌟 Звезды Telegram",
"freekassa_choose_method": "Выберите способ оплаты FreeKassa:",
"freekassa_method_qr": "📱 QR по СБП (i=44)",
"freekassa_method_card": "💳 Банковская карта РФ (i=36)",
"freekassa_method_sberpay": "🏦 SberPay (i=43)",
"connect_button": "🔗 Подключиться", "connect_button": "🔗 Подключиться",
"cancel_button": "❌ Отмена", "cancel_button": "❌ Отмена",
"payment_description_subscription": "Оплата подписки на {months} мес.", "payment_description_subscription": "Оплата подписки на {months} мес.",
"payment_link_message": "Для оплаты подписки на {months} мес., нажмите кнопку ниже:", "payment_link_message": "Для оплаты подписки на {months} мес., нажмите кнопку ниже:",
"free_kassa_order_info": "Заказ №{order_id} от {date}",
"payment_invoice_sent_message": "Счёт Telegram Stars отправлен выше. Нажмите «Оплатить» или вернитесь к выбору способа ниже.",
"payment_successful_error_details": "✅ Оплата прошла успешно, но возникла ошибка при отображении деталей. Ваша подписка активна. Свяжитесь с поддержкой, если что-то не так.", "payment_successful_error_details": "✅ Оплата прошла успешно, но возникла ошибка при отображении деталей. Ваша подписка активна. Свяжитесь с поддержкой, если что-то не так.",
"payment_successful_full": "✅ Оплата прошла успешно!\nВаша подписка на {months} мес. активна до {end_date}.\n\nКлюч подключения:\n<code>{config_link}</code>\n\nЧтобы подключиться, перейдите по ссылке и следуйте инструкции 👇", "payment_successful_full": "✅ Оплата прошла успешно!\nВаша подписка на {months} мес. активна до {end_date}.\n\nКлюч подключения:\n<code>{config_link}</code>\n\nЧтобы подключиться, перейдите по ссылке и следуйте инструкции 👇",
"payment_successful_with_referral_bonus_full": "✅ Оплата прошла успешно!\nВаша подписка на {months} мес. (базовая дата окончания: {base_end_date}) продлена на {bonus_days} бонусных дней за приглашение от {inviter_name} и теперь активна до {final_end_date}.\n\nКлюч подключения:\n<code>{config_link}</code>\n\nЧтобы подключиться, перейдите по ссылке и следуйте инструкции 👇", "payment_successful_with_referral_bonus_full": "✅ Оплата прошла успешно!\nВаша подписка на {months} мес. (базовая дата окончания: {base_end_date}) продлена на {bonus_days} бонусных дней за приглашение от {inviter_name} и теперь активна до {final_end_date}.\n\nКлюч подключения:\n<code>{config_link}</code>\n\nЧтобы подключиться, перейдите по ссылке и следуйте инструкции 👇",
"payment_failed": "❌ Оплата не удалась или была отменена. Пожалуйста, попробуйте еще раз или свяжитесь с поддержкой.", "payment_failed": "❌ Оплата не удалась или была отменена. Пожалуйста, попробуйте еще раз или свяжитесь с поддержкой.",
"config_link_not_available": "недоступна, обратитесь в поддержку", "config_link_not_available": "недоступна, обратитесь в поддержку",
"traffic_unlimited": "Безлимитный", "traffic_unlimited": "Безлимитный",
"promo_code_prompt": "Пожалуйста, введите ваш промокод:", "promo_code_prompt": "Пожалуйста, введите ваш промокод:",
"promo_code_not_found": "Промокод <code>{code}</code> не найден, истек или уже использован максимальное количество раз.", "promo_code_not_found": "Промокод <code>{code}</code> не найден, истек или уже использован максимальное количество раз.",
"promo_code_already_used_by_user": "Вы уже активировали промокод <code>{code}</code>.", "promo_code_already_used_by_user": "Вы уже активировали промокод <code>{code}</code>.",
@@ -70,7 +62,6 @@
"referral_bonus_inviter_notification_extended": "🎉 Поздравляем! Ваш друг {referee_name} оплатил подписку. Вам начислено {days} бонусных дней! Ваша подписка теперь активна до {new_end_date}.", "referral_bonus_inviter_notification_extended": "🎉 Поздравляем! Ваш друг {referee_name} оплатил подписку. Вам начислено {days} бонусных дней! Ваша подписка теперь активна до {new_end_date}.",
"referral_bonus_inviter_notification_new_sub": "🎉 Поздравляем! Ваш друг {referee_name} оплатил подписку. Вам начислена бонусная подписка на {days} дней! Она активна до {new_end_date}.", "referral_bonus_inviter_notification_new_sub": "🎉 Поздравляем! Ваш друг {referee_name} оплатил подписку. Вам начислена бонусная подписка на {days} дней! Она активна до {new_end_date}.",
"user_is_banned": "🚫 Ваш аккаунт заблокирован. Пожалуйста, свяжитесь со службой поддержки.", "user_is_banned": "🚫 Ваш аккаунт заблокирован. Пожалуйста, свяжитесь со службой поддержки.",
"admin_panel_title": "Панель администратора", "admin_panel_title": "Панель администратора",
"admin_stats_button": "📊 Статистика", "admin_stats_button": "📊 Статистика",
"admin_broadcast_button": "📢 Рассылка", "admin_broadcast_button": "📢 Рассылка",
@@ -327,7 +318,6 @@
"admin_user_subscription_active_until": "⏰ <b>Действует до:</b>", "admin_user_subscription_active_until": "⏰ <b>Действует до:</b>",
"admin_user_subscription_error": "Ошибка загрузки", "admin_user_subscription_error": "Ошибка загрузки",
"admin_promo_management_button": "🎟 Управление промокодами", "admin_promo_management_button": "🎟 Управление промокодами",
"admin_promo_management_title": "🎟 <b>Управление промокодами</b>\n\nВыберите промокод для детального просмотра:", "admin_promo_management_title": "🎟 <b>Управление промокодами</b>\n\nВыберите промокод для детального просмотра:",
"admin_promo_management_empty": "📭 Промокоды отсутствуют", "admin_promo_management_empty": "📭 Промокоды отсутствуют",
"admin_promo_card_title": "🎟 <b>Промокод: {code}</b>", "admin_promo_card_title": "🎟 <b>Промокод: {code}</b>",
@@ -440,5 +430,6 @@
"admin_ads_delete_button": "🗑 Удалить кампанию", "admin_ads_delete_button": "🗑 Удалить кампанию",
"admin_ads_delete_confirm": "Вы уверены, что хотите удалить кампанию #{id}? Это действие необратимо.", "admin_ads_delete_confirm": "Вы уверены, что хотите удалить кампанию #{id}? Это действие необратимо.",
"admin_ads_deleted_success": "Кампания удалена.", "admin_ads_deleted_success": "Кампания удалена.",
"admin_ads_not_found": "Кампания не найдена." "admin_ads_not_found": "Кампания не найдена.",
} "free_kassa_order_full": "Заказ №{order_id} от {date}\n\n"
}