country_mode in database/ payment options

This commit is contained in:
Vladless
2025-12-10 12:44:27 +03:00
parent 1b1780df55
commit 877c005a6f
10 changed files with 196 additions and 91 deletions
+16
View File
@@ -11,6 +11,22 @@ from ..defaults import DEFAULT_MONEY_CONFIG
MONEY_CONFIG: dict[str, Any] = DEFAULT_MONEY_CONFIG.copy()
def get_currency_mode() -> tuple[str, bool]:
mode_cfg = MONEY_CONFIG.get("CURRENCY_MODE", "RUB")
raw = str(mode_cfg or "RUB").upper()
if raw not in ("RUB", "USD", "RUB+USD", "RUB+USD_ONE_SCREEN"):
raw = "RUB"
one_screen = raw == "RUB+USD_ONE_SCREEN"
if raw in ("RUB+USD", "RUB+USD_ONE_SCREEN"):
base_mode = "RUB+USD"
else:
base_mode = raw
return base_mode, one_screen
async def load_money_config(session: AsyncSession) -> None:
stmt = select(Setting).where(Setting.key == "MONEY_CONFIG")
result = await session.execute(stmt)
+4 -1
View File
@@ -10,6 +10,7 @@ from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from config import USE_COUNTRY_SELECTION
from core.bootstrap import MODES_CONFIG
from database.models import Key, Server, User
@@ -17,7 +18,9 @@ async def import_keys_from_3xui_db(db_path: str, session: AsyncSession) -> tuple
imported = 0
skipped = 0
if USE_COUNTRY_SELECTION:
use_country_selection = bool(MODES_CONFIG.get("COUNTRY_SELECTION_ENABLED", USE_COUNTRY_SELECTION))
if use_country_selection:
result = await session.execute(
select(Server.server_name).where(Server.enabled.is_(True), Server.panel_type == "3x-ui")
)
+10 -5
View File
@@ -16,6 +16,7 @@ from config import (
REMNAWAVE_PASSWORD,
USE_COUNTRY_SELECTION,
)
from core.bootstrap import MODES_CONFIG
from database import get_servers
from database.models import Key, Server, Tariff
from filters.admin import IsAdminFilter
@@ -205,7 +206,9 @@ async def handle_sync_server(
)
return
if USE_COUNTRY_SELECTION:
use_country_selection = bool(MODES_CONFIG.get("COUNTRY_SELECTION_ENABLED", USE_COUNTRY_SELECTION))
if use_country_selection:
stmt = (
select(
Server.api_url,
@@ -402,7 +405,9 @@ async def handle_sync_cluster(
servers = await get_servers(session)
cluster_servers = servers.get(cluster_name, [])
if USE_COUNTRY_SELECTION:
use_country_selection = bool(MODES_CONFIG.get("COUNTRY_SELECTION_ENABLED", USE_COUNTRY_SELECTION))
if use_country_selection:
server_names = [s.get("server_name") for s in cluster_servers if s.get("server_name")]
if not server_names:
await callback_query.message.edit_text(
@@ -474,7 +479,7 @@ async def handle_sync_cluster(
f"обновим без лимитов"
)
if USE_COUNTRY_SELECTION:
if use_country_selection:
user_server = None
for s in cluster_servers:
if s.get("server_name") == key["server_id"]:
@@ -573,7 +578,7 @@ async def handle_sync_cluster(
delete(Key).where(Key.tg_id == key["tg_id"], Key.client_id == key["client_id"])
)
cluster_id_for_recreate = key["server_id"] if USE_COUNTRY_SELECTION else cluster_name
cluster_id_for_recreate = key["server_id"] if use_country_selection else cluster_name
await create_key_on_cluster(
cluster_id_for_recreate,
key["tg_id"],
@@ -594,7 +599,7 @@ async def handle_sync_cluster(
delete(Key).where(Key.tg_id == key["tg_id"], Key.client_id == key["client_id"])
)
cluster_id_for_recreate = key["server_id"] if USE_COUNTRY_SELECTION else cluster_name
cluster_id_for_recreate = key["server_id"] if use_country_selection else cluster_name
await create_key_on_cluster(
cluster_id_for_recreate,
key["tg_id"],
+4 -1
View File
@@ -5,6 +5,7 @@ from sqlalchemy import delete, update
from sqlalchemy.ext.asyncio import AsyncSession
from config import USE_COUNTRY_SELECTION
from core.bootstrap import MODES_CONFIG
from database.models import Key, Server
from logger import logger
@@ -33,9 +34,11 @@ async def handle_server_transfer(callback_query: CallbackQuery, state: FSMContex
await session.commit()
use_country_selection = bool(MODES_CONFIG.get("COUNTRY_SELECTION_ENABLED", USE_COUNTRY_SELECTION))
base_text = f"✅ Ключи успешно перенесены на сервер '{new_server_name}', сервер '{old_server_name}' удален!"
sync_reminder = '\n\n⚠️ Не забудьте сделать "Синхронизацию".'
final_text = base_text + (sync_reminder if USE_COUNTRY_SELECTION else "")
final_text = base_text + (sync_reminder if use_country_selection else "")
await callback_query.message.edit_text(
text=final_text,
+8 -4
View File
@@ -2,6 +2,7 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from ..panel.keyboard import AdminPanelCallback, build_admin_back_btn
from core.settings.money_config import get_currency_mode
from .settings_config import (
BUTTON_TITLES,
MODES_TITLES,
@@ -192,12 +193,14 @@ def build_settings_money_kb(money_state: dict[str, object]) -> InlineKeyboardMar
).pack(),
)
mode = str(money_state.get("CURRENCY_MODE") or "RUB").upper()
if mode not in ("RUB", "USD", "RUB+USD"):
mode = "RUB"
mode, one_screen = get_currency_mode()
if mode == "RUB+USD" and one_screen:
mode_text = "RUB+USD (одним экраном)"
else:
mode_text = mode
builder.button(
text=f"Режим валют: {mode}",
text=f"Режим валют: {mode_text}",
callback_data=AdminPanelCallback(
action="settings_money_currency",
page=0,
@@ -214,3 +217,4 @@ def build_settings_money_kb(money_state: dict[str, object]) -> InlineKeyboardMar
)
return builder.as_markup()
+14 -4
View File
@@ -137,19 +137,21 @@ async def open_currency_mode_menu(callback: CallbackQuery) -> None:
money_config = MONEY_CONFIG
mode = str(money_config.get("CURRENCY_MODE") or "RUB").upper()
if mode not in ("RUB", "USD", "RUB+USD"):
if mode not in ("RUB", "USD", "RUB+USD", "RUB+USD_ONE_SCREEN"):
mode = "RUB"
text = (
"Выберите режим валют:\n\n"
"• RUB — все цены только в рублях;\n"
"• USD — все цены только в долларах;\n"
"• RUB+USD — мультивалюта, оба варианта."
"• RUB+USD — мультивалюта, выбор валюты перед оплатой;\n"
"• RUB+USD (одним экраном) — мультивалюта, все кассы на одном экране."
)
rub_checked = mode == "RUB"
usd_checked = mode == "USD"
multi_checked = mode == "RUB+USD"
multi_one_screen_checked = mode == "RUB+USD_ONE_SCREEN"
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
@@ -169,6 +171,12 @@ async def open_currency_mode_menu(callback: CallbackQuery) -> None:
callback_data=AdminPanelCallback(action="settings_money_currency_set", page=3).pack(),
),
],
[
InlineKeyboardButton(
text=("✅ RUB+USD (одним экраном)" if multi_one_screen_checked else "RUB+USD (одним экраном)"),
callback_data=AdminPanelCallback(action="settings_money_currency_set", page=4).pack(),
),
],
[
InlineKeyboardButton(
text="Назад",
@@ -190,7 +198,7 @@ async def set_currency_mode(
) -> None:
mode_index = callback_data.page
if mode_index not in (1, 2, 3):
if mode_index not in (1, 2, 3, 4):
await callback.answer("Некорректный выбор", show_alert=True)
return
@@ -198,8 +206,10 @@ async def set_currency_mode(
new_mode = "RUB"
elif mode_index == 2:
new_mode = "USD"
else:
elif mode_index == 3:
new_mode = "RUB+USD"
else:
new_mode = "RUB+USD_ONE_SCREEN"
money_config = MONEY_CONFIG.copy()
money_config["CURRENCY_MODE"] = new_mode
+10 -3
View File
@@ -15,6 +15,7 @@ from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
from config import USE_COUNTRY_SELECTION
from core.bootstrap import MODES_CONFIG
from database import (
delete_key,
delete_user_data,
@@ -533,7 +534,9 @@ async def confirm_admin_key_reissue(callback_query: CallbackQuery, session: Asyn
)
return
if USE_COUNTRY_SELECTION:
use_country_selection = bool(MODES_CONFIG.get("COUNTRY_SELECTION_ENABLED", USE_COUNTRY_SELECTION))
if use_country_selection:
unique_countries = {srv["server_name"] for srv in cluster_servers}
await state.update_data(tg_id=tg_id, email=email, cluster_id=cluster_id)
builder = InlineKeyboardBuilder()
@@ -809,7 +812,9 @@ async def handle_create_key_start(
tg_id = callback_data.tg_id
await state.update_data(tg_id=tg_id)
if USE_COUNTRY_SELECTION:
use_country_selection = bool(MODES_CONFIG.get("COUNTRY_SELECTION_ENABLED", USE_COUNTRY_SELECTION))
if use_country_selection:
await state.set_state(UserEditorState.selecting_country)
stmt = select(Server.server_name).distinct().order_by(Server.server_name)
@@ -935,6 +940,8 @@ async def handle_create_key_duration(callback_query: CallbackQuery, state: FSMCo
data = await state.get_data()
tg_id = data.get("tg_id", callback_query.from_user.id)
use_country_selection = bool(MODES_CONFIG.get("COUNTRY_SELECTION_ENABLED", USE_COUNTRY_SELECTION))
try:
if not callback_query.data.startswith("tariff_"):
raise ValueError("Некорректный callback_data")
@@ -950,7 +957,7 @@ async def handle_create_key_duration(callback_query: CallbackQuery, state: FSMCo
expiry = datetime.now(tz=timezone.utc) + timedelta(days=duration_days)
expiry_ms = int(expiry.timestamp() * 1000)
if USE_COUNTRY_SELECTION and "country" in data:
if use_country_selection and "country" in data:
country = data["country"]
await create_key_on_cluster(
country,
+4 -1
View File
@@ -19,6 +19,7 @@ from config import (
USERNAME_BOT,
USE_COUNTRY_SELECTION,
)
from core.bootstrap import MODES_CONFIG
from database import get_key_details, get_servers
from database.models import Server
from handlers.texts import HAPP_ANNOUNCE, HIDDIFY_PROFILE_TITLE, SUBSCRIPTION_INFO_TEXT, V2RAYTUN_ANNOUNCE
@@ -73,7 +74,9 @@ async def get_subscription_urls(
server_id: str, email: str, session: AsyncSession, include_remnawave_key: str = None
) -> list[str]:
urls = []
if USE_COUNTRY_SELECTION:
use_country_selection = bool(MODES_CONFIG.get("COUNTRY_SELECTION_ENABLED", USE_COUNTRY_SELECTION))
if use_country_selection:
result = await session.execute(
select(Server.subscription_url).where(Server.server_name == server_id, Server.enabled.is_(True))
)
+99 -55
View File
@@ -5,8 +5,10 @@ from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from config import USE_NEW_PAYMENT_FLOW
from core.bootstrap import PAYMENTS_CONFIG, MONEY_CONFIG
from config import USE_NEW_PAYMENT_FLOW, TRIBUTE_LINK
from core.bootstrap import PAYMENTS_CONFIG
from core.settings.money_config import get_currency_mode
from database.temporary_data import create_temporary_data
from handlers import buttons as btn
from handlers.payments.currency_flow import (
@@ -28,14 +30,6 @@ async def get_payment_providers_config() -> dict[str, bool]:
return dict(config)
def get_currency_mode() -> str:
mode_cfg = MONEY_CONFIG.get("CURRENCY_MODE", "RUB")
mode = str(mode_cfg or "RUB").upper()
if mode not in ("RUB", "USD", "RUB+USD"):
mode = "RUB"
return mode
async def _run_provider_flow(
provider: str,
callback_query: CallbackQuery,
@@ -97,46 +91,57 @@ async def try_fast_payment_flow(
payment_config = await get_payment_providers_config()
providers_map = await get_providers_with_hooks(payment_config)
providers = (
[USE_NEW_PAYMENT_FLOW]
if isinstance(USE_NEW_PAYMENT_FLOW, str)
else [str(provider) for provider in (USE_NEW_PAYMENT_FLOW or [])]
)
providers = [
provider
for provider in providers
if (providers_map.get(str(provider).upper()) or {}).get("fast")
and (providers_map.get(str(provider).upper()) or {}).get("enabled", True)
]
configured = [str(p) for p in (USE_NEW_PAYMENT_FLOW or [])]
configured_upper = [p.upper() for p in configured]
configured_set = set(configured_upper)
mode = get_currency_mode()
multicurrency_enabled = mode == "RUB+USD"
providers: list[str] = []
for p_up in configured_upper:
cfg = providers_map.get(p_up) or {}
if cfg.get("fast") and cfg.get("enabled", True):
providers.append(p_up)
if not multicurrency_enabled:
mode, one_screen = get_currency_mode()
multicurrency_mode = mode == "RUB+USD"
if not multicurrency_mode:
allowed_currency = "RUB" if mode == "RUB" else "USD"
providers = [
provider
for provider in providers
if (providers_map.get(provider.upper()) or {}).get("currency") in (allowed_currency, "RUB+USD")
]
filtered: list[str] = []
for p_up in providers:
cfg = providers_map.get(p_up) or {}
curr = str(cfg.get("currency") or "").upper()
if curr in (allowed_currency, "RUB+USD"):
filtered.append(p_up)
providers = filtered
if not providers:
if not providers and "TRIBUTE" not in configured_set:
return False
if len(providers) == 1 and not multicurrency_enabled:
single_provider = providers[0].upper()
cfg = providers_map.get(single_provider) or {}
currency = cfg.get("currency")
if currency:
await state.update_data(chosen_currency=currency)
if await _run_provider_flow(single_provider, callback_query, session, state, required_amount):
return True
return False
tribute_cfg = providers_map.get("TRIBUTE") or {}
tribute_link = (TRIBUTE_LINK or "").strip()
tribute_enabled = (
"TRIBUTE" in configured_set
and tribute_cfg.get("enabled", True)
and bool(tribute_link)
)
if multicurrency_enabled:
show_stars = bool((providers_map.get("STARS") or {}).get("enabled"))
show_tribute = bool((providers_map.get("TRIBUTE") or {}).get("enabled"))
keyboard = build_currency_choice_kb(show_stars=show_stars, show_tribute=show_tribute)
stars_cfg = providers_map.get("STARS") or {}
stars_enabled_for_fast = (
"STARS" in configured_set
and stars_cfg.get("fast")
and stars_cfg.get("enabled", True)
)
if multicurrency_mode and not one_screen:
show_stars = stars_enabled_for_fast
show_tribute = tribute_enabled
if not providers and not show_tribute:
return False
keyboard = build_currency_choice_kb(
show_stars=show_stars,
show_tribute=show_tribute,
)
lead_text = await shortfall_lead_text(
session,
tg_id,
@@ -157,11 +162,39 @@ async def try_fast_payment_flow(
)
return True
total_options = len(providers) + (1 if tribute_enabled else 0)
if len(providers) == 1 and total_options == 1:
single_provider = providers[0]
cfg = providers_map.get(single_provider) or {}
currency = cfg.get("currency")
if currency:
await state.update_data(chosen_currency=currency)
if await _run_provider_flow(single_provider, callback_query, session, state, required_amount):
return True
return False
keyboard = InlineKeyboardBuilder()
for provider in providers:
provider_upper = provider.upper()
for provider_upper in providers:
button_text = getattr(btn, provider_upper, provider_upper)
keyboard.row(InlineKeyboardButton(text=button_text, callback_data=f"choose_payment_provider|{provider_upper}"))
if one_screen:
cfg = providers_map.get(provider_upper) or {}
curr = cfg.get("currency")
if curr and curr != "RUB+USD":
button_text = f"{button_text} ({currency_label(curr)})"
keyboard.row(
InlineKeyboardButton(
text=button_text,
callback_data=f"choose_payment_provider|{provider_upper}",
)
)
if tribute_enabled:
keyboard.row(
InlineKeyboardButton(
text=getattr(btn, "TRIBUTE", "TRIBUTE"),
url=tribute_link,
)
)
keyboard.row(InlineKeyboardButton(text=btn.MAIN_MENU, callback_data="profile"))
@@ -171,7 +204,11 @@ async def try_fast_payment_flow(
required_amount,
getattr(callback_query.from_user, "language_code", None),
)
await state.update_data(temp_key=temp_key, temp_payload=temp_payload, required_amount=required_amount)
await state.update_data(
temp_key=temp_key,
temp_payload=temp_payload,
required_amount=required_amount,
)
await edit_or_send_message(
target_message=callback_query.message,
text=f"{lead_text}.\n\n{FAST_PAY_CHOOSE_PROVIDER}",
@@ -188,14 +225,14 @@ async def choose_payment_currency(callback_query: CallbackQuery, state: FSMConte
currency = callback_query.data.split("|")[1]
data = await state.get_data()
providers = data.get("fastflow_providers") or (
[USE_NEW_PAYMENT_FLOW]
if isinstance(USE_NEW_PAYMENT_FLOW, str)
else [str(provider) for provider in (USE_NEW_PAYMENT_FLOW or [])]
)
providers = data.get("fastflow_providers") or []
if not providers:
configured = [str(p) for p in (USE_NEW_PAYMENT_FLOW or [])]
providers = [p.upper() for p in configured]
filtered = [
provider_upper
for provider_upper in (provider.upper() for provider in providers)
for provider_upper in (p.upper() for p in providers)
if (providers_map.get(provider_upper) or {}).get("currency") == currency
and (providers_map.get(provider_upper) or {}).get("fast")
and (providers_map.get(provider_upper) or {}).get("enabled", True)
@@ -228,7 +265,10 @@ async def choose_payment_currency(callback_query: CallbackQuery, state: FSMConte
for provider_upper in filtered:
button_text = getattr(btn, provider_upper, provider_upper)
keyboard.row(
InlineKeyboardButton(text=button_text, callback_data=f"choose_payment_provider|{provider_upper}")
InlineKeyboardButton(
text=button_text,
callback_data=f"choose_payment_provider|{provider_upper}",
)
)
keyboard.row(InlineKeyboardButton(text=btn.MAIN_MENU, callback_data="profile"))
@@ -241,7 +281,11 @@ async def choose_payment_currency(callback_query: CallbackQuery, state: FSMConte
force_currency=currency,
)
text = f"{lead_text}.\n\nВалюта: {currency_label(currency)}\n{FAST_PAY_CHOOSE_PROVIDER}"
await edit_or_send_message(target_message=callback_query.message, text=text, reply_markup=keyboard.as_markup())
await edit_or_send_message(
target_message=callback_query.message,
text=text,
reply_markup=keyboard.as_markup(),
)
@router.callback_query(F.data.startswith("choose_payment_provider|"))
+27 -17
View File
@@ -8,8 +8,9 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from config import DONATIONS_ENABLE
from core.bootstrap import PAYMENTS_CONFIG, MONEY_CONFIG, BUTTONS_CONFIG
from config import DONATIONS_ENABLE, TRIBUTE_LINK
from core.bootstrap import PAYMENTS_CONFIG, BUTTONS_CONFIG
from core.settings.money_config import get_currency_mode
from database import get_last_payments
from database.models import User
from handlers import buttons as btn
@@ -33,22 +34,18 @@ async def get_payment_providers_config() -> dict[str, bool]:
return dict(config)
def get_currency_mode() -> str:
mode_cfg = MONEY_CONFIG.get("CURRENCY_MODE", "RUB")
mode = str(mode_cfg or "RUB").upper()
if mode not in ("RUB", "USD", "RUB+USD"):
mode = "RUB"
return mode
@router.callback_query(F.data == "pay")
async def handle_pay(callback_query: CallbackQuery, state: FSMContext, session: AsyncSession):
providers_config = await get_payment_providers_config()
providers_with_hooks = await get_providers_with_hooks(providers_config)
mode = get_currency_mode()
mode, one_screen = get_currency_mode()
multicurrency_enabled = mode == "RUB+USD"
tribute_cfg = providers_with_hooks.get("TRIBUTE") or {}
tribute_link = (TRIBUTE_LINK or "").strip()
tribute_enabled = bool(tribute_cfg.get("enabled") and tribute_link)
if not multicurrency_enabled:
allowed_currency = "RUB" if mode == "RUB" else "USD"
filtered: dict[str, dict[str, Any]] = {}
@@ -78,14 +75,15 @@ async def handle_pay(callback_query: CallbackQuery, state: FSMContext, session:
)
donations_enabled = bool(BUTTONS_CONFIG.get("DONATIONS_BUTTON_ENABLE", DONATIONS_ENABLE))
has_extra_menu_items = bool(module_buttons) or donations_enabled or bool(
(providers_with_hooks.get("TRIBUTE") or {}).get("enabled")
)
has_extra_menu_items = bool(module_buttons) or donations_enabled or tribute_enabled
if multicurrency_enabled:
if multicurrency_enabled and not one_screen:
show_stars = bool((providers_with_hooks.get("STARS") or {}).get("enabled"))
show_tribute = bool((providers_with_hooks.get("TRIBUTE") or {}).get("enabled"))
keyboard = build_currency_choice_kb(show_stars=show_stars, prefix="pay_currency", show_tribute=show_tribute)
keyboard = build_currency_choice_kb(
show_stars=show_stars,
prefix="pay_currency",
show_tribute=tribute_enabled,
)
await edit_or_send_message(
target_message=callback_query.message,
text=FAST_PAY_CHOOSE_CURRENCY,
@@ -102,9 +100,19 @@ async def handle_pay(callback_query: CallbackQuery, state: FSMContext, session:
for key, cfg in providers_with_hooks.items():
if not cfg.get("enabled"):
continue
if key == "TRIBUTE":
continue
text = getattr(btn, key, key)
builder.row(InlineKeyboardButton(text=text, callback_data=cfg["value"]))
if tribute_enabled:
builder.row(
InlineKeyboardButton(
text=getattr(btn, "TRIBUTE", "TRIBUTE"),
url=tribute_link,
)
)
if donations_enabled:
builder.row(InlineKeyboardButton(text=btn.DONAT_BUTTON, callback_data="donate"))
@@ -124,6 +132,8 @@ async def _build_pay_menu_for_currency(currency: str) -> InlineKeyboardBuilder:
builder = InlineKeyboardBuilder()
for key, cfg in providers_with_hooks.items():
if key == "TRIBUTE":
continue
if not cfg.get("enabled"):
continue
if cfg.get("currency") != currency: