feat: show optional serivce description before payment

This commit is contained in:
3252a8
2026-05-20 21:55:22 +03:00
parent d8a1da1f13
commit 7521f89ffd
51 changed files with 1199 additions and 499 deletions
+1 -1
View File
@@ -18,9 +18,9 @@ from .registry import (
get_spec_presentation,
iter_provider_manifest_fields,
iter_provider_specs,
manifest_field_default,
iter_service_keys,
iter_unique_provider_routers,
manifest_field_default,
pending_statuses,
provider_emoji_map,
provider_label_map,
+4 -2
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
import os
from dataclasses import dataclass, field
from typing import Any, Awaitable, Callable, List, Mapping, Optional, Sequence, Type
from typing import Any, Awaitable, Callable, Mapping, Optional, Sequence, Type
from pydantic_settings import BaseSettings, SettingsConfigDict
@@ -65,7 +65,9 @@ class ProviderManifestField:
choices: Optional[Sequence[tuple[str, str]]] = None
subsection: Optional[str] = None
target: str = "config" # "config" or "presentation" — which bundle slot it writes to
attr: Optional[str] = None # attribute name on the target model; defaults to key without env_prefix
attr: Optional[str] = (
None # attribute name on the target model; defaults to key without env_prefix
)
@dataclass(frozen=True)
+93 -33
View File
@@ -349,10 +349,7 @@ async def pay_crypto_callback_handler(
await notify_callback_parse_error(callback, translator)
return
if (
not cryptopay_service
or not getattr(cryptopay_service, "configured", False)
):
if not cryptopay_service or not getattr(cryptopay_service, "configured", False):
await notify_service_unavailable(callback, translator)
return
@@ -390,7 +387,11 @@ async def pay_crypto_callback_handler(
def create_service(ctx: ServiceFactoryContext) -> CryptoPayService:
bundle = ctx.config_for("cryptopay_service")
config = bundle.config if bundle and isinstance(bundle.config, CryptoPayConfig) else CryptoPayConfig()
config = (
bundle.config
if bundle and isinstance(bundle.config, CryptoPayConfig)
else CryptoPayConfig()
)
return CryptoPayService(
bot=ctx.bot,
settings=ctx.settings,
@@ -422,40 +423,99 @@ async def create_webapp_payment(ctx: WebAppPaymentContext) -> web.Response:
_PRESENTATION_MANIFEST = tuple(
ProviderManifestField(
key=key, type=type_, label=label, description=description,
placeholder=placeholder, subsection="CryptoPay",
target="presentation", attr=attr,
key=key,
type=type_,
label=label,
description=description,
placeholder=placeholder,
subsection="CryptoPay",
target="presentation",
attr=attr,
)
for key, type_, label, description, placeholder, attr in (
("PAYMENT_CRYPTOPAY_WEBAPP_LABEL_RU", "string", "WebApp button text (RU)",
"Custom Russian text shown in the Web App payment method button.", "", "WEBAPP_LABEL_RU"),
("PAYMENT_CRYPTOPAY_WEBAPP_LABEL_EN", "string", "WebApp button text (EN)",
"Custom English text shown in the Web App payment method button.", "", "WEBAPP_LABEL_EN"),
("PAYMENT_CRYPTOPAY_WEBAPP_ICON", "icon", "WebApp button icon",
"Lucide icon name rendered inside the Web App payment method button.",
"Bitcoin", "WEBAPP_ICON"),
("PAYMENT_CRYPTOPAY_TELEGRAM_LABEL_RU", "string", "Telegram button text (RU)",
"Custom Russian text shown in Telegram bot payment buttons.", "", "TELEGRAM_LABEL_RU"),
("PAYMENT_CRYPTOPAY_TELEGRAM_LABEL_EN", "string", "Telegram button text (EN)",
"Custom English text shown in Telegram bot payment buttons.", "", "TELEGRAM_LABEL_EN"),
("PAYMENT_CRYPTOPAY_TELEGRAM_EMOJI", "string", "Telegram button emoji",
"Emoji prepended to the Telegram bot payment button when customized.",
"", "TELEGRAM_EMOJI"),
(
"PAYMENT_CRYPTOPAY_WEBAPP_LABEL_RU",
"string",
"WebApp button text (RU)",
"Custom Russian text shown in the Web App payment method button.",
"",
"WEBAPP_LABEL_RU",
),
(
"PAYMENT_CRYPTOPAY_WEBAPP_LABEL_EN",
"string",
"WebApp button text (EN)",
"Custom English text shown in the Web App payment method button.",
"",
"WEBAPP_LABEL_EN",
),
(
"PAYMENT_CRYPTOPAY_WEBAPP_ICON",
"icon",
"WebApp button icon",
"Lucide icon name rendered inside the Web App payment method button.",
"Bitcoin",
"WEBAPP_ICON",
),
(
"PAYMENT_CRYPTOPAY_TELEGRAM_LABEL_RU",
"string",
"Telegram button text (RU)",
"Custom Russian text shown in Telegram bot payment buttons.",
"",
"TELEGRAM_LABEL_RU",
),
(
"PAYMENT_CRYPTOPAY_TELEGRAM_LABEL_EN",
"string",
"Telegram button text (EN)",
"Custom English text shown in Telegram bot payment buttons.",
"",
"TELEGRAM_LABEL_EN",
),
(
"PAYMENT_CRYPTOPAY_TELEGRAM_EMOJI",
"string",
"Telegram button emoji",
"Emoji prepended to the Telegram bot payment button when customized.",
"",
"TELEGRAM_EMOJI",
),
)
)
_CONFIG_MANIFEST = (
ProviderManifestField("CRYPTOPAY_ENABLED", "bool", "Включена",
subsection="CryptoPay", attr="ENABLED"),
ProviderManifestField("CRYPTOPAY_TOKEN", "string", "Token",
subsection="CryptoPay", secret=True, attr="TOKEN"),
ProviderManifestField("CRYPTOPAY_NETWORK", "string", "Network",
placeholder="mainnet", subsection="CryptoPay", attr="NETWORK"),
ProviderManifestField("CRYPTOPAY_CURRENCY_TYPE", "string", "Currency type",
description="fiat or crypto.",
placeholder="fiat", subsection="CryptoPay", attr="CURRENCY_TYPE"),
ProviderManifestField("CRYPTOPAY_ASSET", "string", "Asset",
placeholder="RUB", subsection="CryptoPay", attr="ASSET"),
ProviderManifestField(
"CRYPTOPAY_ENABLED", "bool", "Включена", subsection="CryptoPay", attr="ENABLED"
),
ProviderManifestField(
"CRYPTOPAY_TOKEN", "string", "Token", subsection="CryptoPay", secret=True, attr="TOKEN"
),
ProviderManifestField(
"CRYPTOPAY_NETWORK",
"string",
"Network",
placeholder="mainnet",
subsection="CryptoPay",
attr="NETWORK",
),
ProviderManifestField(
"CRYPTOPAY_CURRENCY_TYPE",
"string",
"Currency type",
description="fiat or crypto.",
placeholder="fiat",
subsection="CryptoPay",
attr="CURRENCY_TYPE",
),
ProviderManifestField(
"CRYPTOPAY_ASSET",
"string",
"Asset",
placeholder="RUB",
subsection="CryptoPay",
attr="ASSET",
),
)
+133 -46
View File
@@ -71,9 +71,7 @@ class FreeKassaConfig(ProviderEnvConfig):
API_KEY: Optional[str] = None
PAYMENT_IP: Optional[str] = None
PAYMENT_METHOD_ID: Optional[int] = None
TRUSTED_IPS: str = Field(
default="168.119.157.136,168.119.60.227,178.154.197.79,51.250.54.238"
)
TRUSTED_IPS: str = Field(default="168.119.157.136,168.119.60.227,178.154.197.79,51.250.54.238")
@field_validator("PAYMENT_METHOD_ID", mode="before")
@classmethod
@@ -85,7 +83,11 @@ class FreeKassaConfig(ProviderEnvConfig):
return v
@field_validator(
"MERCHANT_ID", "FIRST_SECRET", "SECOND_SECRET", "API_KEY", "PAYMENT_IP",
"MERCHANT_ID",
"FIRST_SECRET",
"SECOND_SECRET",
"API_KEY",
"PAYMENT_IP",
mode="before",
)
@classmethod
@@ -505,8 +507,10 @@ async def pay_fk_callback_handler(
provider_identifier = first_value(response_data, "orderHash", "orderId")
lead_text: Optional[str] = None
if success and location:
order_id_display = first_value(response_data, "orderId") or provider_identifier or str(
payment_record.payment_id
order_id_display = (
first_value(response_data, "orderId")
or provider_identifier
or str(payment_record.payment_id)
)
lead_text = translator(
"free_kassa_order_info",
@@ -532,7 +536,11 @@ async def pay_fk_callback_handler(
def create_service(ctx: ServiceFactoryContext) -> FreeKassaService:
bundle = ctx.config_for("freekassa_service")
config = bundle.config if bundle and isinstance(bundle.config, FreeKassaConfig) else FreeKassaConfig()
config = (
bundle.config
if bundle and isinstance(bundle.config, FreeKassaConfig)
else FreeKassaConfig()
)
return FreeKassaService(
bot=ctx.bot,
settings=ctx.settings,
@@ -584,51 +592,130 @@ async def create_webapp_payment(ctx: WebAppPaymentContext) -> web.Response:
_PRESENTATION_MANIFEST = tuple(
ProviderManifestField(
key=key, type=type_, label=label, description=description,
placeholder=placeholder, subsection="FreeKassa",
target="presentation", attr=attr,
key=key,
type=type_,
label=label,
description=description,
placeholder=placeholder,
subsection="FreeKassa",
target="presentation",
attr=attr,
)
for key, type_, label, description, placeholder, attr in (
("PAYMENT_FREEKASSA_WEBAPP_LABEL_RU", "string", "WebApp button text (RU)",
"Custom Russian text shown in the Web App payment method button.", "", "WEBAPP_LABEL_RU"),
("PAYMENT_FREEKASSA_WEBAPP_LABEL_EN", "string", "WebApp button text (EN)",
"Custom English text shown in the Web App payment method button.", "", "WEBAPP_LABEL_EN"),
("PAYMENT_FREEKASSA_WEBAPP_ICON", "icon", "WebApp button icon",
"Lucide icon name rendered inside the Web App payment method button.",
"Smartphone", "WEBAPP_ICON"),
("PAYMENT_FREEKASSA_TELEGRAM_LABEL_RU", "string", "Telegram button text (RU)",
"Custom Russian text shown in Telegram bot payment buttons.", "", "TELEGRAM_LABEL_RU"),
("PAYMENT_FREEKASSA_TELEGRAM_LABEL_EN", "string", "Telegram button text (EN)",
"Custom English text shown in Telegram bot payment buttons.", "", "TELEGRAM_LABEL_EN"),
("PAYMENT_FREEKASSA_TELEGRAM_EMOJI", "string", "Telegram button emoji",
"Emoji prepended to the Telegram bot payment button when customized.",
"📱", "TELEGRAM_EMOJI"),
(
"PAYMENT_FREEKASSA_WEBAPP_LABEL_RU",
"string",
"WebApp button text (RU)",
"Custom Russian text shown in the Web App payment method button.",
"",
"WEBAPP_LABEL_RU",
),
(
"PAYMENT_FREEKASSA_WEBAPP_LABEL_EN",
"string",
"WebApp button text (EN)",
"Custom English text shown in the Web App payment method button.",
"",
"WEBAPP_LABEL_EN",
),
(
"PAYMENT_FREEKASSA_WEBAPP_ICON",
"icon",
"WebApp button icon",
"Lucide icon name rendered inside the Web App payment method button.",
"Smartphone",
"WEBAPP_ICON",
),
(
"PAYMENT_FREEKASSA_TELEGRAM_LABEL_RU",
"string",
"Telegram button text (RU)",
"Custom Russian text shown in Telegram bot payment buttons.",
"",
"TELEGRAM_LABEL_RU",
),
(
"PAYMENT_FREEKASSA_TELEGRAM_LABEL_EN",
"string",
"Telegram button text (EN)",
"Custom English text shown in Telegram bot payment buttons.",
"",
"TELEGRAM_LABEL_EN",
),
(
"PAYMENT_FREEKASSA_TELEGRAM_EMOJI",
"string",
"Telegram button emoji",
"Emoji prepended to the Telegram bot payment button when customized.",
"📱",
"TELEGRAM_EMOJI",
),
)
)
_CONFIG_MANIFEST = (
ProviderManifestField("FREEKASSA_ENABLED", "bool", "Включена",
subsection="FreeKassa", attr="ENABLED"),
ProviderManifestField("FREEKASSA_MERCHANT_ID", "string", "Merchant ID",
subsection="FreeKassa", attr="MERCHANT_ID"),
ProviderManifestField("FREEKASSA_FIRST_SECRET", "string", "First secret",
subsection="FreeKassa", secret=True, attr="FIRST_SECRET"),
ProviderManifestField("FREEKASSA_SECOND_SECRET", "string", "Second secret",
subsection="FreeKassa", secret=True, attr="SECOND_SECRET"),
ProviderManifestField("FREEKASSA_API_KEY", "string", "API key",
subsection="FreeKassa", secret=True, attr="API_KEY"),
ProviderManifestField("FREEKASSA_PAYMENT_URL", "url", "Payment URL",
placeholder="https://pay.freekassa.ru/",
subsection="FreeKassa", attr="PAYMENT_URL"),
ProviderManifestField("FREEKASSA_PAYMENT_METHOD_ID", "int", "Payment method ID",
description="See https://merchant.freekassa.net/settings/currencies",
subsection="FreeKassa", attr="PAYMENT_METHOD_ID"),
ProviderManifestField("FREEKASSA_PAYMENT_IP", "string", "Server IP",
description="Public IP address reported to FreeKassa.",
subsection="FreeKassa", attr="PAYMENT_IP"),
ProviderManifestField("FREEKASSA_TRUSTED_IPS", "string", "Trusted IPs",
description="Comma-separated IP addresses accepted for FreeKassa webhooks.",
subsection="FreeKassa", attr="TRUSTED_IPS"),
ProviderManifestField(
"FREEKASSA_ENABLED", "bool", "Включена", subsection="FreeKassa", attr="ENABLED"
),
ProviderManifestField(
"FREEKASSA_MERCHANT_ID", "string", "Merchant ID", subsection="FreeKassa", attr="MERCHANT_ID"
),
ProviderManifestField(
"FREEKASSA_FIRST_SECRET",
"string",
"First secret",
subsection="FreeKassa",
secret=True,
attr="FIRST_SECRET",
),
ProviderManifestField(
"FREEKASSA_SECOND_SECRET",
"string",
"Second secret",
subsection="FreeKassa",
secret=True,
attr="SECOND_SECRET",
),
ProviderManifestField(
"FREEKASSA_API_KEY",
"string",
"API key",
subsection="FreeKassa",
secret=True,
attr="API_KEY",
),
ProviderManifestField(
"FREEKASSA_PAYMENT_URL",
"url",
"Payment URL",
placeholder="https://pay.freekassa.ru/",
subsection="FreeKassa",
attr="PAYMENT_URL",
),
ProviderManifestField(
"FREEKASSA_PAYMENT_METHOD_ID",
"int",
"Payment method ID",
description="See https://merchant.freekassa.net/settings/currencies",
subsection="FreeKassa",
attr="PAYMENT_METHOD_ID",
),
ProviderManifestField(
"FREEKASSA_PAYMENT_IP",
"string",
"Server IP",
description="Public IP address reported to FreeKassa.",
subsection="FreeKassa",
attr="PAYMENT_IP",
),
ProviderManifestField(
"FREEKASSA_TRUSTED_IPS",
"string",
"Trusted IPs",
description="Comma-separated IP addresses accepted for FreeKassa webhooks.",
subsection="FreeKassa",
attr="TRUSTED_IPS",
),
)
+140 -42
View File
@@ -93,7 +93,12 @@ class HeleketConfig(ProviderEnvConfig):
return min(43200, max(300, value))
@field_validator(
"MERCHANT_ID", "API_KEY", "TO_CURRENCY", "NETWORK", "RETURN_URL", "SUCCESS_URL",
"MERCHANT_ID",
"API_KEY",
"TO_CURRENCY",
"NETWORK",
"RETURN_URL",
"SUCCESS_URL",
mode="before",
)
@classmethod
@@ -644,7 +649,9 @@ async def heleket_webhook_route(request: web.Request) -> web.Response:
def create_service(ctx: ServiceFactoryContext) -> HeleketService:
bundle = ctx.config_for("heleket_service")
config = bundle.config if bundle and isinstance(bundle.config, HeleketConfig) else HeleketConfig()
config = (
bundle.config if bundle and isinstance(bundle.config, HeleketConfig) else HeleketConfig()
)
return HeleketService(
bot=ctx.bot,
settings=ctx.settings,
@@ -669,51 +676,142 @@ _PRESENTATION_MANIFEST = tuple(
attr=attr,
)
for key, type_, label, description, placeholder, attr in (
("PAYMENT_HELEKET_WEBAPP_LABEL_RU", "string", "WebApp button text (RU)",
"Custom Russian text shown in the Web App payment method button.", "", "WEBAPP_LABEL_RU"),
("PAYMENT_HELEKET_WEBAPP_LABEL_EN", "string", "WebApp button text (EN)",
"Custom English text shown in the Web App payment method button.", "", "WEBAPP_LABEL_EN"),
("PAYMENT_HELEKET_WEBAPP_ICON", "icon", "WebApp button icon",
"Lucide icon name rendered inside the Web App payment method button.", "Bitcoin", "WEBAPP_ICON"),
("PAYMENT_HELEKET_TELEGRAM_LABEL_RU", "string", "Telegram button text (RU)",
"Custom Russian text shown in Telegram bot payment buttons.", "", "TELEGRAM_LABEL_RU"),
("PAYMENT_HELEKET_TELEGRAM_LABEL_EN", "string", "Telegram button text (EN)",
"Custom English text shown in Telegram bot payment buttons.", "", "TELEGRAM_LABEL_EN"),
("PAYMENT_HELEKET_TELEGRAM_EMOJI", "string", "Telegram button emoji",
"Emoji prepended to the Telegram bot payment button when customized.", "🪙", "TELEGRAM_EMOJI"),
(
"PAYMENT_HELEKET_WEBAPP_LABEL_RU",
"string",
"WebApp button text (RU)",
"Custom Russian text shown in the Web App payment method button.",
"",
"WEBAPP_LABEL_RU",
),
(
"PAYMENT_HELEKET_WEBAPP_LABEL_EN",
"string",
"WebApp button text (EN)",
"Custom English text shown in the Web App payment method button.",
"",
"WEBAPP_LABEL_EN",
),
(
"PAYMENT_HELEKET_WEBAPP_ICON",
"icon",
"WebApp button icon",
"Lucide icon name rendered inside the Web App payment method button.",
"Bitcoin",
"WEBAPP_ICON",
),
(
"PAYMENT_HELEKET_TELEGRAM_LABEL_RU",
"string",
"Telegram button text (RU)",
"Custom Russian text shown in Telegram bot payment buttons.",
"",
"TELEGRAM_LABEL_RU",
),
(
"PAYMENT_HELEKET_TELEGRAM_LABEL_EN",
"string",
"Telegram button text (EN)",
"Custom English text shown in Telegram bot payment buttons.",
"",
"TELEGRAM_LABEL_EN",
),
(
"PAYMENT_HELEKET_TELEGRAM_EMOJI",
"string",
"Telegram button emoji",
"Emoji prepended to the Telegram bot payment button when customized.",
"🪙",
"TELEGRAM_EMOJI",
),
)
)
_CONFIG_MANIFEST = (
ProviderManifestField("HELEKET_ENABLED", "bool", "Enabled", subsection="Heleket", attr="ENABLED"),
ProviderManifestField("HELEKET_MERCHANT_ID", "string", "Merchant ID", subsection="Heleket",
secret=True, attr="MERCHANT_ID"),
ProviderManifestField("HELEKET_API_KEY", "string", "Payment API key", subsection="Heleket",
secret=True, attr="API_KEY"),
ProviderManifestField("HELEKET_BASE_URL", "url", "Base URL",
placeholder="https://api.heleket.com", subsection="Heleket", attr="BASE_URL"),
ProviderManifestField("HELEKET_CURRENCY", "string", "Invoice currency",
description="Fiat or crypto code (RUB, USD, USDT).",
placeholder="RUB", subsection="Heleket", attr="CURRENCY"),
ProviderManifestField("HELEKET_TO_CURRENCY", "string", "Target crypto",
description="Optional target cryptocurrency for conversion.",
subsection="Heleket", attr="TO_CURRENCY"),
ProviderManifestField("HELEKET_NETWORK", "string", "Blockchain network",
description="Optional blockchain network code (tron, bsc, eth).",
subsection="Heleket", attr="NETWORK"),
ProviderManifestField("HELEKET_RETURN_URL", "url", "Return URL", subsection="Heleket",
attr="RETURN_URL"),
ProviderManifestField("HELEKET_SUCCESS_URL", "url", "Success URL", subsection="Heleket",
attr="SUCCESS_URL"),
ProviderManifestField("HELEKET_LIFETIME_SECONDS", "int", "Invoice lifetime (seconds)",
description="300..43200; Heleket defaults to 3600.",
subsection="Heleket", min=300, max=43200, attr="LIFETIME_SECONDS"),
ProviderManifestField("HELEKET_VERIFY_WEBHOOK_SIGNATURE", "bool", "Verify webhook signature",
subsection="Heleket", attr="VERIFY_WEBHOOK_SIGNATURE"),
ProviderManifestField("HELEKET_TRUSTED_IPS", "string", "Trusted IPs",
description="Comma-separated IP addresses accepted for Heleket webhooks.",
subsection="Heleket", attr="TRUSTED_IPS"),
ProviderManifestField(
"HELEKET_ENABLED", "bool", "Enabled", subsection="Heleket", attr="ENABLED"
),
ProviderManifestField(
"HELEKET_MERCHANT_ID",
"string",
"Merchant ID",
subsection="Heleket",
secret=True,
attr="MERCHANT_ID",
),
ProviderManifestField(
"HELEKET_API_KEY",
"string",
"Payment API key",
subsection="Heleket",
secret=True,
attr="API_KEY",
),
ProviderManifestField(
"HELEKET_BASE_URL",
"url",
"Base URL",
placeholder="https://api.heleket.com",
subsection="Heleket",
attr="BASE_URL",
),
ProviderManifestField(
"HELEKET_CURRENCY",
"string",
"Invoice currency",
description="Fiat or crypto code (RUB, USD, USDT).",
placeholder="RUB",
subsection="Heleket",
attr="CURRENCY",
),
ProviderManifestField(
"HELEKET_TO_CURRENCY",
"string",
"Target crypto",
description="Optional target cryptocurrency for conversion.",
subsection="Heleket",
attr="TO_CURRENCY",
),
ProviderManifestField(
"HELEKET_NETWORK",
"string",
"Blockchain network",
description="Optional blockchain network code (tron, bsc, eth).",
subsection="Heleket",
attr="NETWORK",
),
ProviderManifestField(
"HELEKET_RETURN_URL", "url", "Return URL", subsection="Heleket", attr="RETURN_URL"
),
ProviderManifestField(
"HELEKET_SUCCESS_URL", "url", "Success URL", subsection="Heleket", attr="SUCCESS_URL"
),
ProviderManifestField(
"HELEKET_LIFETIME_SECONDS",
"int",
"Invoice lifetime (seconds)",
description="300..43200; Heleket defaults to 3600.",
subsection="Heleket",
min=300,
max=43200,
attr="LIFETIME_SECONDS",
),
ProviderManifestField(
"HELEKET_VERIFY_WEBHOOK_SIGNATURE",
"bool",
"Verify webhook signature",
subsection="Heleket",
attr="VERIFY_WEBHOOK_SIGNATURE",
),
ProviderManifestField(
"HELEKET_TRUSTED_IPS",
"string",
"Trusted IPs",
description="Comma-separated IP addresses accepted for Heleket webhooks.",
subsection="Heleket",
attr="TRUSTED_IPS",
),
)
+115 -51
View File
@@ -429,7 +429,11 @@ async def pay_platega_callback_handler(
return
callback_prefix, _, _ = (callback.data or "").partition(":")
variant = _resolve_platega_variant(callback_prefix, platega_service.config) if platega_service else None
variant = (
_resolve_platega_variant(callback_prefix, platega_service.config)
if platega_service
else None
)
if variant is None:
await safe_callback_answer(callback)
return
@@ -513,7 +517,9 @@ 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()
config = (
bundle.config if bundle and isinstance(bundle.config, PlategaConfig) else PlategaConfig()
)
return PlategaService(
bot=ctx.bot,
settings=ctx.settings,
@@ -613,52 +619,109 @@ def _platega_presentation_manifest(subsection: str, default_icon: str, prefix: s
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"),
(
"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"),
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"
),
)
@@ -672,7 +735,9 @@ SBP_SPEC = PaymentProviderSpec(
telegram_labels={"ru": "Оплата через СБП", "en": "Pay via SBP"},
telegram_emoji="🏦",
pending_status="pending_platega",
enabled=lambda config: bool(getattr(config, "ENABLED", False) and getattr(config, "SBP_ENABLED", False)),
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",),
@@ -683,9 +748,8 @@ SBP_SPEC = PaymentProviderSpec(
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"
),
manifest_fields=_CONFIG_MANIFEST
+ _platega_presentation_manifest("Platega SBP", "CreditCard", "PLATEGA_SBP"),
)
CRYPTO_SPEC = PaymentProviderSpec(
@@ -700,15 +764,15 @@ CRYPTO_SPEC = PaymentProviderSpec(
pending_status="pending_platega",
# 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)),
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"
),
manifest_fields=_platega_presentation_manifest("Platega Crypto", "Bitcoin", "PLATEGA_CRYPTO"),
)
SPECS = (SBP_SPEC, CRYPTO_SPEC)
+3 -12
View File
@@ -211,10 +211,7 @@ def resolve_provider_presentation(
or _localized_default(spec.webapp_labels, lang, spec.webapp_label)
or spec.label
)
webapp_icon = (
_bare_setting_value(settings, spec, "WEBAPP_ICON")
or spec.webapp_icon
)
webapp_icon = _bare_setting_value(settings, spec, "WEBAPP_ICON") or spec.webapp_icon
telegram_label_override = _localized_setting_value(
settings,
spec,
@@ -351,15 +348,9 @@ def manifest_field_default(
return None
attr = manifest_field.attr or manifest_field.key
if attr == "WEBAPP_LABEL_RU":
return (
_localized_default(spec.webapp_labels, "ru", spec.webapp_label)
or spec.label
)
return _localized_default(spec.webapp_labels, "ru", spec.webapp_label) or spec.label
if attr == "WEBAPP_LABEL_EN":
return (
_localized_default(spec.webapp_labels, "en", spec.webapp_label)
or spec.label
)
return _localized_default(spec.webapp_labels, "en", spec.webapp_label) or spec.label
if attr == "WEBAPP_ICON":
return spec.webapp_icon
if attr == "TELEGRAM_LABEL_RU":
+86 -30
View File
@@ -453,7 +453,9 @@ async def pay_severpay_callback_handler(
def create_service(ctx: ServiceFactoryContext) -> SeverPayService:
bundle = ctx.config_for("severpay_service")
config = bundle.config if bundle and isinstance(bundle.config, SeverPayConfig) else SeverPayConfig()
config = (
bundle.config if bundle and isinstance(bundle.config, SeverPayConfig) else SeverPayConfig()
)
return SeverPayService(
bot=ctx.bot,
settings=ctx.settings,
@@ -506,42 +508,96 @@ async def create_webapp_payment(ctx: WebAppPaymentContext) -> web.Response:
_PRESENTATION_MANIFEST = tuple(
ProviderManifestField(
key=key, type=type_, label=label, description=description,
placeholder=placeholder, subsection="SeverPay",
target="presentation", attr=attr,
key=key,
type=type_,
label=label,
description=description,
placeholder=placeholder,
subsection="SeverPay",
target="presentation",
attr=attr,
)
for key, type_, label, description, placeholder, attr in (
("PAYMENT_SEVERPAY_WEBAPP_LABEL_RU", "string", "WebApp button text (RU)",
"Custom Russian text shown in the Web App payment method button.", "", "WEBAPP_LABEL_RU"),
("PAYMENT_SEVERPAY_WEBAPP_LABEL_EN", "string", "WebApp button text (EN)",
"Custom English text shown in the Web App payment method button.", "", "WEBAPP_LABEL_EN"),
("PAYMENT_SEVERPAY_WEBAPP_ICON", "icon", "WebApp button icon",
"Lucide icon name rendered inside the Web App payment method button.",
"CreditCard", "WEBAPP_ICON"),
("PAYMENT_SEVERPAY_TELEGRAM_LABEL_RU", "string", "Telegram button text (RU)",
"Custom Russian text shown in Telegram bot payment buttons.", "", "TELEGRAM_LABEL_RU"),
("PAYMENT_SEVERPAY_TELEGRAM_LABEL_EN", "string", "Telegram button text (EN)",
"Custom English text shown in Telegram bot payment buttons.", "", "TELEGRAM_LABEL_EN"),
("PAYMENT_SEVERPAY_TELEGRAM_EMOJI", "string", "Telegram button emoji",
"Emoji prepended to the Telegram bot payment button when customized.",
"💳", "TELEGRAM_EMOJI"),
(
"PAYMENT_SEVERPAY_WEBAPP_LABEL_RU",
"string",
"WebApp button text (RU)",
"Custom Russian text shown in the Web App payment method button.",
"",
"WEBAPP_LABEL_RU",
),
(
"PAYMENT_SEVERPAY_WEBAPP_LABEL_EN",
"string",
"WebApp button text (EN)",
"Custom English text shown in the Web App payment method button.",
"",
"WEBAPP_LABEL_EN",
),
(
"PAYMENT_SEVERPAY_WEBAPP_ICON",
"icon",
"WebApp button icon",
"Lucide icon name rendered inside the Web App payment method button.",
"CreditCard",
"WEBAPP_ICON",
),
(
"PAYMENT_SEVERPAY_TELEGRAM_LABEL_RU",
"string",
"Telegram button text (RU)",
"Custom Russian text shown in Telegram bot payment buttons.",
"",
"TELEGRAM_LABEL_RU",
),
(
"PAYMENT_SEVERPAY_TELEGRAM_LABEL_EN",
"string",
"Telegram button text (EN)",
"Custom English text shown in Telegram bot payment buttons.",
"",
"TELEGRAM_LABEL_EN",
),
(
"PAYMENT_SEVERPAY_TELEGRAM_EMOJI",
"string",
"Telegram button emoji",
"Emoji prepended to the Telegram bot payment button when customized.",
"💳",
"TELEGRAM_EMOJI",
),
)
)
_CONFIG_MANIFEST = (
ProviderManifestField("SEVERPAY_ENABLED", "bool", "Включена",
subsection="SeverPay", attr="ENABLED"),
ProviderManifestField(
"SEVERPAY_ENABLED", "bool", "Включена", subsection="SeverPay", attr="ENABLED"
),
ProviderManifestField("SEVERPAY_MID", "int", "MID", subsection="SeverPay", attr="MID"),
ProviderManifestField("SEVERPAY_TOKEN", "string", "Token", subsection="SeverPay",
secret=True, attr="TOKEN"),
ProviderManifestField("SEVERPAY_BASE_URL", "url", "Base URL",
placeholder="https://severpay.io/api/merchant",
subsection="SeverPay", attr="BASE_URL"),
ProviderManifestField("SEVERPAY_RETURN_URL", "url", "Return URL",
subsection="SeverPay", attr="RETURN_URL"),
ProviderManifestField("SEVERPAY_LIFETIME_MINUTES", "int", "Payment link lifetime (minutes)",
description="30..4320; leave empty for the SeverPay default.",
subsection="SeverPay", min=30, max=4320, attr="LIFETIME_MINUTES"),
ProviderManifestField(
"SEVERPAY_TOKEN", "string", "Token", subsection="SeverPay", secret=True, attr="TOKEN"
),
ProviderManifestField(
"SEVERPAY_BASE_URL",
"url",
"Base URL",
placeholder="https://severpay.io/api/merchant",
subsection="SeverPay",
attr="BASE_URL",
),
ProviderManifestField(
"SEVERPAY_RETURN_URL", "url", "Return URL", subsection="SeverPay", attr="RETURN_URL"
),
ProviderManifestField(
"SEVERPAY_LIFETIME_MINUTES",
"int",
"Payment link lifetime (minutes)",
description="30..4320; leave empty for the SeverPay default.",
subsection="SeverPay",
min=30,
max=4320,
attr="LIFETIME_MINUTES",
),
)
@@ -125,11 +125,7 @@ def payment_link_message_text(
"topup",
"premium_topup",
}
key = (
"payment_link_message_traffic"
if traffic_like
else "payment_link_message"
)
key = "payment_link_message_traffic" if traffic_like else "payment_link_message"
body = translator(
key,
months=int(parts.months),
@@ -35,9 +35,7 @@ async def resolve_user_language(
if db_user is None:
db_user = await user_dal.get_user_by_id(session, user_id)
language = (
db_user.language_code
if db_user and db_user.language_code
else settings.DEFAULT_LANGUAGE
db_user.language_code if db_user and db_user.language_code else settings.DEFAULT_LANGUAGE
)
return db_user, language
@@ -256,9 +254,7 @@ async def finalize_successful_payment(
activation_months = (
int(float(req.months)) if is_subscription else int(float(req.traffic_amount or req.months))
)
traffic_gb_for_activation = (
float(req.traffic_amount or req.months) if is_traffic else None
)
traffic_gb_for_activation = float(req.traffic_amount or req.months) if is_traffic else None
try:
activation = await req.subscription_service.activate_subscription(
@@ -308,9 +304,7 @@ async def finalize_successful_payment(
base_end_date = activation.get("end_date") if activation else None
final_end_date = base_end_date
applied_referee_bonus_days = 0
applied_promo_bonus_days = (
activation.get("applied_promo_bonus_days", 0) if activation else 0
)
applied_promo_bonus_days = activation.get("applied_promo_bonus_days", 0) if activation else 0
inviter_name: Optional[str] = None
if referral_bonus and referral_bonus.get("referee_new_end_date"):
@@ -351,9 +345,7 @@ async def finalize_successful_payment(
log_prefix=req.log_prefix,
)
refreshed_payment = await payment_dal.get_payment_by_db_id(
req.session, req.payment.payment_id
)
refreshed_payment = await payment_dal.get_payment_by_db_id(req.session, req.payment.payment_id)
tariff_key = getattr(refreshed_payment or req.payment, "tariff_key", None)
await notify_admins_payment_received(
@@ -36,9 +36,7 @@ async def lookup_payment_by_order_or_provider_id(
if payment_db_id is not None:
payment = await payment_dal.get_payment_by_db_id(session, payment_db_id)
if not payment and provider_payment_id:
payment = await payment_dal.get_payment_by_provider_payment_id(
session, provider_payment_id
)
payment = await payment_dal.get_payment_by_provider_payment_id(session, provider_payment_id)
return payment
@@ -54,9 +52,7 @@ async def notify_user_payment_failed(
"""Send the localized ``payment_failed`` text to the user; never raises."""
db_user = payment.user or await user_dal.get_user_by_id(session, payment.user_id)
language = (
db_user.language_code
if db_user and db_user.language_code
else settings.DEFAULT_LANGUAGE
db_user.language_code if db_user and db_user.language_code else settings.DEFAULT_LANGUAGE
)
translator = make_translator(i18n, language)
try:
+57 -20
View File
@@ -323,9 +323,7 @@ async def create_webapp_payment(ctx: WebAppPaymentContext) -> web.Response:
provider="telegram_stars",
)
payload_units = amounts.purchased_gb if amounts.traffic_sale else ctx.months
payload = (
f"{payment.payment_id}:{format_number_for_payload(payload_units)}:{ctx.sale_mode}"
)
payload = f"{payment.payment_id}:{format_number_for_payload(payload_units)}:{ctx.sale_mode}"
prices = [LabeledPrice(label=ctx.description, amount=ctx.stars_price)]
create_invoice_link = getattr(bot, "create_invoice_link", None)
if callable(create_invoice_link):
@@ -371,25 +369,64 @@ async def create_webapp_payment(ctx: WebAppPaymentContext) -> web.Response:
_PRESENTATION_MANIFEST = tuple(
ProviderManifestField(
key=key, type=type_, label=label, description=description,
placeholder=placeholder, subsection="Telegram Stars",
target="presentation", attr=attr,
key=key,
type=type_,
label=label,
description=description,
placeholder=placeholder,
subsection="Telegram Stars",
target="presentation",
attr=attr,
)
for key, type_, label, description, placeholder, attr in (
("PAYMENT_STARS_WEBAPP_LABEL_RU", "string", "WebApp button text (RU)",
"Custom Russian text shown in the Web App payment method button.", "", "WEBAPP_LABEL_RU"),
("PAYMENT_STARS_WEBAPP_LABEL_EN", "string", "WebApp button text (EN)",
"Custom English text shown in the Web App payment method button.", "", "WEBAPP_LABEL_EN"),
("PAYMENT_STARS_WEBAPP_ICON", "icon", "WebApp button icon",
"Lucide icon name rendered inside the Web App payment method button.",
"Sparkles", "WEBAPP_ICON"),
("PAYMENT_STARS_TELEGRAM_LABEL_RU", "string", "Telegram button text (RU)",
"Custom Russian text shown in Telegram bot payment buttons.", "", "TELEGRAM_LABEL_RU"),
("PAYMENT_STARS_TELEGRAM_LABEL_EN", "string", "Telegram button text (EN)",
"Custom English text shown in Telegram bot payment buttons.", "", "TELEGRAM_LABEL_EN"),
("PAYMENT_STARS_TELEGRAM_EMOJI", "string", "Telegram button emoji",
"Emoji prepended to the Telegram bot payment button when customized.",
"🌟", "TELEGRAM_EMOJI"),
(
"PAYMENT_STARS_WEBAPP_LABEL_RU",
"string",
"WebApp button text (RU)",
"Custom Russian text shown in the Web App payment method button.",
"",
"WEBAPP_LABEL_RU",
),
(
"PAYMENT_STARS_WEBAPP_LABEL_EN",
"string",
"WebApp button text (EN)",
"Custom English text shown in the Web App payment method button.",
"",
"WEBAPP_LABEL_EN",
),
(
"PAYMENT_STARS_WEBAPP_ICON",
"icon",
"WebApp button icon",
"Lucide icon name rendered inside the Web App payment method button.",
"Sparkles",
"WEBAPP_ICON",
),
(
"PAYMENT_STARS_TELEGRAM_LABEL_RU",
"string",
"Telegram button text (RU)",
"Custom Russian text shown in Telegram bot payment buttons.",
"",
"TELEGRAM_LABEL_RU",
),
(
"PAYMENT_STARS_TELEGRAM_LABEL_EN",
"string",
"Telegram button text (EN)",
"Custom English text shown in Telegram bot payment buttons.",
"",
"TELEGRAM_LABEL_EN",
),
(
"PAYMENT_STARS_TELEGRAM_EMOJI",
"string",
"Telegram button emoji",
"Emoji prepended to the Telegram bot payment button when customized.",
"🌟",
"TELEGRAM_EMOJI",
),
)
)
+107 -37
View File
@@ -542,50 +542,120 @@ 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,
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"),
(
"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"),
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",
),
)
+112 -50
View File
@@ -90,9 +90,7 @@ class YooKassaConfig(ProviderEnvConfig):
AUTOPAYMENTS_ENABLED: bool = Field(default=False)
AUTOPAYMENTS_REQUIRE_CARD_BINDING: bool = Field(default=True)
@field_validator(
"SHOP_ID", "SECRET_KEY", "RETURN_URL", "DEFAULT_RECEIPT_EMAIL", mode="before"
)
@field_validator("SHOP_ID", "SECRET_KEY", "RETURN_URL", "DEFAULT_RECEIPT_EMAIL", mode="before")
@classmethod
def _strip_optional(cls, v):
if isinstance(v, str) and not v.strip():
@@ -147,7 +145,9 @@ class YooKassaService:
self.config = config or YooKassaConfig()
self._bot_username_for_default_return = bot_username_for_default_return
self._configured_return_url_override = configured_return_url
self._sdk_configured_for = None # (shop_id, secret_key) currently loaded into the global SDK
self._sdk_configured_for = (
None # (shop_id, secret_key) currently loaded into the global SDK
)
if not self.configured:
if not self.config.ENABLED:
@@ -1214,9 +1214,7 @@ async def _initiate_yk_payment(
"description": payment_description,
"subscription_duration_months": int(months) if sale_base == "subscription" else None,
"sale_mode": sale_base,
"tariff_key": sale_mode.split("@", 1)[1].split("|", 1)[0]
if "@" in sale_mode
else None,
"tariff_key": sale_mode.split("@", 1)[1].split("|", 1)[0] if "@" in sale_mode else None,
"purchased_gb": float(months)
if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"}
else None,
@@ -2505,7 +2503,9 @@ logger = logging.getLogger(__name__)
def create_service(ctx: ServiceFactoryContext) -> YooKassaService:
bundle = ctx.config_for("yookassa_service")
config = bundle.config if bundle and isinstance(bundle.config, YooKassaConfig) else YooKassaConfig()
config = (
bundle.config if bundle and isinstance(bundle.config, YooKassaConfig) else YooKassaConfig()
)
return YooKassaService(
shop_id=config.SHOP_ID,
secret_key=config.SECRET_KEY,
@@ -2516,12 +2516,7 @@ def create_service(ctx: ServiceFactoryContext) -> YooKassaService:
)
async def create_webapp_payment(ctx: WebAppPaymentContext) -> web.Response:
settings = ctx.request.app["settings"]
service: YooKassaService = ctx.request.app["yookassa_service"]
if not service or not service.configured:
return payment_unavailable()
@@ -2588,49 +2583,116 @@ async def create_webapp_payment(ctx: WebAppPaymentContext) -> web.Response:
_PRESENTATION_MANIFEST = tuple(
ProviderManifestField(
key=key, type=type_, label=label, description=description,
placeholder=placeholder, subsection="YooKassa",
target="presentation", attr=attr,
key=key,
type=type_,
label=label,
description=description,
placeholder=placeholder,
subsection="YooKassa",
target="presentation",
attr=attr,
)
for key, type_, label, description, placeholder, attr in (
("PAYMENT_YOOKASSA_WEBAPP_LABEL_RU", "string", "WebApp button text (RU)",
"Custom Russian text shown in the Web App payment method button.", "", "WEBAPP_LABEL_RU"),
("PAYMENT_YOOKASSA_WEBAPP_LABEL_EN", "string", "WebApp button text (EN)",
"Custom English text shown in the Web App payment method button.", "", "WEBAPP_LABEL_EN"),
("PAYMENT_YOOKASSA_WEBAPP_ICON", "icon", "WebApp button icon",
"Lucide icon name rendered inside the Web App payment method button.",
"CreditCard", "WEBAPP_ICON"),
("PAYMENT_YOOKASSA_TELEGRAM_LABEL_RU", "string", "Telegram button text (RU)",
"Custom Russian text shown in Telegram bot payment buttons.", "", "TELEGRAM_LABEL_RU"),
("PAYMENT_YOOKASSA_TELEGRAM_LABEL_EN", "string", "Telegram button text (EN)",
"Custom English text shown in Telegram bot payment buttons.", "", "TELEGRAM_LABEL_EN"),
("PAYMENT_YOOKASSA_TELEGRAM_EMOJI", "string", "Telegram button emoji",
"Emoji prepended to the Telegram bot payment button when customized.",
"💳", "TELEGRAM_EMOJI"),
(
"PAYMENT_YOOKASSA_WEBAPP_LABEL_RU",
"string",
"WebApp button text (RU)",
"Custom Russian text shown in the Web App payment method button.",
"",
"WEBAPP_LABEL_RU",
),
(
"PAYMENT_YOOKASSA_WEBAPP_LABEL_EN",
"string",
"WebApp button text (EN)",
"Custom English text shown in the Web App payment method button.",
"",
"WEBAPP_LABEL_EN",
),
(
"PAYMENT_YOOKASSA_WEBAPP_ICON",
"icon",
"WebApp button icon",
"Lucide icon name rendered inside the Web App payment method button.",
"CreditCard",
"WEBAPP_ICON",
),
(
"PAYMENT_YOOKASSA_TELEGRAM_LABEL_RU",
"string",
"Telegram button text (RU)",
"Custom Russian text shown in Telegram bot payment buttons.",
"",
"TELEGRAM_LABEL_RU",
),
(
"PAYMENT_YOOKASSA_TELEGRAM_LABEL_EN",
"string",
"Telegram button text (EN)",
"Custom English text shown in Telegram bot payment buttons.",
"",
"TELEGRAM_LABEL_EN",
),
(
"PAYMENT_YOOKASSA_TELEGRAM_EMOJI",
"string",
"Telegram button emoji",
"Emoji prepended to the Telegram bot payment button when customized.",
"💳",
"TELEGRAM_EMOJI",
),
)
)
_CONFIG_MANIFEST = (
ProviderManifestField("YOOKASSA_ENABLED", "bool", "Включена",
subsection="YooKassa", attr="ENABLED"),
ProviderManifestField("YOOKASSA_SHOP_ID", "string", "Shop ID",
subsection="YooKassa", attr="SHOP_ID"),
ProviderManifestField("YOOKASSA_SECRET_KEY", "string", "Secret key",
subsection="YooKassa", secret=True, attr="SECRET_KEY"),
ProviderManifestField("YOOKASSA_RETURN_URL", "url", "Return URL",
subsection="YooKassa", attr="RETURN_URL"),
ProviderManifestField("YOOKASSA_DEFAULT_RECEIPT_EMAIL", "string",
"Email для чека по умолчанию",
subsection="YooKassa", attr="DEFAULT_RECEIPT_EMAIL"),
ProviderManifestField("YOOKASSA_VAT_CODE", "int", "VAT code",
description="1..6 в зависимости от системы налогообложения",
subsection="YooKassa", min=1, max=6, attr="VAT_CODE"),
ProviderManifestField("YOOKASSA_AUTOPAYMENTS_ENABLED", "bool",
"Автоплатежи (recurring)",
subsection="YooKassa", attr="AUTOPAYMENTS_ENABLED"),
ProviderManifestField("YOOKASSA_AUTOPAYMENTS_REQUIRE_CARD_BINDING", "bool",
"Принудительная привязка карты",
subsection="YooKassa", attr="AUTOPAYMENTS_REQUIRE_CARD_BINDING"),
ProviderManifestField(
"YOOKASSA_ENABLED", "bool", "Включена", subsection="YooKassa", attr="ENABLED"
),
ProviderManifestField(
"YOOKASSA_SHOP_ID", "string", "Shop ID", subsection="YooKassa", attr="SHOP_ID"
),
ProviderManifestField(
"YOOKASSA_SECRET_KEY",
"string",
"Secret key",
subsection="YooKassa",
secret=True,
attr="SECRET_KEY",
),
ProviderManifestField(
"YOOKASSA_RETURN_URL", "url", "Return URL", subsection="YooKassa", attr="RETURN_URL"
),
ProviderManifestField(
"YOOKASSA_DEFAULT_RECEIPT_EMAIL",
"string",
"Email для чека по умолчанию",
subsection="YooKassa",
attr="DEFAULT_RECEIPT_EMAIL",
),
ProviderManifestField(
"YOOKASSA_VAT_CODE",
"int",
"VAT code",
description="1..6 в зависимости от системы налогообложения",
subsection="YooKassa",
min=1,
max=6,
attr="VAT_CODE",
),
ProviderManifestField(
"YOOKASSA_AUTOPAYMENTS_ENABLED",
"bool",
"Автоплатежи (recurring)",
subsection="YooKassa",
attr="AUTOPAYMENTS_ENABLED",
),
ProviderManifestField(
"YOOKASSA_AUTOPAYMENTS_REQUIRE_CARD_BINDING",
"bool",
"Принудительная привязка карты",
subsection="YooKassa",
attr="AUTOPAYMENTS_REQUIRE_CARD_BINDING",
),
)