diff --git a/backend/bot/app/web/admin_settings_manifest.py b/backend/bot/app/web/admin_settings_manifest.py index e856440..6f8eb7c 100644 --- a/backend/bot/app/web/admin_settings_manifest.py +++ b/backend/bot/app/web/admin_settings_manifest.py @@ -249,35 +249,6 @@ SETTINGS_MANIFEST: List[SettingField] = [ subsection="YooKassa", ), *_payment_presentation_fields("YOOKASSA", "YooKassa", default_icon="CreditCard"), - # Platega - SettingField("PLATEGA_ENABLED", "bool", "payments", "Включена", subsection="Platega"), - SettingField( - "PLATEGA_BASE_URL", - "url", - "payments", - "Base URL", - placeholder="https://app.platega.io", - subsection="Platega", - ), - SettingField("PLATEGA_MERCHANT_ID", "string", "payments", "Merchant ID", subsection="Platega"), - SettingField( - "PLATEGA_SECRET", "string", "payments", "Secret", subsection="Platega", secret=True - ), - SettingField( - "PLATEGA_PAYMENT_METHOD", "int", "payments", "Метод оплаты (legacy)", subsection="Platega" - ), - SettingField("PLATEGA_SBP_ENABLED", "bool", "payments", "SBP-кнопка", subsection="Platega"), - SettingField("PLATEGA_SBP_METHOD", "int", "payments", "SBP method ID", subsection="Platega"), - SettingField( - "PLATEGA_CRYPTO_ENABLED", "bool", "payments", "Crypto-кнопка", subsection="Platega" - ), - SettingField( - "PLATEGA_CRYPTO_METHOD", "int", "payments", "Crypto method ID", subsection="Platega" - ), - SettingField("PLATEGA_RETURN_URL", "url", "payments", "Return URL", subsection="Platega"), - SettingField("PLATEGA_FAILED_URL", "url", "payments", "Failed URL", subsection="Platega"), - *_payment_presentation_fields("PLATEGA_SBP", "Platega SBP", default_icon="CreditCard"), - *_payment_presentation_fields("PLATEGA_CRYPTO", "Platega Crypto", default_icon="Bitcoin"), # ─── Trial ───────────────────────────────────────────────────── SettingField("TRIAL_ENABLED", "bool", "trial", "Триал включён"), SettingField("TRIAL_DURATION_DAYS", "int", "trial", "Длительность триала (дней)", min=0), diff --git a/backend/bot/keyboards/inline/user_keyboards.py b/backend/bot/keyboards/inline/user_keyboards.py index a3eb142..b53ec32 100644 --- a/backend/bot/keyboards/inline/user_keyboards.py +++ b/backend/bot/keyboards/inline/user_keyboards.py @@ -452,11 +452,8 @@ def get_payment_method_keyboard( import logging as _kbd_logging _kbd_logging.info( - "payment_method_keyboard build: order=%s | platega_enabled=%s sbp=%s crypto=%s", + "payment_method_keyboard build: order=%s", settings.payment_methods_order, - settings.PLATEGA_ENABLED, - settings.PLATEGA_SBP_ENABLED, - settings.PLATEGA_CRYPTO_ENABLED, ) from bot.payment_providers import get_provider_spec, provider_telegram_button_text diff --git a/backend/bot/payment_providers/__init__.py b/backend/bot/payment_providers/__init__.py index 8a5a3ad..c8d6265 100644 --- a/backend/bot/payment_providers/__init__.py +++ b/backend/bot/payment_providers/__init__.py @@ -15,6 +15,7 @@ from .registry import ( find_manifest_owner, get_provider_bundle, get_provider_spec, + get_spec_presentation, iter_provider_manifest_fields, iter_provider_specs, iter_service_keys, @@ -41,6 +42,7 @@ __all__ = [ "find_manifest_owner", "get_provider_bundle", "get_provider_spec", + "get_spec_presentation", "iter_provider_manifest_fields", "iter_provider_specs", "iter_service_keys", diff --git a/backend/bot/payment_providers/platega.py b/backend/bot/payment_providers/platega.py index 10d8a20..f84a4cf 100644 --- a/backend/bot/payment_providers/platega.py +++ b/backend/bot/payment_providers/platega.py @@ -5,6 +5,8 @@ from typing import Any, Dict, Optional, Tuple from aiogram import Bot, F, Router, types from aiohttp import web +from pydantic import Field, field_validator +from pydantic_settings import SettingsConfigDict from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import sessionmaker @@ -16,8 +18,11 @@ from db.dal import payment_dal from .base import ( PaymentProviderSpec, + ProviderEnvConfig, + ProviderManifestField, ServiceFactoryContext, WebAppPaymentContext, + provider_env_file, ) from .shared import ( HttpClientMixin, @@ -47,12 +52,84 @@ from .shared import ( _LOG = "platega" +class PlategaConfig(ProviderEnvConfig): + model_config = SettingsConfigDict( + env_file=provider_env_file(), + env_file_encoding="utf-8", + env_prefix="PLATEGA_", + extra="ignore", + ) + + ENABLED: bool = Field(default=False) + BASE_URL: str = Field(default="https://app.platega.io") + MERCHANT_ID: Optional[str] = None + SECRET: Optional[str] = None + PAYMENT_METHOD: int = Field(default=2) + SBP_ENABLED: bool = Field(default=False) + CRYPTO_ENABLED: bool = Field(default=False) + SBP_METHOD: int = Field(default=2) + CRYPTO_METHOD: int = Field(default=13) + RETURN_URL: Optional[str] = None + FAILED_URL: Optional[str] = None + + @field_validator("MERCHANT_ID", "SECRET", "RETURN_URL", "FAILED_URL", mode="before") + @classmethod + def _strip_optional(cls, v): + if isinstance(v, str) and not v.strip(): + return None + return v + + @property + def sbp_method_resolved(self) -> int: + """Falls back to the legacy ``PAYMENT_METHOD`` for backwards compat.""" + if self.SBP_METHOD != 2: + return self.SBP_METHOD + return self.PAYMENT_METHOD or 2 + + @property + def webhook_path(self) -> str: + return "/webhook/platega" + + +class PlategaSbpPresentation(ProviderEnvConfig): + model_config = SettingsConfigDict( + env_file=provider_env_file(), + env_file_encoding="utf-8", + env_prefix="PAYMENT_PLATEGA_SBP_", + extra="ignore", + ) + + WEBAPP_LABEL_RU: Optional[str] = None + WEBAPP_LABEL_EN: Optional[str] = None + WEBAPP_ICON: Optional[str] = None + TELEGRAM_LABEL_RU: Optional[str] = None + TELEGRAM_LABEL_EN: Optional[str] = None + TELEGRAM_EMOJI: Optional[str] = None + + +class PlategaCryptoPresentation(ProviderEnvConfig): + model_config = SettingsConfigDict( + env_file=provider_env_file(), + env_file_encoding="utf-8", + env_prefix="PAYMENT_PLATEGA_CRYPTO_", + extra="ignore", + ) + + WEBAPP_LABEL_RU: Optional[str] = None + WEBAPP_LABEL_EN: Optional[str] = None + WEBAPP_ICON: Optional[str] = None + TELEGRAM_LABEL_RU: Optional[str] = None + TELEGRAM_LABEL_EN: Optional[str] = None + TELEGRAM_EMOJI: Optional[str] = None + + class PlategaService(HttpClientMixin): def __init__( self, *, bot: Bot, settings: Settings, + config: PlategaConfig, i18n: JsonI18n, async_session_factory: sessionmaker, subscription_service: SubscriptionService, @@ -61,19 +138,20 @@ class PlategaService(HttpClientMixin): ): self.bot = bot self.settings = settings + self.config = config self.i18n = i18n self.async_session_factory = async_session_factory self.subscription_service = subscription_service self.referral_service = referral_service - self.base_url = (settings.PLATEGA_BASE_URL or "https://app.platega.io").rstrip("/") - self.merchant_id = settings.PLATEGA_MERCHANT_ID - self.secret = settings.PLATEGA_SECRET - self.payment_method = settings.PLATEGA_PAYMENT_METHOD - self.sbp_method = settings.platega_sbp_method_resolved - self.crypto_method = settings.PLATEGA_CRYPTO_METHOD - self.return_url = settings.PLATEGA_RETURN_URL or f"https://t.me/{default_return_url}" - self.failed_url = settings.PLATEGA_FAILED_URL or self.return_url + self.base_url = (config.BASE_URL or "https://app.platega.io").rstrip("/") + self.merchant_id = config.MERCHANT_ID + self.secret = config.SECRET + self.payment_method = config.PAYMENT_METHOD + self.sbp_method = config.sbp_method_resolved + self.crypto_method = config.CRYPTO_METHOD + self.return_url = config.RETURN_URL or f"https://t.me/{default_return_url}" + self.failed_url = config.FAILED_URL or self.return_url self._init_http_client(total_timeout=20) self._auth_headers = { @@ -81,7 +159,7 @@ class PlategaService(HttpClientMixin): "X-Secret": self.secret or "", "Content-Type": "application/json", } - self.configured: bool = bool(settings.PLATEGA_ENABLED and self.merchant_id and self.secret) + self.configured: bool = bool(config.ENABLED and self.merchant_id and self.secret) if not self.configured: logging.warning( "PlategaService initialized but not fully configured. Payments disabled." @@ -89,9 +167,9 @@ class PlategaService(HttpClientMixin): else: logging.info( "PlategaService configured. SBP button: %s (method=%s), Crypto button: %s (method=%s)", # noqa: E501 - "ON" if settings.PLATEGA_SBP_ENABLED else "OFF", + "ON" if config.SBP_ENABLED else "OFF", self.sbp_method, - "ON" if settings.PLATEGA_CRYPTO_ENABLED else "OFF", + "ON" if config.CRYPTO_ENABLED else "OFF", self.crypto_method, ) @@ -282,18 +360,20 @@ async def platega_webhook_route(request: web.Request) -> web.Response: router = Router(name="user_subscription_payments_platega_router") -def _resolve_platega_variant(callback_prefix: str, settings: Settings) -> Optional[Tuple[str, int]]: +def _resolve_platega_variant( + callback_prefix: str, config: PlategaConfig +) -> Optional[Tuple[str, int]]: """Map the callback prefix to (variant, payment_method_id) or ``None`` if disabled.""" if callback_prefix == "pay_platega_crypto": - if not settings.PLATEGA_CRYPTO_ENABLED: + if not config.CRYPTO_ENABLED: return None - return "crypto", settings.PLATEGA_CRYPTO_METHOD + return "crypto", config.CRYPTO_METHOD if callback_prefix == "pay_platega_sbp": - if not settings.PLATEGA_SBP_ENABLED: + if not config.SBP_ENABLED: return None - return "sbp", settings.platega_sbp_method_resolved + return "sbp", config.sbp_method_resolved # Legacy "pay_platega:" callback — keep working as SBP. - return "sbp", settings.platega_sbp_method_resolved + return "sbp", config.sbp_method_resolved @router.callback_query( @@ -319,7 +399,7 @@ async def pay_platega_callback_handler( return callback_prefix, _, _ = (callback.data or "").partition(":") - variant = _resolve_platega_variant(callback_prefix, settings) + variant = _resolve_platega_variant(callback_prefix, platega_service.config) if platega_service else None if variant is None: await safe_callback_answer(callback) return @@ -402,9 +482,12 @@ async def pay_platega_callback_handler( def create_service(ctx: ServiceFactoryContext) -> PlategaService: + bundle = ctx.config_for("platega_service") + config = bundle.config if bundle and isinstance(bundle.config, PlategaConfig) else PlategaConfig() return PlategaService( bot=ctx.bot, settings=ctx.settings, + config=config, i18n=ctx.i18n, async_session_factory=ctx.async_session_factory, subscription_service=ctx.subscription_service, @@ -414,18 +497,17 @@ def create_service(ctx: ServiceFactoryContext) -> PlategaService: async def _create_webapp_payment(ctx: WebAppPaymentContext, variant: str) -> web.Response: - settings = ctx.request.app["settings"] service: PlategaService = ctx.request.app["platega_service"] if not service or not service.configured: return payment_unavailable() if variant == "platega_crypto": - if not settings.PLATEGA_CRYPTO_ENABLED: + if not service.config.CRYPTO_ENABLED: return payment_unavailable() - platega_method_id = settings.PLATEGA_CRYPTO_METHOD + platega_method_id = service.config.CRYPTO_METHOD else: - if variant == "platega_sbp" and not settings.PLATEGA_SBP_ENABLED: + if variant == "platega_sbp" and not service.config.SBP_ENABLED: return payment_unavailable() - platega_method_id = settings.platega_sbp_method_resolved + platega_method_id = service.config.sbp_method_resolved try: amounts = payment_record_amounts( @@ -487,6 +569,68 @@ async def create_crypto_webapp_payment(ctx: WebAppPaymentContext) -> web.Respons return await _create_webapp_payment(ctx, "platega_crypto") +def _platega_presentation_manifest(subsection: str, default_icon: str, prefix: str) -> tuple: + return tuple( + ProviderManifestField( + key=f"PAYMENT_{prefix}_{suffix_key}", + type=type_, + label=label, + description=description, + placeholder=placeholder, + subsection=subsection, + target="presentation", + attr=attr, + ) + for suffix_key, type_, label, description, placeholder, attr in ( + ("WEBAPP_LABEL_RU", "string", "WebApp button text (RU)", + "Custom Russian text shown in the Web App payment method button.", + "", "WEBAPP_LABEL_RU"), + ("WEBAPP_LABEL_EN", "string", "WebApp button text (EN)", + "Custom English text shown in the Web App payment method button.", + "", "WEBAPP_LABEL_EN"), + ("WEBAPP_ICON", "icon", "WebApp button icon", + "Lucide icon name rendered inside the Web App payment method button.", + default_icon, "WEBAPP_ICON"), + ("TELEGRAM_LABEL_RU", "string", "Telegram button text (RU)", + "Custom Russian text shown in Telegram bot payment buttons.", + "", "TELEGRAM_LABEL_RU"), + ("TELEGRAM_LABEL_EN", "string", "Telegram button text (EN)", + "Custom English text shown in Telegram bot payment buttons.", + "", "TELEGRAM_LABEL_EN"), + ("TELEGRAM_EMOJI", "string", "Telegram button emoji", + "Emoji prepended to the Telegram bot payment button when customized.", + "", "TELEGRAM_EMOJI"), + ) + ) + + +_CONFIG_MANIFEST = ( + ProviderManifestField("PLATEGA_ENABLED", "bool", "Включена", + subsection="Platega", attr="ENABLED"), + ProviderManifestField("PLATEGA_BASE_URL", "url", "Base URL", + placeholder="https://app.platega.io", + subsection="Platega", attr="BASE_URL"), + ProviderManifestField("PLATEGA_MERCHANT_ID", "string", "Merchant ID", + subsection="Platega", attr="MERCHANT_ID"), + ProviderManifestField("PLATEGA_SECRET", "string", "Secret", + subsection="Platega", secret=True, attr="SECRET"), + ProviderManifestField("PLATEGA_PAYMENT_METHOD", "int", "Метод оплаты (legacy)", + subsection="Platega", attr="PAYMENT_METHOD"), + ProviderManifestField("PLATEGA_SBP_ENABLED", "bool", "SBP-кнопка", + subsection="Platega", attr="SBP_ENABLED"), + ProviderManifestField("PLATEGA_SBP_METHOD", "int", "SBP method ID", + subsection="Platega", attr="SBP_METHOD"), + ProviderManifestField("PLATEGA_CRYPTO_ENABLED", "bool", "Crypto-кнопка", + subsection="Platega", attr="CRYPTO_ENABLED"), + ProviderManifestField("PLATEGA_CRYPTO_METHOD", "int", "Crypto method ID", + subsection="Platega", attr="CRYPTO_METHOD"), + ProviderManifestField("PLATEGA_RETURN_URL", "url", "Return URL", + subsection="Platega", attr="RETURN_URL"), + ProviderManifestField("PLATEGA_FAILED_URL", "url", "Failed URL", + subsection="Platega", attr="FAILED_URL"), +) + + SBP_SPEC = PaymentProviderSpec( id="platega_sbp", provider_key="platega", @@ -497,15 +641,20 @@ SBP_SPEC = PaymentProviderSpec( telegram_labels={"ru": "Оплата через СБП", "en": "Pay via SBP"}, telegram_emoji="🏦", pending_status="pending_platega", - enabled=lambda settings: settings.PLATEGA_ENABLED and settings.PLATEGA_SBP_ENABLED, + enabled=lambda config: bool(getattr(config, "ENABLED", False) and getattr(config, "SBP_ENABLED", False)), service_key="platega_service", callback_prefix="pay_platega_sbp", aliases=("platega",), router=router, create_service=create_service, - webhook_path=lambda settings: settings.platega_webhook_path, + webhook_path=lambda source: "/webhook/platega", webhook_route=platega_webhook_route, create_webapp_payment=create_sbp_webapp_payment, + config_class=PlategaConfig, + presentation_class=PlategaSbpPresentation, + manifest_fields=_CONFIG_MANIFEST + _platega_presentation_manifest( + "Platega SBP", "CreditCard", "PLATEGA_SBP" + ), ) CRYPTO_SPEC = PaymentProviderSpec( @@ -518,10 +667,17 @@ CRYPTO_SPEC = PaymentProviderSpec( telegram_labels={"ru": "Оплата криптой", "en": "Pay with crypto"}, telegram_emoji="🪙", pending_status="pending_platega", - enabled=lambda settings: settings.PLATEGA_ENABLED and settings.PLATEGA_CRYPTO_ENABLED, + # Uses the same PlategaConfig as SBP_SPEC (shared service_key); enable + # flag combines the global PLATEGA_ENABLED with the per-button toggle. + enabled=lambda config: bool(getattr(config, "ENABLED", False) and getattr(config, "CRYPTO_ENABLED", False)), service_key="platega_service", callback_prefix="pay_platega_crypto", create_webapp_payment=create_crypto_webapp_payment, + config_class=PlategaConfig, + presentation_class=PlategaCryptoPresentation, + manifest_fields=_platega_presentation_manifest( + "Platega Crypto", "Bitcoin", "PLATEGA_CRYPTO" + ), ) SPECS = (SBP_SPEC, CRYPTO_SPEC) diff --git a/backend/bot/payment_providers/registry.py b/backend/bot/payment_providers/registry.py index aaaa7dc..5021a67 100644 --- a/backend/bot/payment_providers/registry.py +++ b/backend/bot/payment_providers/registry.py @@ -28,7 +28,12 @@ PAYMENT_PROVIDER_SPECS: tuple[PaymentProviderSpec, ...] = ( # singleton populated by build_provider_configs() on startup. Modules that need # to read provider configs without changing call signatures (e.g. presentation # resolution from arbitrary callers) can look them up via current_provider_configs(). +# +# Keyed by ``service_key`` because multiple SPECs (Platega SBP / Platega Crypto) +# can share the same backing service. Per-SPEC presentation overrides live in +# ``_provider_presentations`` instead, indexed by ``spec.id``. _provider_configs: Dict[str, ProviderConfigBundle] = {} +_provider_presentations: Dict[str, Any] = {} def iter_provider_specs() -> Iterable[PaymentProviderSpec]: @@ -47,31 +52,42 @@ def build_provider_configs() -> Dict[str, ProviderConfigBundle]: """Instantiate per-provider BaseSettings models declared on each SPEC. Returns a mapping ``service_key`` → ``ProviderConfigBundle(config, presentation)``. - Specs with neither ``config_class`` nor ``presentation_class`` are skipped. - The result is cached as the process-wide bundle. + For SPECs that share a service (Platega SBP + Platega Crypto), only the + first one's presentation lands in the shared bundle — per-SPEC presentation + overrides live separately in ``_provider_presentations`` keyed by ``spec.id``. """ from .base import provider_env_file env_file = provider_env_file() - init_kwargs = {"_env_file": env_file} if env_file is not None else {"_env_file": None} + init_kwargs = {"_env_file": env_file} bundles: Dict[str, ProviderConfigBundle] = {} - seen: set[str] = set() + presentations: Dict[str, Any] = {} + seen_services: set[str] = set() for spec in PAYMENT_PROVIDER_SPECS: - if not spec.service_key or spec.service_key in seen: + if spec.presentation_class is not None: + presentations[spec.id] = spec.presentation_class(**init_kwargs) + + if not spec.service_key or spec.service_key in seen_services: continue if spec.config_class is None and spec.presentation_class is None: continue - seen.add(spec.service_key) + seen_services.add(spec.service_key) bundles[spec.service_key] = ProviderConfigBundle( config=spec.config_class(**init_kwargs) if spec.config_class else None, - presentation=spec.presentation_class(**init_kwargs) if spec.presentation_class else None, + presentation=presentations.get(spec.id), ) _provider_configs.clear() _provider_configs.update(bundles) + _provider_presentations.clear() + _provider_presentations.update(presentations) return bundles +def get_spec_presentation(spec_id: str) -> Optional[Any]: + return _provider_presentations.get(spec_id) + + def current_provider_configs() -> Mapping[str, ProviderConfigBundle]: return _provider_configs @@ -119,11 +135,14 @@ def _provider_presentation_value( *, language: Optional[str] = None, ) -> Optional[str]: - bundle = _provider_configs.get(spec.service_key) if spec.service_key else None - if not bundle or not bundle.presentation: + presentation = _provider_presentations.get(spec.id) + if presentation is None: + bundle = _provider_configs.get(spec.service_key) if spec.service_key else None + presentation = bundle.presentation if bundle else None + if presentation is None: return None attr = _presentation_attr(suffix, language=language) - return _setting_value(bundle.presentation, attr) + return _setting_value(presentation, attr) def _localized_setting_value( diff --git a/backend/bot/services/settings_override_service.py b/backend/bot/services/settings_override_service.py index 9b3ca55..a901cfd 100644 --- a/backend/bot/services/settings_override_service.py +++ b/backend/bot/services/settings_override_service.py @@ -72,14 +72,20 @@ def _apply_to_provider_bundle(key: str, value: Any) -> bool: get_provider_bundle, ) + from bot.payment_providers import get_spec_presentation + owner = find_manifest_owner(key) if owner is None: return False spec, manifest_field = owner - bundle = get_provider_bundle(spec.service_key) - if bundle is None: - return False - target = bundle.presentation if manifest_field.target == "presentation" else bundle.config + if manifest_field.target == "presentation": + target = get_spec_presentation(spec.id) + if target is None: + bundle = get_provider_bundle(spec.service_key) + target = bundle.presentation if bundle else None + else: + bundle = get_provider_bundle(spec.service_key) + target = bundle.config if bundle else None if target is None: return False attr_name = manifest_field.attr or key @@ -270,7 +276,11 @@ async def update_overrides( # default by instantiating a fresh Settings() / provider-config model # (cheap; just a few ms) and copying the matching attributes back over. if valid_deletes: - from bot.payment_providers import find_manifest_owner, get_provider_bundle + from bot.payment_providers import ( + find_manifest_owner, + get_provider_bundle, + get_spec_presentation, + ) try: env_only = Settings() @@ -278,14 +288,14 @@ async def update_overrides( owner = find_manifest_owner(key) if owner is not None: spec, manifest_field = owner - bundle = get_provider_bundle(spec.service_key) - if bundle is None: - continue - target = ( - bundle.presentation - if manifest_field.target == "presentation" - else bundle.config - ) + if manifest_field.target == "presentation": + target = get_spec_presentation(spec.id) + if target is None: + bundle = get_provider_bundle(spec.service_key) + target = bundle.presentation if bundle else None + else: + bundle = get_provider_bundle(spec.service_key) + target = bundle.config if bundle else None if target is None: continue cls = type(target) diff --git a/backend/config/settings.py b/backend/config/settings.py index 9d7f432..e695cc2 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -38,17 +38,6 @@ class PaymentSettings(BaseModel): yookassa_payment_subject: str yookassa_autopayments_enabled: bool yookassa_autopayments_require_card_binding: bool - platega_enabled: bool - platega_base_url: str - platega_merchant_id: Optional[str] - platega_secret: Optional[str] - platega_payment_method: int - platega_sbp_enabled: bool - platega_crypto_enabled: bool - platega_sbp_method: int - platega_crypto_method: int - platega_return_url: Optional[str] - platega_failed_url: Optional[str] class EmailSettings(BaseModel): @@ -182,51 +171,12 @@ class Settings(BaseSettings): description="Comma-separated list of reverse proxy IPs or CIDRs trusted to forward X-Forwarded-For.", # noqa: E501 ) - PLATEGA_ENABLED: bool = Field(default=False) - PLATEGA_BASE_URL: str = Field(default="https://app.platega.io") - PLATEGA_MERCHANT_ID: Optional[str] = None - PLATEGA_SECRET: Optional[str] = None - PLATEGA_PAYMENT_METHOD: int = Field( - default=2, - description="Legacy Platega payment method ID. Used as fallback for PLATEGA_SBP_METHOD when the new field is unset.", # noqa: E501 - ) - PLATEGA_SBP_ENABLED: bool = Field( - default=False, - description="Show a separate Platega SBP payment button.", - ) - PLATEGA_CRYPTO_ENABLED: bool = Field( - default=False, - description="Show a separate Platega crypto payment button.", - ) - PLATEGA_SBP_METHOD: int = Field( - default=2, - description="Platega method ID for SBP QR (default 2).", - ) - PLATEGA_CRYPTO_METHOD: int = Field( - default=13, - description="Platega method ID for crypto (default 13).", - ) - PLATEGA_RETURN_URL: Optional[str] = Field(default=None) - PLATEGA_FAILED_URL: Optional[str] = Field(default=None) - YOOKASSA_ENABLED: bool = Field(default=True) STARS_ENABLED: bool = Field(default=True) PAYMENT_METHODS_ORDER: Optional[str] = Field( default=None, description="Comma-separated list of payment methods to show (e.g., severpay,wata,freekassa,yookassa,platega,stars,cryptopay)", # noqa: E501 ) - PAYMENT_PLATEGA_SBP_WEBAPP_LABEL_RU: Optional[str] = None - PAYMENT_PLATEGA_SBP_WEBAPP_LABEL_EN: Optional[str] = None - PAYMENT_PLATEGA_SBP_WEBAPP_ICON: Optional[str] = None - PAYMENT_PLATEGA_SBP_TELEGRAM_LABEL_RU: Optional[str] = None - PAYMENT_PLATEGA_SBP_TELEGRAM_LABEL_EN: Optional[str] = None - PAYMENT_PLATEGA_SBP_TELEGRAM_EMOJI: Optional[str] = None - PAYMENT_PLATEGA_CRYPTO_WEBAPP_LABEL_RU: Optional[str] = None - PAYMENT_PLATEGA_CRYPTO_WEBAPP_LABEL_EN: Optional[str] = None - PAYMENT_PLATEGA_CRYPTO_WEBAPP_ICON: Optional[str] = None - PAYMENT_PLATEGA_CRYPTO_TELEGRAM_LABEL_RU: Optional[str] = None - PAYMENT_PLATEGA_CRYPTO_TELEGRAM_LABEL_EN: Optional[str] = None - PAYMENT_PLATEGA_CRYPTO_TELEGRAM_EMOJI: Optional[str] = None PAYMENT_YOOKASSA_WEBAPP_LABEL_RU: Optional[str] = None PAYMENT_YOOKASSA_WEBAPP_LABEL_EN: Optional[str] = None PAYMENT_YOOKASSA_WEBAPP_ICON: Optional[str] = None @@ -480,17 +430,6 @@ class Settings(BaseSettings): yookassa_payment_subject=self.YOOKASSA_PAYMENT_SUBJECT, yookassa_autopayments_enabled=self.YOOKASSA_AUTOPAYMENTS_ENABLED, yookassa_autopayments_require_card_binding=self.YOOKASSA_AUTOPAYMENTS_REQUIRE_CARD_BINDING, - platega_enabled=self.PLATEGA_ENABLED, - platega_base_url=self.PLATEGA_BASE_URL, - platega_merchant_id=self.PLATEGA_MERCHANT_ID, - platega_secret=self.PLATEGA_SECRET, - platega_payment_method=self.PLATEGA_PAYMENT_METHOD, - platega_sbp_enabled=self.PLATEGA_SBP_ENABLED, - platega_crypto_enabled=self.PLATEGA_CRYPTO_ENABLED, - platega_sbp_method=self.platega_sbp_method_resolved, - platega_crypto_method=self.PLATEGA_CRYPTO_METHOD, - platega_return_url=self.PLATEGA_RETURN_URL, - platega_failed_url=self.PLATEGA_FAILED_URL, ) @computed_field @@ -631,19 +570,6 @@ class Settings(BaseSettings): - @computed_field - @property - def platega_webhook_path(self) -> str: - return "/webhook/platega" - - @computed_field - @property - def platega_full_webhook_url(self) -> Optional[str]: - base = self.WEBHOOK_BASE_URL - if base: - return f"{base.rstrip('/')}{self.platega_webhook_path}" - return None - # Computed YooKassa receipt fields based on recurring toggle @computed_field @property @@ -883,14 +809,6 @@ class Settings(BaseSettings): methods.append(slug) return methods or default_order - @computed_field - @property - def platega_sbp_method_resolved(self) -> int: - """SBP method ID, falling back to legacy PLATEGA_PAYMENT_METHOD when SBP-specific value is the default.""" # noqa: E501 - if self.PLATEGA_SBP_METHOD != 2: - return self.PLATEGA_SBP_METHOD - return self.PLATEGA_PAYMENT_METHOD or 2 - @computed_field @property def email_auth_configured(self) -> bool: @@ -971,8 +889,6 @@ class Settings(BaseSettings): @field_validator( "REQUIRED_CHANNEL_LINK", - "PLATEGA_RETURN_URL", - "PLATEGA_FAILED_URL", "CRYPT4_REDIRECT_URL", "PRIVACY_POLICY_URL", "USER_AGREEMENT_URL", @@ -1063,14 +979,6 @@ def get_settings() -> Settings: logging.warning( "WARNING: LKNPD credentials are incomplete. Receipt sending will be disabled." ) - if _settings_instance.PLATEGA_ENABLED: - if ( - not _settings_instance.PLATEGA_MERCHANT_ID - or not _settings_instance.PLATEGA_SECRET - ): - logging.warning( - "CRITICAL: Platega is enabled but merchant credentials (PLATEGA_MERCHANT_ID/PLATEGA_SECRET) are missing. Platega payments will not work." # noqa: E501 - ) except ValidationError as e: logging.critical(f"Pydantic validation error while loading settings: {e}")