refactor: move wata env-config into module
This commit is contained in:
@@ -336,61 +336,6 @@ SETTINGS_MANIFEST: List[SettingField] = [
|
|||||||
SettingField("PLATEGA_FAILED_URL", "url", "payments", "Failed 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_SBP", "Platega SBP", default_icon="CreditCard"),
|
||||||
*_payment_presentation_fields("PLATEGA_CRYPTO", "Platega Crypto", default_icon="Bitcoin"),
|
*_payment_presentation_fields("PLATEGA_CRYPTO", "Platega Crypto", default_icon="Bitcoin"),
|
||||||
# Wata
|
|
||||||
SettingField("WATA_ENABLED", "bool", "payments", "Enabled", subsection="Wata"),
|
|
||||||
SettingField(
|
|
||||||
"WATA_API_TOKEN",
|
|
||||||
"string",
|
|
||||||
"payments",
|
|
||||||
"API token",
|
|
||||||
subsection="Wata",
|
|
||||||
secret=True,
|
|
||||||
),
|
|
||||||
SettingField(
|
|
||||||
"WATA_BASE_URL",
|
|
||||||
"url",
|
|
||||||
"payments",
|
|
||||||
"Base URL",
|
|
||||||
placeholder="https://api.wata.pro/api/h2h",
|
|
||||||
subsection="Wata",
|
|
||||||
),
|
|
||||||
SettingField("WATA_RETURN_URL", "url", "payments", "Return URL", subsection="Wata"),
|
|
||||||
SettingField("WATA_FAILED_URL", "url", "payments", "Failed URL", subsection="Wata"),
|
|
||||||
SettingField(
|
|
||||||
"WATA_PAYMENT_LINK_TTL_DAYS",
|
|
||||||
"int",
|
|
||||||
"payments",
|
|
||||||
"Payment link lifetime (days)",
|
|
||||||
"1..30; Wata defaults to 3 days and allows up to 30 days.",
|
|
||||||
subsection="Wata",
|
|
||||||
min=1,
|
|
||||||
max=30,
|
|
||||||
),
|
|
||||||
SettingField(
|
|
||||||
"WATA_WEBHOOK_VERIFY_SIGNATURE",
|
|
||||||
"bool",
|
|
||||||
"payments",
|
|
||||||
"Verify webhook signature",
|
|
||||||
subsection="Wata",
|
|
||||||
),
|
|
||||||
SettingField(
|
|
||||||
"WATA_PUBLIC_KEY",
|
|
||||||
"text",
|
|
||||||
"payments",
|
|
||||||
"Webhook public key",
|
|
||||||
"Optional. If empty, the backend fetches it from Wata.",
|
|
||||||
subsection="Wata",
|
|
||||||
secret=True,
|
|
||||||
),
|
|
||||||
SettingField(
|
|
||||||
"WATA_TRUSTED_IPS",
|
|
||||||
"string",
|
|
||||||
"payments",
|
|
||||||
"Trusted IPs",
|
|
||||||
"Comma-separated IP addresses accepted for Wata webhooks.",
|
|
||||||
subsection="Wata",
|
|
||||||
),
|
|
||||||
*_payment_presentation_fields("WATA", "Wata", default_icon="WalletCards"),
|
|
||||||
# CryptoPay
|
# CryptoPay
|
||||||
SettingField("CRYPTOPAY_ENABLED", "bool", "payments", "Включена", subsection="CryptoPay"),
|
SettingField("CRYPTOPAY_ENABLED", "bool", "payments", "Включена", subsection="CryptoPay"),
|
||||||
SettingField(
|
SettingField(
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ import base64
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Any, Dict, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from aiogram import Bot, F, Router, types
|
from aiogram import Bot, F, Router, types
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from cryptography.exceptions import InvalidSignature
|
from cryptography.exceptions import InvalidSignature
|
||||||
from cryptography.hazmat.primitives import hashes, serialization
|
from cryptography.hazmat.primitives import hashes, serialization
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
|
from pydantic import Field, field_validator
|
||||||
|
from pydantic_settings import SettingsConfigDict
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
@@ -19,7 +21,13 @@ from bot.utils.request_security import ip_in_allowlist, request_client_ip
|
|||||||
from config.settings import Settings
|
from config.settings import Settings
|
||||||
from db.dal import payment_dal
|
from db.dal import payment_dal
|
||||||
|
|
||||||
from .base import PaymentProviderSpec, ServiceFactoryContext, WebAppPaymentContext
|
from .base import (
|
||||||
|
PaymentProviderSpec,
|
||||||
|
ProviderEnvConfig,
|
||||||
|
ProviderManifestField,
|
||||||
|
ServiceFactoryContext,
|
||||||
|
WebAppPaymentContext,
|
||||||
|
)
|
||||||
from .shared import (
|
from .shared import (
|
||||||
HttpClientMixin,
|
HttpClientMixin,
|
||||||
PaymentSuccessRequest,
|
PaymentSuccessRequest,
|
||||||
@@ -48,12 +56,74 @@ router = Router(name="user_subscription_payments_wata_router")
|
|||||||
_LOG = "wata"
|
_LOG = "wata"
|
||||||
|
|
||||||
|
|
||||||
|
class WataConfig(ProviderEnvConfig):
|
||||||
|
model_config = SettingsConfigDict(
|
||||||
|
env_file=".env",
|
||||||
|
env_file_encoding="utf-8",
|
||||||
|
env_prefix="WATA_",
|
||||||
|
extra="ignore",
|
||||||
|
)
|
||||||
|
|
||||||
|
ENABLED: bool = Field(default=False)
|
||||||
|
API_TOKEN: Optional[str] = None
|
||||||
|
BASE_URL: str = Field(default="https://api.wata.pro/api/h2h")
|
||||||
|
RETURN_URL: Optional[str] = None
|
||||||
|
FAILED_URL: Optional[str] = None
|
||||||
|
PAYMENT_LINK_TTL_DAYS: int = Field(default=3)
|
||||||
|
WEBHOOK_VERIFY_SIGNATURE: bool = Field(default=True)
|
||||||
|
PUBLIC_KEY: Optional[str] = None
|
||||||
|
TRUSTED_IPS: str = Field(default="62.84.126.140,51.250.106.150")
|
||||||
|
|
||||||
|
@field_validator("PAYMENT_LINK_TTL_DAYS", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def _clamp_ttl(cls, v):
|
||||||
|
if isinstance(v, str):
|
||||||
|
v = v.strip()
|
||||||
|
try:
|
||||||
|
value = int(v)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return 3
|
||||||
|
return min(30, max(1, value))
|
||||||
|
|
||||||
|
@field_validator("API_TOKEN", "RETURN_URL", "FAILED_URL", "PUBLIC_KEY", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def _strip_optional(cls, v):
|
||||||
|
if isinstance(v, str) and not v.strip():
|
||||||
|
return None
|
||||||
|
return v
|
||||||
|
|
||||||
|
@property
|
||||||
|
def webhook_path(self) -> str:
|
||||||
|
return "/webhook/wata"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def trusted_ips_list(self) -> List[str]:
|
||||||
|
return [item.strip() for item in (self.TRUSTED_IPS or "").split(",") if item.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
class WataPresentation(ProviderEnvConfig):
|
||||||
|
model_config = SettingsConfigDict(
|
||||||
|
env_file=".env",
|
||||||
|
env_file_encoding="utf-8",
|
||||||
|
env_prefix="PAYMENT_WATA_",
|
||||||
|
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 WataService(HttpClientMixin):
|
class WataService(HttpClientMixin):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
|
config: WataConfig,
|
||||||
i18n: JsonI18n,
|
i18n: JsonI18n,
|
||||||
async_session_factory: sessionmaker,
|
async_session_factory: sessionmaker,
|
||||||
subscription_service: SubscriptionService,
|
subscription_service: SubscriptionService,
|
||||||
@@ -62,21 +132,22 @@ class WataService(HttpClientMixin):
|
|||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
|
self.config = config
|
||||||
self.i18n = i18n
|
self.i18n = i18n
|
||||||
self.async_session_factory = async_session_factory
|
self.async_session_factory = async_session_factory
|
||||||
self.subscription_service = subscription_service
|
self.subscription_service = subscription_service
|
||||||
self.referral_service = referral_service
|
self.referral_service = referral_service
|
||||||
|
|
||||||
self.base_url = (settings.WATA_BASE_URL or "https://api.wata.pro/api/h2h").rstrip("/")
|
self.base_url = (config.BASE_URL or "https://api.wata.pro/api/h2h").rstrip("/")
|
||||||
self.api_token = settings.WATA_API_TOKEN or ""
|
self.api_token = config.API_TOKEN or ""
|
||||||
self.return_url = settings.WATA_RETURN_URL or f"https://t.me/{default_return_url}"
|
self.return_url = config.RETURN_URL or f"https://t.me/{default_return_url}"
|
||||||
self.failed_url = settings.WATA_FAILED_URL or self.return_url
|
self.failed_url = config.FAILED_URL or self.return_url
|
||||||
self.payment_link_ttl_days = settings.WATA_PAYMENT_LINK_TTL_DAYS
|
self.payment_link_ttl_days = config.PAYMENT_LINK_TTL_DAYS
|
||||||
self.verify_webhook_signature = settings.WATA_WEBHOOK_VERIFY_SIGNATURE
|
self.verify_webhook_signature = config.WEBHOOK_VERIFY_SIGNATURE
|
||||||
self._public_key_pem = settings.WATA_PUBLIC_KEY
|
self._public_key_pem = config.PUBLIC_KEY
|
||||||
|
|
||||||
self._init_http_client(total_timeout=20)
|
self._init_http_client(total_timeout=20)
|
||||||
self.configured: bool = bool(settings.WATA_ENABLED and self.api_token)
|
self.configured: bool = bool(config.ENABLED and self.api_token)
|
||||||
if not self.configured:
|
if not self.configured:
|
||||||
logging.warning("WataService initialized but not fully configured. Payments disabled.")
|
logging.warning("WataService initialized but not fully configured. Payments disabled.")
|
||||||
|
|
||||||
@@ -119,7 +190,8 @@ class WataService(HttpClientMixin):
|
|||||||
|
|
||||||
async def _get_public_key_pem(self) -> Optional[str]:
|
async def _get_public_key_pem(self) -> Optional[str]:
|
||||||
if self._public_key_pem:
|
if self._public_key_pem:
|
||||||
return self._public_key_pem.replace("\\n", "\n")
|
value = self._public_key_pem
|
||||||
|
return value.replace("\\n", "\n") if isinstance(value, str) else None
|
||||||
|
|
||||||
session = await self._get_session()
|
session = await self._get_session()
|
||||||
try:
|
try:
|
||||||
@@ -159,9 +231,8 @@ class WataService(HttpClientMixin):
|
|||||||
return web.Response(status=503, text="wata_disabled")
|
return web.Response(status=503, text="wata_disabled")
|
||||||
|
|
||||||
client_ip = request_client_ip(request, trusted_proxies=self.settings.trusted_proxies)
|
client_ip = request_client_ip(request, trusted_proxies=self.settings.trusted_proxies)
|
||||||
if self.settings.wata_trusted_ips and not ip_in_allowlist(
|
trusted = self.config.trusted_ips_list
|
||||||
client_ip, self.settings.wata_trusted_ips
|
if trusted and not ip_in_allowlist(client_ip, trusted):
|
||||||
):
|
|
||||||
logging.warning("Wata webhook denied from unauthorized IP source.")
|
logging.warning("Wata webhook denied from unauthorized IP source.")
|
||||||
return web.Response(status=403, text="forbidden")
|
return web.Response(status=403, text="forbidden")
|
||||||
|
|
||||||
@@ -417,9 +488,12 @@ async def wata_webhook_route(request: web.Request) -> web.Response:
|
|||||||
|
|
||||||
|
|
||||||
def create_service(ctx: ServiceFactoryContext) -> WataService:
|
def create_service(ctx: ServiceFactoryContext) -> WataService:
|
||||||
|
bundle = ctx.config_for("wata_service")
|
||||||
|
config = bundle.config if bundle and isinstance(bundle.config, WataConfig) else WataConfig()
|
||||||
return WataService(
|
return WataService(
|
||||||
bot=ctx.bot,
|
bot=ctx.bot,
|
||||||
settings=ctx.settings,
|
settings=ctx.settings,
|
||||||
|
config=config,
|
||||||
i18n=ctx.i18n,
|
i18n=ctx.i18n,
|
||||||
async_session_factory=ctx.async_session_factory,
|
async_session_factory=ctx.async_session_factory,
|
||||||
subscription_service=ctx.subscription_service,
|
subscription_service=ctx.subscription_service,
|
||||||
@@ -428,6 +502,55 @@ def create_service(ctx: ServiceFactoryContext) -> WataService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_PRESENTATION_MANIFEST = tuple(
|
||||||
|
ProviderManifestField(
|
||||||
|
key=key, type=type_, label=label, description=description,
|
||||||
|
placeholder=placeholder, subsection="Wata",
|
||||||
|
target="presentation", attr=attr,
|
||||||
|
)
|
||||||
|
for key, type_, label, description, placeholder, attr in (
|
||||||
|
("PAYMENT_WATA_WEBAPP_LABEL_RU", "string", "WebApp button text (RU)",
|
||||||
|
"Custom Russian text shown in the Web App payment method button.", "", "WEBAPP_LABEL_RU"),
|
||||||
|
("PAYMENT_WATA_WEBAPP_LABEL_EN", "string", "WebApp button text (EN)",
|
||||||
|
"Custom English text shown in the Web App payment method button.", "", "WEBAPP_LABEL_EN"),
|
||||||
|
("PAYMENT_WATA_WEBAPP_ICON", "icon", "WebApp button icon",
|
||||||
|
"Lucide icon name rendered inside the Web App payment method button.",
|
||||||
|
"WalletCards", "WEBAPP_ICON"),
|
||||||
|
("PAYMENT_WATA_TELEGRAM_LABEL_RU", "string", "Telegram button text (RU)",
|
||||||
|
"Custom Russian text shown in Telegram bot payment buttons.", "", "TELEGRAM_LABEL_RU"),
|
||||||
|
("PAYMENT_WATA_TELEGRAM_LABEL_EN", "string", "Telegram button text (EN)",
|
||||||
|
"Custom English text shown in Telegram bot payment buttons.", "", "TELEGRAM_LABEL_EN"),
|
||||||
|
("PAYMENT_WATA_TELEGRAM_EMOJI", "string", "Telegram button emoji",
|
||||||
|
"Emoji prepended to the Telegram bot payment button when customized.",
|
||||||
|
"💳", "TELEGRAM_EMOJI"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
_CONFIG_MANIFEST = (
|
||||||
|
ProviderManifestField("WATA_ENABLED", "bool", "Enabled", subsection="Wata", attr="ENABLED"),
|
||||||
|
ProviderManifestField("WATA_API_TOKEN", "string", "API token", subsection="Wata",
|
||||||
|
secret=True, attr="API_TOKEN"),
|
||||||
|
ProviderManifestField("WATA_BASE_URL", "url", "Base URL",
|
||||||
|
placeholder="https://api.wata.pro/api/h2h",
|
||||||
|
subsection="Wata", attr="BASE_URL"),
|
||||||
|
ProviderManifestField("WATA_RETURN_URL", "url", "Return URL",
|
||||||
|
subsection="Wata", attr="RETURN_URL"),
|
||||||
|
ProviderManifestField("WATA_FAILED_URL", "url", "Failed URL",
|
||||||
|
subsection="Wata", attr="FAILED_URL"),
|
||||||
|
ProviderManifestField("WATA_PAYMENT_LINK_TTL_DAYS", "int", "Payment link lifetime (days)",
|
||||||
|
description="1..30; Wata defaults to 3 days and allows up to 30 days.",
|
||||||
|
subsection="Wata", min=1, max=30, attr="PAYMENT_LINK_TTL_DAYS"),
|
||||||
|
ProviderManifestField("WATA_WEBHOOK_VERIFY_SIGNATURE", "bool", "Verify webhook signature",
|
||||||
|
subsection="Wata", attr="WEBHOOK_VERIFY_SIGNATURE"),
|
||||||
|
ProviderManifestField("WATA_PUBLIC_KEY", "text", "Webhook public key",
|
||||||
|
description="Optional. If empty, the backend fetches it from Wata.",
|
||||||
|
subsection="Wata", secret=True, attr="PUBLIC_KEY"),
|
||||||
|
ProviderManifestField("WATA_TRUSTED_IPS", "string", "Trusted IPs",
|
||||||
|
description="Comma-separated IP addresses accepted for Wata webhooks.",
|
||||||
|
subsection="Wata", attr="TRUSTED_IPS"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
SPEC = PaymentProviderSpec(
|
SPEC = PaymentProviderSpec(
|
||||||
id="wata",
|
id="wata",
|
||||||
provider_key="wata",
|
provider_key="wata",
|
||||||
@@ -438,12 +561,15 @@ SPEC = PaymentProviderSpec(
|
|||||||
telegram_labels={"ru": "Wata", "en": "Wata"},
|
telegram_labels={"ru": "Wata", "en": "Wata"},
|
||||||
telegram_emoji="💳",
|
telegram_emoji="💳",
|
||||||
pending_status="pending_wata",
|
pending_status="pending_wata",
|
||||||
enabled=lambda settings: settings.WATA_ENABLED,
|
enabled=lambda config: bool(getattr(config, "ENABLED", False)),
|
||||||
service_key="wata_service",
|
service_key="wata_service",
|
||||||
callback_prefix="pay_wata",
|
callback_prefix="pay_wata",
|
||||||
router=router,
|
router=router,
|
||||||
create_service=create_service,
|
create_service=create_service,
|
||||||
webhook_path=lambda settings: settings.wata_webhook_path,
|
webhook_path=lambda source: "/webhook/wata",
|
||||||
webhook_route=wata_webhook_route,
|
webhook_route=wata_webhook_route,
|
||||||
create_webapp_payment=create_webapp_payment,
|
create_webapp_payment=create_webapp_payment,
|
||||||
|
config_class=WataConfig,
|
||||||
|
presentation_class=WataPresentation,
|
||||||
|
manifest_fields=_CONFIG_MANIFEST + _PRESENTATION_MANIFEST,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -56,15 +56,6 @@ class PaymentSettings(BaseModel):
|
|||||||
platega_crypto_method: int
|
platega_crypto_method: int
|
||||||
platega_return_url: Optional[str]
|
platega_return_url: Optional[str]
|
||||||
platega_failed_url: Optional[str]
|
platega_failed_url: Optional[str]
|
||||||
wata_enabled: bool
|
|
||||||
wata_api_token: Optional[str]
|
|
||||||
wata_base_url: str
|
|
||||||
wata_return_url: Optional[str]
|
|
||||||
wata_failed_url: Optional[str]
|
|
||||||
wata_payment_link_ttl_days: int
|
|
||||||
wata_webhook_verify_signature: bool
|
|
||||||
wata_public_key: Optional[str]
|
|
||||||
wata_trusted_ips: List[str]
|
|
||||||
cryptopay_enabled: bool
|
cryptopay_enabled: bool
|
||||||
cryptopay_token: Optional[str]
|
cryptopay_token: Optional[str]
|
||||||
cryptopay_network: str
|
cryptopay_network: str
|
||||||
@@ -248,25 +239,6 @@ class Settings(BaseSettings):
|
|||||||
description="Comma-separated FreeKassa webhook IP allowlist.",
|
description="Comma-separated FreeKassa webhook IP allowlist.",
|
||||||
)
|
)
|
||||||
|
|
||||||
WATA_ENABLED: bool = Field(default=False)
|
|
||||||
WATA_API_TOKEN: Optional[str] = None
|
|
||||||
WATA_BASE_URL: str = Field(default="https://api.wata.pro/api/h2h")
|
|
||||||
WATA_RETURN_URL: Optional[str] = None
|
|
||||||
WATA_FAILED_URL: Optional[str] = None
|
|
||||||
WATA_PAYMENT_LINK_TTL_DAYS: int = Field(
|
|
||||||
default=3,
|
|
||||||
description="Payment link lifetime in days (1-30).",
|
|
||||||
)
|
|
||||||
WATA_WEBHOOK_VERIFY_SIGNATURE: bool = Field(default=True)
|
|
||||||
WATA_PUBLIC_KEY: Optional[str] = Field(
|
|
||||||
default=None,
|
|
||||||
description="Optional cached Wata RSA public key for webhook signature verification.",
|
|
||||||
)
|
|
||||||
WATA_TRUSTED_IPS: str = Field(
|
|
||||||
default="62.84.126.140,51.250.106.150",
|
|
||||||
description="Comma-separated Wata webhook IP allowlist.",
|
|
||||||
)
|
|
||||||
|
|
||||||
YOOKASSA_ENABLED: bool = Field(default=True)
|
YOOKASSA_ENABLED: bool = Field(default=True)
|
||||||
STARS_ENABLED: bool = Field(default=True)
|
STARS_ENABLED: bool = Field(default=True)
|
||||||
PAYMENT_METHODS_ORDER: Optional[str] = Field(
|
PAYMENT_METHODS_ORDER: Optional[str] = Field(
|
||||||
@@ -291,12 +263,6 @@ class Settings(BaseSettings):
|
|||||||
PAYMENT_PLATEGA_CRYPTO_TELEGRAM_LABEL_RU: 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_LABEL_EN: Optional[str] = None
|
||||||
PAYMENT_PLATEGA_CRYPTO_TELEGRAM_EMOJI: Optional[str] = None
|
PAYMENT_PLATEGA_CRYPTO_TELEGRAM_EMOJI: Optional[str] = None
|
||||||
PAYMENT_WATA_WEBAPP_LABEL_RU: Optional[str] = None
|
|
||||||
PAYMENT_WATA_WEBAPP_LABEL_EN: Optional[str] = None
|
|
||||||
PAYMENT_WATA_WEBAPP_ICON: Optional[str] = None
|
|
||||||
PAYMENT_WATA_TELEGRAM_LABEL_RU: Optional[str] = None
|
|
||||||
PAYMENT_WATA_TELEGRAM_LABEL_EN: Optional[str] = None
|
|
||||||
PAYMENT_WATA_TELEGRAM_EMOJI: Optional[str] = None
|
|
||||||
PAYMENT_YOOKASSA_WEBAPP_LABEL_RU: Optional[str] = None
|
PAYMENT_YOOKASSA_WEBAPP_LABEL_RU: Optional[str] = None
|
||||||
PAYMENT_YOOKASSA_WEBAPP_LABEL_EN: Optional[str] = None
|
PAYMENT_YOOKASSA_WEBAPP_LABEL_EN: Optional[str] = None
|
||||||
PAYMENT_YOOKASSA_WEBAPP_ICON: Optional[str] = None
|
PAYMENT_YOOKASSA_WEBAPP_ICON: Optional[str] = None
|
||||||
@@ -580,15 +546,6 @@ class Settings(BaseSettings):
|
|||||||
platega_crypto_method=self.PLATEGA_CRYPTO_METHOD,
|
platega_crypto_method=self.PLATEGA_CRYPTO_METHOD,
|
||||||
platega_return_url=self.PLATEGA_RETURN_URL,
|
platega_return_url=self.PLATEGA_RETURN_URL,
|
||||||
platega_failed_url=self.PLATEGA_FAILED_URL,
|
platega_failed_url=self.PLATEGA_FAILED_URL,
|
||||||
wata_enabled=self.WATA_ENABLED,
|
|
||||||
wata_api_token=self.WATA_API_TOKEN,
|
|
||||||
wata_base_url=self.WATA_BASE_URL,
|
|
||||||
wata_return_url=self.WATA_RETURN_URL,
|
|
||||||
wata_failed_url=self.WATA_FAILED_URL,
|
|
||||||
wata_payment_link_ttl_days=self.WATA_PAYMENT_LINK_TTL_DAYS,
|
|
||||||
wata_webhook_verify_signature=self.WATA_WEBHOOK_VERIFY_SIGNATURE,
|
|
||||||
wata_public_key=self.WATA_PUBLIC_KEY,
|
|
||||||
wata_trusted_ips=self.wata_trusted_ips,
|
|
||||||
cryptopay_enabled=self.CRYPTOPAY_ENABLED,
|
cryptopay_enabled=self.CRYPTOPAY_ENABLED,
|
||||||
cryptopay_token=self.CRYPTOPAY_TOKEN,
|
cryptopay_token=self.CRYPTOPAY_TOKEN,
|
||||||
cryptopay_network=self.CRYPTOPAY_NETWORK,
|
cryptopay_network=self.CRYPTOPAY_NETWORK,
|
||||||
@@ -763,24 +720,6 @@ class Settings(BaseSettings):
|
|||||||
return f"{base.rstrip('/')}{self.freekassa_webhook_path}"
|
return f"{base.rstrip('/')}{self.freekassa_webhook_path}"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@computed_field
|
|
||||||
@property
|
|
||||||
def wata_webhook_path(self) -> str:
|
|
||||||
return "/webhook/wata"
|
|
||||||
|
|
||||||
@computed_field
|
|
||||||
@property
|
|
||||||
def wata_full_webhook_url(self) -> Optional[str]:
|
|
||||||
base = self.WEBHOOK_BASE_URL
|
|
||||||
if base:
|
|
||||||
return f"{base.rstrip('/')}{self.wata_webhook_path}"
|
|
||||||
return None
|
|
||||||
|
|
||||||
@computed_field
|
|
||||||
@property
|
|
||||||
def wata_trusted_ips(self) -> List[str]:
|
|
||||||
return _split_csv(self.WATA_TRUSTED_IPS)
|
|
||||||
|
|
||||||
@computed_field
|
@computed_field
|
||||||
@property
|
@property
|
||||||
def platega_webhook_path(self) -> str:
|
def platega_webhook_path(self) -> str:
|
||||||
@@ -1123,10 +1062,6 @@ class Settings(BaseSettings):
|
|||||||
"REQUIRED_CHANNEL_LINK",
|
"REQUIRED_CHANNEL_LINK",
|
||||||
"PLATEGA_RETURN_URL",
|
"PLATEGA_RETURN_URL",
|
||||||
"PLATEGA_FAILED_URL",
|
"PLATEGA_FAILED_URL",
|
||||||
"WATA_RETURN_URL",
|
|
||||||
"WATA_FAILED_URL",
|
|
||||||
"WATA_API_TOKEN",
|
|
||||||
"WATA_PUBLIC_KEY",
|
|
||||||
"CRYPT4_REDIRECT_URL",
|
"CRYPT4_REDIRECT_URL",
|
||||||
"PRIVACY_POLICY_URL",
|
"PRIVACY_POLICY_URL",
|
||||||
"USER_AGREEMENT_URL",
|
"USER_AGREEMENT_URL",
|
||||||
@@ -1156,17 +1091,6 @@ class Settings(BaseSettings):
|
|||||||
return None
|
return None
|
||||||
return v
|
return v
|
||||||
|
|
||||||
@field_validator("WATA_PAYMENT_LINK_TTL_DAYS", mode="before")
|
|
||||||
@classmethod
|
|
||||||
def validate_wata_ttl_days(cls, v):
|
|
||||||
if isinstance(v, str):
|
|
||||||
v = v.strip()
|
|
||||||
try:
|
|
||||||
value = int(v)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return 3
|
|
||||||
return min(30, max(1, value))
|
|
||||||
|
|
||||||
# Notification types
|
# Notification types
|
||||||
LOG_NEW_USERS: bool = Field(
|
LOG_NEW_USERS: bool = Field(
|
||||||
default=True, description="Send notifications for new user registrations"
|
default=True, description="Send notifications for new user registrations"
|
||||||
@@ -1253,11 +1177,6 @@ def get_settings() -> Settings:
|
|||||||
logging.warning(
|
logging.warning(
|
||||||
"CRITICAL: Platega is enabled but merchant credentials (PLATEGA_MERCHANT_ID/PLATEGA_SECRET) are missing. Platega payments will not work." # noqa: E501
|
"CRITICAL: Platega is enabled but merchant credentials (PLATEGA_MERCHANT_ID/PLATEGA_SECRET) are missing. Platega payments will not work." # noqa: E501
|
||||||
)
|
)
|
||||||
if _settings_instance.WATA_ENABLED:
|
|
||||||
if not _settings_instance.WATA_API_TOKEN:
|
|
||||||
logging.warning(
|
|
||||||
"CRITICAL: Wata is enabled but WATA_API_TOKEN is missing. Wata payments will not work." # noqa: E501
|
|
||||||
)
|
|
||||||
|
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
logging.critical(f"Pydantic validation error while loading settings: {e}")
|
logging.critical(f"Pydantic validation error while loading settings: {e}")
|
||||||
|
|||||||
@@ -198,7 +198,14 @@ def test_provider_presentation_ignores_cross_language_override():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_payment_method_keyboard_uses_custom_telegram_text_without_changing_callback():
|
def test_payment_method_keyboard_uses_custom_telegram_text_without_changing_callback(monkeypatch):
|
||||||
|
from bot.payment_providers import build_provider_configs
|
||||||
|
|
||||||
|
monkeypatch.setenv("WATA_ENABLED", "True")
|
||||||
|
monkeypatch.setenv("PAYMENT_WATA_TELEGRAM_LABEL_EN", "Wata custom")
|
||||||
|
monkeypatch.setenv("PAYMENT_WATA_TELEGRAM_EMOJI", "💸")
|
||||||
|
build_provider_configs()
|
||||||
|
|
||||||
settings = Settings(
|
settings = Settings(
|
||||||
_env_file=None,
|
_env_file=None,
|
||||||
BOT_TOKEN="token",
|
BOT_TOKEN="token",
|
||||||
@@ -206,9 +213,6 @@ def test_payment_method_keyboard_uses_custom_telegram_text_without_changing_call
|
|||||||
POSTGRES_PASSWORD="app_password",
|
POSTGRES_PASSWORD="app_password",
|
||||||
TARIFFS_CONFIG_PATH="missing-tariffs.json",
|
TARIFFS_CONFIG_PATH="missing-tariffs.json",
|
||||||
PAYMENT_METHODS_ORDER="wata",
|
PAYMENT_METHODS_ORDER="wata",
|
||||||
WATA_ENABLED=True,
|
|
||||||
PAYMENT_WATA_TELEGRAM_LABEL_EN="Wata custom",
|
|
||||||
PAYMENT_WATA_TELEGRAM_EMOJI="💸",
|
|
||||||
)
|
)
|
||||||
i18n = SimpleNamespace(gettext=lambda _lang, key, **_kwargs: key)
|
i18n = SimpleNamespace(gettext=lambda _lang, key, **_kwargs: key)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user