From e108cc0baacfc60754517c7badcbadbeb6078322 Mon Sep 17 00:00:00 2001 From: 3252a8 <3252a8@proton.me> Date: Thu, 4 Jun 2026 15:01:40 +0300 Subject: [PATCH] fix: omit default PayKilla redirect urls --- backend/bot/payment_providers/paykilla.py | 20 +++++++++++------- docs/features/payments.md | 2 +- tests/test_security.py | 25 +++++++++++++++++++++++ 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/backend/bot/payment_providers/paykilla.py b/backend/bot/payment_providers/paykilla.py index 7917d83..4294a7e 100644 --- a/backend/bot/payment_providers/paykilla.py +++ b/backend/bot/payment_providers/paykilla.py @@ -347,6 +347,10 @@ def _response_invoice_data(response_data: Dict[str, Any]) -> Dict[str, Any]: return response_data if isinstance(response_data, dict) else {} +def _debug_invoice_body(body: Dict[str, Any]) -> str: + return json.dumps(body, ensure_ascii=True, sort_keys=True) + + class PaykillaService(HttpClientMixin): def __init__( self, @@ -449,12 +453,12 @@ class PaykillaService(HttpClientMixin): body["expiredAt"] = expires_at.isoformat().replace("+00:00", "Z") urls: List[Dict[str, Any]] = [] - if self.success_url: - urls.append({"type": "SUCCESS", "url": self.success_url, "autoRedirect": True}) - if self.return_url: - urls.append({"type": "RETURN", "url": self.return_url}) - if self.cancel_url: - urls.append({"type": "CANCEL", "url": self.cancel_url}) + 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 @@ -512,9 +516,11 @@ class PaykillaService(HttpClientMixin): invoice_id = first_value(invoice, "id") if response.status not in {200, 201} or not invoice_id: logging.error( - "Paykilla create_payment_link: API error (status=%s, body=%s)", + "Paykilla create_payment_link: API error " + "(status=%s, body=%s, request_body=%s)", response.status, response_data, + _debug_invoice_body(body), ) return False, {"status": response.status, "message": response_data} invoice["payment_url"] = f"{self.widget_url}/{invoice_id}" diff --git a/docs/features/payments.md b/docs/features/payments.md index 86801c9..ebf22e2 100644 --- a/docs/features/payments.md +++ b/docs/features/payments.md @@ -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`. +4. При необходимости задайте `PAYKILLA_SUCCESS_URL`, `PAYKILLA_RETURN_URL` и `PAYKILLA_CANCEL_URL`; по умолчанию Minishop не отправляет redirect URLs в PayKilla и полагается на webhook для активации платежа. 5. Добавьте `paykilla` в `PAYMENT_METHODS_ORDER`, если хотите задать явный порядок кнопок. Справочник переменных: [PayKilla](../configuration/env-vars.md#paykilla). diff --git a/tests/test_security.py b/tests/test_security.py index fed1dc4..3cc5a43 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -256,6 +256,31 @@ class PaykillaServiceTests(unittest.TestCase): self.assertEqual(body["purpose"], "Minishop 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): + 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, + amount=100, + currency="RUB", + description="ignored", + ) + + self.assertEqual( + body["urls"], + [ + { + "type": "SUCCESS", + "url": "https://shop.example/pay/success", + "autoRedirect": True, + }, + {"type": "RETURN", "url": "https://shop.example/pay/return"}, + ], + ) def test_verify_webhook_signature_accepts_raw_body_signature(self): service = self._make_service()