diff --git a/backend/bot/payment_providers/paykilla.py b/backend/bot/payment_providers/paykilla.py index 3cc72ae..7917d83 100644 --- a/backend/bot/payment_providers/paykilla.py +++ b/backend/bot/payment_providers/paykilla.py @@ -291,6 +291,13 @@ def _clean_paykilla_text(value: Any, *, fallback: str, max_length: int = 255) -> return text[:max_length].strip() or fallback_text[:max_length].strip() or "Payment" +def _invoice_text(payment_db_id: int) -> str: + return _clean_paykilla_text( + f"Minishop payment {payment_db_id}", + fallback=f"Payment {payment_db_id}", + ) + + def _payment_currencies(config: PaykillaConfig) -> List[str]: currencies = list(parse_supported_currency_codes(config.PAYMENT_CURRENCIES)) return currencies or ["USDTTRC"] @@ -423,27 +430,18 @@ class PaykillaService(HttpClientMixin): description: str, ) -> Dict[str, Any]: currency_code = normalize_payment_currency_code(currency or self.currency) - purpose = _clean_paykilla_text( - description, - fallback=f"Payment {payment_db_id}", - max_length=255, - ) + invoice_text = _invoice_text(payment_db_id) body: Dict[str, Any] = { "type": _invoice_type_for(self.config, currency_code), - "purpose": purpose, + "purpose": invoice_text, "currency": currency_code, "totalPrice": str(format_decimal_amount(amount)), "paymentCurrencies": _payment_currencies(self.config), "clientOrderId": str(payment_db_id), "userPaysServiceFee": bool(self.config.USER_PAYS_SERVICE_FEE), "userPaysNetworkFee": bool(self.config.USER_PAYS_NETWORK_FEE), + "description": invoice_text, } - if description: - body["description"] = _clean_paykilla_text( - description, - fallback=purpose, - max_length=255, - ) if self.config.LIFETIME_SECONDS: expires_at = datetime.now(timezone.utc) + timedelta( seconds=int(self.config.LIFETIME_SECONDS) diff --git a/docs/features/payments.md b/docs/features/payments.md index fba963e..86801c9 100644 --- a/docs/features/payments.md +++ b/docs/features/payments.md @@ -158,7 +158,7 @@ Heleket используется для крипто-инвойсов с отд PayKilla используется для крипто-инвойсов V2 через hosted checkout `https://gopay.paykilla.com/{invoice_id}`. API-запросы подписываются HMAC-SHA256, webhook проверяется по заголовку `X-API-SIGN` и raw body. -PayKilla строго валидирует текстовые поля invoice. Minishop перед отправкой автоматически заменяет тире пробелами, транслитерирует кириллицу и оставляет только ASCII-буквы, цифры, пробелы, `_`, `.`, `,` в `purpose` и `description`. +PayKilla строго валидирует текстовые поля invoice. Поэтому Minishop отправляет в `purpose` и `description` простой английский текст `Minishop payment `, а локализованное описание платежа оставляет только внутри Minishop. Дополнительно эти поля проходят ASCII-safe sanitizer: допускаются ASCII-буквы, цифры, пробелы, `_`, `.`, `,`. Какие полномочия нужны API key: diff --git a/tests/test_security.py b/tests/test_security.py index 0b9235e..fed1dc4 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -243,7 +243,7 @@ class PaykillaServiceTests(unittest.TestCase): self.assertEqual(text, "Oplata podpiski na 1 mes. tarif Bazovyy") self.assertRegex(text, r"^[A-Za-z0-9_\s.,]+$") - def test_invoice_body_uses_ascii_safe_purpose_and_description(self): + def test_invoice_body_uses_english_purpose_and_description(self): service = self._make_service() body = service._invoice_body( @@ -253,7 +253,7 @@ class PaykillaServiceTests(unittest.TestCase): description="Оплата подписки на 1 мес. - тариф «Базовый» ✅", ) - self.assertEqual(body["purpose"], "Oplata podpiski na 1 mes. tarif Bazovyy") + self.assertEqual(body["purpose"], "Minishop payment 556") self.assertEqual(body["description"], body["purpose"]) self.assertRegex(body["purpose"], r"^[A-Za-z0-9_\s.,]+$")