refactor: payments providers
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable, Dict, Iterable, List, Mapping, Optional
|
||||
|
||||
from . import cryptopay, freekassa, platega, severpay, stars, wata, yookassa
|
||||
from .base import PaymentProviderPresentation, PaymentProviderSpec, ServiceFactoryContext
|
||||
|
||||
PAYMENT_PROVIDER_SPECS: tuple[PaymentProviderSpec, ...] = (
|
||||
freekassa.SPEC,
|
||||
platega.SBP_SPEC,
|
||||
platega.CRYPTO_SPEC,
|
||||
severpay.SPEC,
|
||||
wata.SPEC,
|
||||
yookassa.SPEC,
|
||||
stars.SPEC,
|
||||
cryptopay.SPEC,
|
||||
)
|
||||
|
||||
|
||||
def iter_provider_specs() -> Iterable[PaymentProviderSpec]:
|
||||
return PAYMENT_PROVIDER_SPECS
|
||||
|
||||
|
||||
def get_provider_spec(method: str) -> Optional[PaymentProviderSpec]:
|
||||
normalized = str(method or "").strip().lower()
|
||||
for spec in PAYMENT_PROVIDER_SPECS:
|
||||
if normalized in spec.method_ids:
|
||||
return spec
|
||||
return None
|
||||
|
||||
|
||||
def _setting_value(settings: Any, key: str) -> Optional[str]:
|
||||
if settings is None:
|
||||
return None
|
||||
value = getattr(settings, key, None)
|
||||
if value is None:
|
||||
return None
|
||||
value = str(value).strip()
|
||||
return value or None
|
||||
|
||||
|
||||
def _presentation_setting(spec: PaymentProviderSpec, suffix: str) -> str:
|
||||
return f"PAYMENT_{spec.settings_key}_{suffix}"
|
||||
|
||||
|
||||
def _normalize_language(language: Optional[str], settings: Any = None) -> str:
|
||||
value = language or getattr(settings, "DEFAULT_LANGUAGE", None) or "ru"
|
||||
normalized = str(value).strip().lower().split("-", 1)[0].split("_", 1)[0]
|
||||
return normalized or "ru"
|
||||
|
||||
|
||||
def _localized_setting_value(
|
||||
settings: Any,
|
||||
spec: PaymentProviderSpec,
|
||||
suffix: str,
|
||||
language: str,
|
||||
) -> Optional[str]:
|
||||
return _setting_value(
|
||||
settings,
|
||||
_presentation_setting(spec, f"{suffix}_{language.upper()}"),
|
||||
)
|
||||
|
||||
|
||||
def _localized_default(
|
||||
values: Optional[Mapping[str, str]],
|
||||
language: str,
|
||||
fallback: Optional[str],
|
||||
) -> Optional[str]:
|
||||
if not values:
|
||||
return fallback
|
||||
return (
|
||||
values.get(language)
|
||||
or values.get("en")
|
||||
or values.get("ru")
|
||||
or next(iter(values.values()), None)
|
||||
or fallback
|
||||
)
|
||||
|
||||
|
||||
def resolve_provider_presentation(
|
||||
spec: PaymentProviderSpec,
|
||||
settings: Any = None,
|
||||
*,
|
||||
language: Optional[str] = None,
|
||||
translate: Optional[Callable[[str], str]] = None,
|
||||
) -> PaymentProviderPresentation:
|
||||
lang = _normalize_language(language, settings)
|
||||
webapp_label = (
|
||||
_localized_setting_value(settings, spec, "WEBAPP_LABEL", lang)
|
||||
or _localized_default(spec.webapp_labels, lang, spec.webapp_label)
|
||||
or spec.label
|
||||
)
|
||||
webapp_icon = (
|
||||
_setting_value(settings, _presentation_setting(spec, "WEBAPP_ICON"))
|
||||
or spec.webapp_icon
|
||||
)
|
||||
telegram_label_override = _localized_setting_value(
|
||||
settings,
|
||||
spec,
|
||||
"TELEGRAM_LABEL",
|
||||
lang,
|
||||
)
|
||||
telegram_emoji_override = _setting_value(
|
||||
settings, _presentation_setting(spec, "TELEGRAM_EMOJI")
|
||||
)
|
||||
telegram_label = (
|
||||
telegram_label_override
|
||||
or _localized_default(spec.telegram_labels, lang, None)
|
||||
or spec.label
|
||||
)
|
||||
telegram_emoji = telegram_emoji_override or spec.default_telegram_emoji
|
||||
|
||||
if (
|
||||
not telegram_label_override
|
||||
and not spec.telegram_labels
|
||||
and translate
|
||||
and spec.button_text_key
|
||||
):
|
||||
telegram_label = translate(spec.button_text_key)
|
||||
|
||||
return PaymentProviderPresentation(
|
||||
webapp_label=webapp_label,
|
||||
webapp_icon=webapp_icon,
|
||||
telegram_label=telegram_label,
|
||||
telegram_emoji=telegram_emoji,
|
||||
telegram_customized=bool(telegram_label_override or telegram_emoji_override),
|
||||
)
|
||||
|
||||
|
||||
def provider_telegram_button_text(
|
||||
spec: PaymentProviderSpec,
|
||||
settings: Any,
|
||||
translate: Callable[[str], str],
|
||||
*,
|
||||
language: Optional[str] = None,
|
||||
) -> str:
|
||||
presentation = resolve_provider_presentation(spec, settings, language=language)
|
||||
if presentation.telegram_emoji:
|
||||
return f"{presentation.telegram_emoji} {presentation.telegram_label}".strip()
|
||||
return presentation.telegram_label
|
||||
|
||||
|
||||
def iter_unique_provider_routers():
|
||||
seen: set[int] = set()
|
||||
for spec in PAYMENT_PROVIDER_SPECS:
|
||||
router = spec.load_router()
|
||||
if not router:
|
||||
continue
|
||||
marker = id(router)
|
||||
if marker in seen:
|
||||
continue
|
||||
seen.add(marker)
|
||||
yield router
|
||||
|
||||
|
||||
def iter_service_keys() -> Iterable[str]:
|
||||
seen: set[str] = set()
|
||||
for spec in PAYMENT_PROVIDER_SPECS:
|
||||
if not spec.service_key or spec.service_key in seen:
|
||||
continue
|
||||
seen.add(spec.service_key)
|
||||
yield spec.service_key
|
||||
|
||||
|
||||
def iter_service_specs() -> Iterable[PaymentProviderSpec]:
|
||||
seen: set[str] = set()
|
||||
for spec in PAYMENT_PROVIDER_SPECS:
|
||||
if not spec.service_key or not spec.create_service or spec.service_key in seen:
|
||||
continue
|
||||
seen.add(spec.service_key)
|
||||
yield spec
|
||||
|
||||
|
||||
def build_provider_services(ctx: ServiceFactoryContext) -> Dict[str, Any]:
|
||||
services: Dict[str, Any] = {}
|
||||
for spec in iter_service_specs():
|
||||
services[spec.service_key] = spec.create_service(ctx)
|
||||
return services
|
||||
|
||||
|
||||
def provider_label_map(settings: Any = None, language: Optional[str] = None) -> Dict[str, str]:
|
||||
labels: Dict[str, str] = {}
|
||||
for spec in PAYMENT_PROVIDER_SPECS:
|
||||
presentation = resolve_provider_presentation(
|
||||
spec,
|
||||
settings,
|
||||
language=language,
|
||||
)
|
||||
label = presentation.telegram_label if presentation.telegram_customized else spec.label
|
||||
labels.setdefault(spec.provider_key, label)
|
||||
for method in spec.method_ids:
|
||||
labels.setdefault(method, label)
|
||||
return labels
|
||||
|
||||
|
||||
def provider_emoji_map(settings: Any = None) -> Dict[str, str]:
|
||||
emojis: Dict[str, str] = {}
|
||||
for spec in PAYMENT_PROVIDER_SPECS:
|
||||
emoji = resolve_provider_presentation(spec, settings).telegram_emoji
|
||||
emojis.setdefault(spec.provider_key, emoji)
|
||||
for method in spec.method_ids:
|
||||
emojis.setdefault(method, emoji)
|
||||
return emojis
|
||||
|
||||
|
||||
def pending_statuses() -> List[str]:
|
||||
statuses = ["pending"]
|
||||
for spec in PAYMENT_PROVIDER_SPECS:
|
||||
if spec.pending_status not in statuses:
|
||||
statuses.append(spec.pending_status)
|
||||
return statuses
|
||||
Reference in New Issue
Block a user