From cd4b40af6bdb01b0f175ead4b535fa568b425aba Mon Sep 17 00:00:00 2001 From: 3252a8 <3252a8@proton.me> Date: Thu, 4 Jun 2026 15:14:13 +0300 Subject: [PATCH] fix: remove PayKilla redirect fields --- backend/bot/payment_providers/paykilla.py | 44 +---------- docs/configuration/env-vars.md | 3 - docs/features/payments.md | 4 +- .../webapp/settingsManifest.generated.json | 75 ------------------- tests/test_security.py | 20 ++--- 5 files changed, 11 insertions(+), 135 deletions(-) diff --git a/backend/bot/payment_providers/paykilla.py b/backend/bot/payment_providers/paykilla.py index 4294a7e..0d8e7ef 100644 --- a/backend/bot/payment_providers/paykilla.py +++ b/backend/bot/payment_providers/paykilla.py @@ -182,9 +182,6 @@ class PaykillaConfig(ProviderEnvConfig): INVOICE_TYPE: Optional[str] = None PAYMENT_CURRENCIES: str = Field(default=PAYKILLA_DEFAULT_PAYMENT_CURRENCIES) SUPPORTED_CURRENCIES: str = Field(default=PAYKILLA_DEFAULT_SUPPORTED_CURRENCIES) - RETURN_URL: Optional[str] = None - SUCCESS_URL: Optional[str] = None - CANCEL_URL: Optional[str] = None LIFETIME_SECONDS: int = Field(default=3600) RECV_WINDOW_MS: int = Field(default=5000) USER_PAYS_SERVICE_FEE: bool = Field(default=True) @@ -219,9 +216,6 @@ class PaykillaConfig(ProviderEnvConfig): "API_KEY", "SECRET_KEY", "INVOICE_TYPE", - "RETURN_URL", - "SUCCESS_URL", - "CANCEL_URL", "WEBHOOK_URL", mode="before", ) @@ -291,9 +285,10 @@ 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: +def _invoice_text(title: Any, payment_db_id: int) -> str: + project_title = _clean_paykilla_text(title, fallback="Minishop") return _clean_paykilla_text( - f"Minishop payment {payment_db_id}", + f"{project_title} payment {payment_db_id}", fallback=f"Payment {payment_db_id}", ) @@ -403,18 +398,6 @@ class PaykillaService(HttpClientMixin): def currency(self) -> str: return (self.config.CURRENCY or "RUB").upper() - @property - def return_url(self) -> str: - return self.config.RETURN_URL or f"https://t.me/{self._default_return_url}" - - @property - def success_url(self) -> str: - return self.config.SUCCESS_URL or self.return_url - - @property - def cancel_url(self) -> Optional[str]: - return self.config.CANCEL_URL - @property def verify_webhook_signature(self) -> bool: return self.config.VERIFY_WEBHOOK_SIGNATURE @@ -434,7 +417,7 @@ class PaykillaService(HttpClientMixin): description: str, ) -> Dict[str, Any]: currency_code = normalize_payment_currency_code(currency or self.currency) - invoice_text = _invoice_text(payment_db_id) + 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), "purpose": invoice_text, @@ -451,16 +434,6 @@ class PaykillaService(HttpClientMixin): seconds=int(self.config.LIFETIME_SECONDS) ) body["expiredAt"] = expires_at.isoformat().replace("+00:00", "Z") - - urls: List[Dict[str, Any]] = [] - if self.config.SUCCESS_URL: - urls.append({"type": "SUCCESS", "url": self.config.SUCCESS_URL, "autoRedirect": True}) - if self.config.RETURN_URL: - urls.append({"type": "RETURN", "url": self.config.RETURN_URL}) - if self.config.CANCEL_URL: - urls.append({"type": "CANCEL", "url": self.config.CANCEL_URL}) - if urls: - body["urls"] = urls return body async def create_payment_link( @@ -1053,15 +1026,6 @@ _CONFIG_MANIFEST = ( ("OPEN_AMOUNT", "OPEN_AMOUNT"), ), ), - ProviderManifestField( - "PAYKILLA_RETURN_URL", "url", "Return URL", subsection="PayKilla", attr="RETURN_URL" - ), - ProviderManifestField( - "PAYKILLA_SUCCESS_URL", "url", "Success URL", subsection="PayKilla", attr="SUCCESS_URL" - ), - ProviderManifestField( - "PAYKILLA_CANCEL_URL", "url", "Cancel URL", subsection="PayKilla", attr="CANCEL_URL" - ), ProviderManifestField( "PAYKILLA_LIFETIME_SECONDS", "int", diff --git a/docs/configuration/env-vars.md b/docs/configuration/env-vars.md index 6bea97d..e968fb2 100644 --- a/docs/configuration/env-vars.md +++ b/docs/configuration/env-vars.md @@ -380,9 +380,6 @@ Webhook настраивается в PayKilla Dashboard: **Settings -> Webhooks | `PAYKILLA_PAYMENT_CURRENCIES` | Crypto tickers для оплаты, например `USDTTRC,BTC,ETH`. | | `PAYKILLA_SUPPORTED_CURRENCIES` | Валюты инвойса, разрешенные в этом магазине. | | `PAYKILLA_INVOICE_TYPE` | Необязательный override: `FIAT_BASED`, `FIXED_AMOUNT` или `OPEN_AMOUNT`. | -| `PAYKILLA_RETURN_URL` | URL кнопки возврата на checkout-странице. | -| `PAYKILLA_SUCCESS_URL` | URL после успешной оплаты; используется с auto-redirect. | -| `PAYKILLA_CANCEL_URL` | URL после отмены оплаты. | | `PAYKILLA_LIFETIME_SECONDS` | TTL инвойса, отправляется как `expiredAt`. | | `PAYKILLA_RECV_WINDOW_MS` | `recvWindow` для подписанных API-запросов. | | `PAYKILLA_USER_PAYS_SERVICE_FEE` | `true`, если пользователь оплачивает service fee. | diff --git a/docs/features/payments.md b/docs/features/payments.md index ebf22e2..cbf8706 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 отправляет в `purpose` и `description` простой английский текст `Minishop payment `, а локализованное описание платежа оставляет только внутри Minishop. Дополнительно эти поля проходят ASCII-safe sanitizer: допускаются ASCII-буквы, цифры, пробелы, `_`, `.`, `,`. +PayKilla строго валидирует текстовые поля invoice. Поэтому Minishop отправляет в `purpose` и `description` простой английский текст ` payment `, а локализованное описание платежа оставляет только внутри Minishop. Дополнительно эти поля проходят ASCII-safe sanitizer: допускаются ASCII-буквы, цифры, пробелы, `_`, `.`, `,`. Какие полномочия нужны API key: @@ -182,7 +182,7 @@ PayKilla строго валидирует текстовые поля invoice. 1. Включите `PAYKILLA_ENABLED`. 2. Укажите `PAYKILLA_API_KEY` и `PAYKILLA_SECRET_KEY`. 3. Проверьте `PAYKILLA_CURRENCY` и `PAYKILLA_PAYMENT_CURRENCIES`, например `USDTTRC,BTC,ETH`. -4. При необходимости задайте `PAYKILLA_SUCCESS_URL`, `PAYKILLA_RETURN_URL` и `PAYKILLA_CANCEL_URL`; по умолчанию Minishop не отправляет redirect URLs в PayKilla и полагается на webhook для активации платежа. +4. Убедитесь, что webhook `/webhook/paykilla` настроен в PayKilla: Minishop не отправляет redirect URLs в PayKilla и полагается на webhook для активации платежа. 5. Добавьте `paykilla` в `PAYMENT_METHODS_ORDER`, если хотите задать явный порядок кнопок. Справочник переменных: [PayKilla](../configuration/env-vars.md#paykilla). diff --git a/frontend/src/lib/webapp/settingsManifest.generated.json b/frontend/src/lib/webapp/settingsManifest.generated.json index 36ac6d3..b88cead 100644 --- a/frontend/src/lib/webapp/settingsManifest.generated.json +++ b/frontend/src/lib/webapp/settingsManifest.generated.json @@ -4090,81 +4090,6 @@ "updated_at": null, "webhook_base_url_configured": false }, - { - "key": "PAYKILLA_RETURN_URL", - "type": "url", - "section": "payments", - "section_order": 4, - "subsection": "PayKilla", - "label": "Return URL", - "description": "", - "i18n_label_key": "admin_settings_field_paykilla_return_url_label", - "i18n_description_key": null, - "i18n_subsection_key": "admin_settings_subsection_paykilla", - "i18n_placeholder_key": null, - "placeholder": "", - "optional": true, - "secret": false, - "provider_id": "paykilla", - "provider_label": "PayKilla", - "webhook_provider_id": "paykilla", - "webhook_path": "/webhook/paykilla", - "webhook_requires_base_url": false, - "value": "", - "overridden": false, - "updated_at": null, - "webhook_base_url_configured": false - }, - { - "key": "PAYKILLA_SUCCESS_URL", - "type": "url", - "section": "payments", - "section_order": 4, - "subsection": "PayKilla", - "label": "Success URL", - "description": "", - "i18n_label_key": "admin_settings_field_paykilla_success_url_label", - "i18n_description_key": null, - "i18n_subsection_key": "admin_settings_subsection_paykilla", - "i18n_placeholder_key": null, - "placeholder": "", - "optional": true, - "secret": false, - "provider_id": "paykilla", - "provider_label": "PayKilla", - "webhook_provider_id": "paykilla", - "webhook_path": "/webhook/paykilla", - "webhook_requires_base_url": false, - "value": "", - "overridden": false, - "updated_at": null, - "webhook_base_url_configured": false - }, - { - "key": "PAYKILLA_CANCEL_URL", - "type": "url", - "section": "payments", - "section_order": 4, - "subsection": "PayKilla", - "label": "Cancel URL", - "description": "", - "i18n_label_key": "admin_settings_field_paykilla_cancel_url_label", - "i18n_description_key": null, - "i18n_subsection_key": "admin_settings_subsection_paykilla", - "i18n_placeholder_key": null, - "placeholder": "", - "optional": true, - "secret": false, - "provider_id": "paykilla", - "provider_label": "PayKilla", - "webhook_provider_id": "paykilla", - "webhook_path": "/webhook/paykilla", - "webhook_requires_base_url": false, - "value": "", - "overridden": false, - "updated_at": null, - "webhook_base_url_configured": false - }, { "key": "PAYKILLA_LIFETIME_SECONDS", "type": "int", diff --git a/tests/test_security.py b/tests/test_security.py index 3cc5a43..b6085ce 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -218,6 +218,7 @@ class PaykillaServiceTests(unittest.TestCase): ) service.settings = SimpleNamespace( WEBHOOK_BASE_URL="https://shop.example", + WEBAPP_TITLE="/minishop", trusted_proxies=["127.0.0.1"], ) service._default_return_url = "test_bot" @@ -245,6 +246,7 @@ class PaykillaServiceTests(unittest.TestCase): def test_invoice_body_uses_english_purpose_and_description(self): service = self._make_service() + service.settings.WEBAPP_TITLE = "Tunnel Shop" body = service._invoice_body( payment_db_id=556, @@ -253,15 +255,13 @@ class PaykillaServiceTests(unittest.TestCase): description="Оплата подписки на 1 мес. - тариф «Базовый» ✅", ) - self.assertEqual(body["purpose"], "Minishop payment 556") + self.assertEqual(body["purpose"], "Tunnel Shop payment 556") self.assertEqual(body["description"], body["purpose"]) self.assertRegex(body["purpose"], r"^[A-Za-z0-9_\s.,]+$") self.assertNotIn("urls", body) - def test_invoice_body_includes_only_explicit_redirect_urls(self): + def test_invoice_body_omits_redirect_urls(self): service = self._make_service() - service.config.SUCCESS_URL = "https://shop.example/pay/success" - service.config.RETURN_URL = "https://shop.example/pay/return" body = service._invoice_body( payment_db_id=556, @@ -270,17 +270,7 @@ class PaykillaServiceTests(unittest.TestCase): description="ignored", ) - self.assertEqual( - body["urls"], - [ - { - "type": "SUCCESS", - "url": "https://shop.example/pay/success", - "autoRedirect": True, - }, - {"type": "RETURN", "url": "https://shop.example/pay/return"}, - ], - ) + self.assertNotIn("urls", body) def test_verify_webhook_signature_accepts_raw_body_signature(self): service = self._make_service()