From 69985e44dd7bd5a420670ad2a6b240352e46d37e Mon Sep 17 00:00:00 2001 From: 3252a8 <3252a8@proton.me> Date: Thu, 4 Jun 2026 15:34:26 +0300 Subject: [PATCH] fix: send tariff currency to PayKilla --- backend/bot/payment_providers/paykilla.py | 16 ++++------------ docs/configuration/env-vars.md | 2 +- docs/features/payments.md | 4 +++- .../lib/webapp/settingsManifest.generated.json | 2 +- tests/test_security.py | 16 +++++++++++++++- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/backend/bot/payment_providers/paykilla.py b/backend/bot/payment_providers/paykilla.py index b4581f8..7cc45e1 100644 --- a/backend/bot/payment_providers/paykilla.py +++ b/backend/bot/payment_providers/paykilla.py @@ -416,7 +416,7 @@ class PaykillaService(HttpClientMixin): currency: Optional[str], description: str, ) -> Dict[str, Any]: - currency_code = self.currency + currency_code = normalize_payment_currency_code(currency or self.currency) invoice_text = _invoice_text(getattr(self.settings, "WEBAPP_TITLE", None), payment_db_id) body: Dict[str, Any] = { "type": _invoice_type_for(self.config, currency_code), @@ -449,8 +449,7 @@ class PaykillaService(HttpClientMixin): logging.error("PaykillaService is not configured. Cannot create payment link.") return False, {"message": "service_not_configured"} - payment_currency = normalize_payment_currency_code(currency or self.currency) - currency_code = self.currency + currency_code = normalize_payment_currency_code(currency or self.currency) supported = parse_supported_currency_codes(self.config.SUPPORTED_CURRENCIES) if supported and currency_code not in supported: return False, { @@ -458,13 +457,6 @@ class PaykillaService(HttpClientMixin): "currency": currency_code, "supported_currencies": list(supported), } - if payment_currency and payment_currency != currency_code: - logging.info( - "Paykilla create_payment_link: using configured invoice currency %s " - "instead of payment record currency %s; amount is sent unchanged.", - currency_code, - payment_currency, - ) body = self._invoice_body( payment_db_id=payment_db_id, @@ -994,8 +986,8 @@ _CONFIG_MANIFEST = ( "string", "Invoice currency", description=( - "PayKilla invoice currency. Amount is sent unchanged, so keep tariffs " - "priced in this currency or use a PayKilla-enabled fiat such as USD." + "Fallback invoice currency when the payment flow does not provide one. " + "Usually matches the tariff/default currency, e.g. RUB." ), placeholder="RUB", subsection="PayKilla", diff --git a/docs/configuration/env-vars.md b/docs/configuration/env-vars.md index 123ee3e..b54f97e 100644 --- a/docs/configuration/env-vars.md +++ b/docs/configuration/env-vars.md @@ -376,7 +376,7 @@ Webhook настраивается в PayKilla Dashboard: **Settings -> Webhooks | `PAYKILLA_WIDGET_URL` | URL hosted checkout, по умолчанию `https://gopay.paykilla.com`. | | `PAYKILLA_API_KEY` / `PAYKILLA_V2_API_KEY` | Public HMAC key с правом `INVOICE`. | | `PAYKILLA_SECRET_KEY` / `PAYKILLA_V2_SECRET_KEY` | Secret HMAC key для подписи API-запросов и проверки webhook. | -| `PAYKILLA_CURRENCY` | Валюта инвойса PayKilla. Сумма отправляется без FX-конвертации, поэтому цены должны быть в этой валюте или валюта должна быть включена у PayKilla. Для RUB/USD/EUR/AED/GBP используется `FIAT_BASED`, для остальных - `FIXED_AMOUNT`. | +| `PAYKILLA_CURRENCY` | Резервная валюта инвойса PayKilla, если платежный поток не передал валюту тарифа. Обычно совпадает с `DEFAULT_CURRENCY_SYMBOL`, например `RUB`. Для RUB/USD/EUR/AED/GBP используется `FIAT_BASED`, для остальных - `FIXED_AMOUNT`. | | `PAYKILLA_PAYMENT_CURRENCIES` | Crypto tickers для оплаты, например `USDTTRC,BTC,ETH`. | | `PAYKILLA_SUPPORTED_CURRENCIES` | Валюты инвойса, разрешенные в этом магазине. | | `PAYKILLA_INVOICE_TYPE` | Необязательный override: `FIAT_BASED`, `FIXED_AMOUNT` или `OPEN_AMOUNT`. | diff --git a/docs/features/payments.md b/docs/features/payments.md index cd88d4f..cf0debb 100644 --- a/docs/features/payments.md +++ b/docs/features/payments.md @@ -160,6 +160,8 @@ PayKilla используется для крипто-инвойсов V2 чер PayKilla строго валидирует текстовые поля invoice. Поэтому Minishop отправляет в `purpose` и `description` простой английский текст ` payment `, а локализованное описание платежа оставляет только внутри Minishop. Дополнительно эти поля проходят ASCII-safe sanitizer: допускаются ASCII-буквы, цифры, пробелы, `_`, `.`, `,`. +Для `FIAT_BASED` инвойса Minishop отправляет сумму и валюту тарифа как есть, например `190.00 RUB`. PayKilla рассчитывает сумму к оплате в выбранной криптовалюте из `paymentCurrencies`. + Какие полномочия нужны API key: 1. В PayKilla Dashboard откройте **Settings -> API keys**. @@ -181,7 +183,7 @@ PayKilla строго валидирует текстовые поля invoice. 1. Включите `PAYKILLA_ENABLED`. 2. Укажите `PAYKILLA_API_KEY` и `PAYKILLA_SECRET_KEY`. -3. Проверьте `PAYKILLA_CURRENCY` и `PAYKILLA_PAYMENT_CURRENCIES`, например `USD` и `USDTTRC,BTC,ETH`. `PAYKILLA_CURRENCY` теперь напрямую задаёт валюту invoice; Minishop не конвертирует сумму между RUB/USD/EUR. +3. Проверьте валюту тарифов/`DEFAULT_CURRENCY_SYMBOL` и `PAYKILLA_CURRENCY`, например `RUB`; в `PAYKILLA_PAYMENT_CURRENCIES` укажите crypto tickers, например `USDTTRC,BTC,ETH`. 4. Убедитесь, что webhook `/webhook/paykilla` настроен в PayKilla: Minishop не отправляет redirect URLs в PayKilla и полагается на webhook для активации платежа. 5. Добавьте `paykilla` в `PAYMENT_METHODS_ORDER`, если хотите задать явный порядок кнопок. diff --git a/frontend/src/lib/webapp/settingsManifest.generated.json b/frontend/src/lib/webapp/settingsManifest.generated.json index 1762d3c..b4a39be 100644 --- a/frontend/src/lib/webapp/settingsManifest.generated.json +++ b/frontend/src/lib/webapp/settingsManifest.generated.json @@ -3975,7 +3975,7 @@ "section_order": 4, "subsection": "PayKilla", "label": "Invoice currency", - "description": "PayKilla invoice currency. Amount is sent unchanged, so keep tariffs priced in this currency or use a PayKilla-enabled fiat such as USD.", + "description": "Fallback invoice currency when the payment flow does not provide one. Usually matches the tariff/default currency, e.g. RUB.", "i18n_label_key": "admin_settings_field_paykilla_currency_label", "i18n_description_key": "admin_settings_field_paykilla_currency_description", "i18n_subsection_key": "admin_settings_subsection_paykilla", diff --git a/tests/test_security.py b/tests/test_security.py index d463420..8f1ead9 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -260,7 +260,7 @@ class PaykillaServiceTests(unittest.TestCase): self.assertRegex(body["purpose"], r"^[A-Za-z0-9_\s.,]+$") self.assertNotIn("urls", body) - def test_invoice_body_uses_configured_paykilla_currency(self): + def test_invoice_body_uses_payment_currency_before_configured_fallback(self): service = self._make_service() service.config.CURRENCY = "USD" @@ -271,6 +271,20 @@ class PaykillaServiceTests(unittest.TestCase): description="ignored", ) + self.assertEqual(body["currency"], "RUB") + self.assertEqual(body["type"], "FIAT_BASED") + + def test_invoice_body_uses_configured_currency_as_fallback(self): + service = self._make_service() + service.config.CURRENCY = "USD" + + body = service._invoice_body( + payment_db_id=556, + amount=100, + currency=None, + description="ignored", + ) + self.assertEqual(body["currency"], "USD") self.assertEqual(body["type"], "FIAT_BASED")