diff --git a/backend/bot/app/web/admin_settings_manifest.py b/backend/bot/app/web/admin_settings_manifest.py index a4c4c57..76570b9 100644 --- a/backend/bot/app/web/admin_settings_manifest.py +++ b/backend/bot/app/web/admin_settings_manifest.py @@ -348,6 +348,16 @@ SETTINGS_MANIFEST: List[SettingField] = [ "Английская версия текста на этапе оплаты.", subsection="checkout", ), + SettingField( + "PAYMENT_REQUEST_TIMEOUT_SECONDS", + "float", + "payments", + "Таймаут запроса к провайдеру", + "Максимальное общее время одного API-запроса к платёжному провайдеру, в секундах.", + optional=False, + min=1, + subsection="checkout", + ), # ─── Payment providers (toggles) ─────────────────────────────── # Common SettingField("STARS_ENABLED", "bool", "payments", "Telegram Stars", subsection="common"), diff --git a/backend/bot/payment_providers/freekassa.py b/backend/bot/payment_providers/freekassa.py index 1d830a5..8212f1d 100644 --- a/backend/bot/payment_providers/freekassa.py +++ b/backend/bot/payment_providers/freekassa.py @@ -153,7 +153,7 @@ class FreeKassaService(HttpClientMixin): self.default_currency: str = default_payment_currency_code_for_settings(settings).upper() self.api_base_url: str = "https://api.fk.life/v1" - self._init_http_client(total_timeout=15) + self._init_http_client(total_timeout=self.settings.PAYMENT_REQUEST_TIMEOUT_SECONDS) self._nonce_lock = asyncio.Lock() self._last_nonce = int(time.time() * 1000) diff --git a/backend/bot/payment_providers/heleket.py b/backend/bot/payment_providers/heleket.py index 5faa786..f11bed5 100644 --- a/backend/bot/payment_providers/heleket.py +++ b/backend/bot/payment_providers/heleket.py @@ -243,7 +243,7 @@ class HeleketService(HttpClientMixin): self.referral_service = referral_service self._default_return_url = default_return_url - self._init_http_client(total_timeout=20) + self._init_http_client(total_timeout=self.settings.PAYMENT_REQUEST_TIMEOUT_SECONDS) if not self.configured: logging.warning( "HeleketService initialized but not fully configured. Payments disabled." diff --git a/backend/bot/payment_providers/paykilla.py b/backend/bot/payment_providers/paykilla.py index 7917d83..0242f6e 100644 --- a/backend/bot/payment_providers/paykilla.py +++ b/backend/bot/payment_providers/paykilla.py @@ -369,7 +369,7 @@ class PaykillaService(HttpClientMixin): self.referral_service = referral_service self._default_return_url = default_return_url - self._init_http_client(total_timeout=20) + self._init_http_client(total_timeout=self.settings.PAYMENT_REQUEST_TIMEOUT_SECONDS) if not self.configured: logging.warning( "PaykillaService initialized but not fully configured. Payments disabled." diff --git a/backend/bot/payment_providers/platega.py b/backend/bot/payment_providers/platega.py index 879d643..a05ffe2 100644 --- a/backend/bot/payment_providers/platega.py +++ b/backend/bot/payment_providers/platega.py @@ -157,7 +157,7 @@ class PlategaService(HttpClientMixin): self.referral_service = referral_service self._default_return_url = default_return_url - self._init_http_client(total_timeout=60) + self._init_http_client(total_timeout=self.settings.PAYMENT_REQUEST_TIMEOUT_SECONDS) if not self.configured: logging.warning( "PlategaService initialized but not fully configured. Payments disabled." diff --git a/backend/bot/payment_providers/severpay.py b/backend/bot/payment_providers/severpay.py index 2822e4a..92fd910 100644 --- a/backend/bot/payment_providers/severpay.py +++ b/backend/bot/payment_providers/severpay.py @@ -136,7 +136,7 @@ class SeverPayService(HttpClientMixin): self.referral_service = referral_service self._default_return_url = default_return_url - self._init_http_client(total_timeout=15) + self._init_http_client(total_timeout=self.settings.PAYMENT_REQUEST_TIMEOUT_SECONDS) if not self.configured: logging.warning( diff --git a/backend/bot/payment_providers/wata.py b/backend/bot/payment_providers/wata.py index 3d0109e..4c4e2ec 100644 --- a/backend/bot/payment_providers/wata.py +++ b/backend/bot/payment_providers/wata.py @@ -206,7 +206,7 @@ class WataService(HttpClientMixin): self._default_return_url = default_return_url self._cached_public_key_pem = None # populated by webhook on first verify - self._init_http_client(total_timeout=10) + self._init_http_client(total_timeout=self.settings.PAYMENT_REQUEST_TIMEOUT_SECONDS) if not self.configured: logging.warning("WataService initialized but not fully configured. Payments disabled.") diff --git a/backend/config/settings.py b/backend/config/settings.py index 43795a3..7568d55 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -319,6 +319,11 @@ class Settings(BaseSettings): default=DEFAULT_SUBSCRIPTION_PURCHASE_DESCRIPTION_EN, description="English subscription description shown before purchase/renewal options.", ) + PAYMENT_REQUEST_TIMEOUT_SECONDS: float = Field( + default=20, + ge=1, + description="Maximum total time for one payment provider API request, in seconds.", + ) MONTH_1_ENABLED: bool = Field(default=True, alias="1_MONTH_ENABLED") MONTH_3_ENABLED: bool = Field(default=True, alias="3_MONTHS_ENABLED") diff --git a/docs/configuration/env-vars.md b/docs/configuration/env-vars.md index 6bea97d..3f93d9d 100644 --- a/docs/configuration/env-vars.md +++ b/docs/configuration/env-vars.md @@ -200,6 +200,7 @@ | `PAYMENT_METHODS_ORDER` | Порядок кнопок оплаты: `severpay,wata,freekassa,platega,yookassa,stars,cryptopay,heleket,paykilla`. | | `SUBSCRIPTION_PURCHASE_DESCRIPTION_ENABLED` | Показывать описание подписки перед выбором срока. | | `SUBSCRIPTION_PURCHASE_DESCRIPTION_RU` / `SUBSCRIPTION_PURCHASE_DESCRIPTION_EN` | Локализованное описание подписки. | +| `PAYMENT_REQUEST_TIMEOUT_SECONDS` | Общий таймаут одного API-запроса к платёжному провайдеру, в секундах. По умолчанию `20`. | | `PAYMENT__WEBAPP_LABEL_RU` / `PAYMENT__WEBAPP_LABEL_EN` | Текст кнопки провайдера в Web App. | | `PAYMENT__WEBAPP_ICON` | Lucide-иконка кнопки в Web App. | | `PAYMENT__TELEGRAM_LABEL_RU` / `PAYMENT__TELEGRAM_LABEL_EN` | Текст кнопки в Telegram. | diff --git a/frontend/src/lib/webapp/settingsManifest.generated.json b/frontend/src/lib/webapp/settingsManifest.generated.json index 36ac6d3..00ef193 100644 --- a/frontend/src/lib/webapp/settingsManifest.generated.json +++ b/frontend/src/lib/webapp/settingsManifest.generated.json @@ -603,6 +603,26 @@ "overridden": false, "updated_at": null }, + { + "key": "PAYMENT_REQUEST_TIMEOUT_SECONDS", + "type": "float", + "section": "payments", + "section_order": 4, + "subsection": "checkout", + "label": "Таймаут запроса к провайдеру", + "description": "Максимальное общее время одного API-запроса к платёжному провайдеру, в секундах.", + "i18n_label_key": "admin_settings_field_payment_request_timeout_seconds_label", + "i18n_description_key": "admin_settings_field_payment_request_timeout_seconds_description", + "i18n_subsection_key": "admin_settings_subsection_checkout", + "i18n_placeholder_key": null, + "placeholder": "", + "optional": false, + "secret": false, + "min": 1, + "value": "", + "overridden": false, + "updated_at": null + }, { "key": "STARS_ENABLED", "type": "bool", diff --git a/locales/en.json b/locales/en.json index f234711..cd94f0d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1170,6 +1170,7 @@ "admin_settings_subsection_cryptopay": "CryptoPay", "admin_settings_subsection_wata": "Wata", "admin_settings_subsection_heleket": "Heleket", + "admin_settings_subsection_paykilla": "PayKilla", "admin_settings_provider_webhook_url": "Webhook URL", "admin_settings_provider_webhook_url_hint": "Use this URL in the provider webhook settings.", "admin_settings_panel_webhook_url_hint": "Use this URL as WEBHOOK_URL in Remnawave Panel.", @@ -1676,6 +1677,8 @@ "admin_settings_field_subscription_purchase_description_ru_description": "Russian text shown during checkout.", "admin_settings_field_subscription_purchase_description_en_label": "Subscription description (EN)", "admin_settings_field_subscription_purchase_description_en_description": "English text shown during checkout.", + "admin_settings_field_payment_request_timeout_seconds_label": "Payment provider request timeout", + "admin_settings_field_payment_request_timeout_seconds_description": "Maximum total time for one payment provider API request, in seconds.", "admin_settings_field_stars_enabled_label": "Stars Enabled", "admin_settings_field_yookassa_enabled_label": "YooKassa Enabled", "admin_settings_field_yookassa_shop_id_label": "YooKassa Shop ID", diff --git a/locales/ru.json b/locales/ru.json index 6900916..ecacb52 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1170,6 +1170,7 @@ "admin_settings_subsection_cryptopay": "CryptoPay", "admin_settings_subsection_wata": "Wata", "admin_settings_subsection_heleket": "Heleket", + "admin_settings_subsection_paykilla": "PayKilla", "admin_settings_provider_webhook_url": "Webhook URL", "admin_settings_provider_webhook_url_hint": "Укажите этот адрес в настройках вебхуков провайдера.", "admin_settings_panel_webhook_url_hint": "Укажите этот адрес как WEBHOOK_URL в Remnawave Panel.", @@ -1676,6 +1677,8 @@ "admin_settings_field_subscription_purchase_description_ru_description": "Русская версия текста на этапе оплаты.", "admin_settings_field_subscription_purchase_description_en_label": "Описание подписки (EN)", "admin_settings_field_subscription_purchase_description_en_description": "Английская версия текста на этапе оплаты.", + "admin_settings_field_payment_request_timeout_seconds_label": "Таймаут запроса к провайдеру", + "admin_settings_field_payment_request_timeout_seconds_description": "Максимальное общее время одного API-запроса к платёжному провайдеру, в секундах.", "admin_settings_field_stars_enabled_label": "Telegram Stars", "admin_settings_field_yookassa_enabled_label": "Включена", "admin_settings_field_yookassa_shop_id_label": "Shop ID", diff --git a/tests/test_admin_settings_manifest_i18n.py b/tests/test_admin_settings_manifest_i18n.py index 1228bcd..fed24a5 100644 --- a/tests/test_admin_settings_manifest_i18n.py +++ b/tests/test_admin_settings_manifest_i18n.py @@ -24,6 +24,7 @@ SUBSCRIPTION_PURCHASE_DESCRIPTION_SETTINGS = ( "SUBSCRIPTION_PURCHASE_DESCRIPTION_ENABLED", "SUBSCRIPTION_PURCHASE_DESCRIPTION_RU", "SUBSCRIPTION_PURCHASE_DESCRIPTION_EN", + "PAYMENT_REQUEST_TIMEOUT_SECONDS", ) SUBSCRIPTION_GUIDE_SETTINGS = ( @@ -177,6 +178,11 @@ def test_support_settings_i18n_keys_exist_in_admin_locales(): def test_subscription_purchase_description_settings_i18n_keys_exist(): manifest = _manifest_by_key() + timeout_field = manifest["PAYMENT_REQUEST_TIMEOUT_SECONDS"] + assert timeout_field["type"] == "float" + assert timeout_field["optional"] is False + assert timeout_field["min"] == 1 + for language in ("ru", "en"): messages = _locale(language) for setting_key in SUBSCRIPTION_PURCHASE_DESCRIPTION_SETTINGS: