fix: remove PayKilla redirect fields
This commit is contained in:
@@ -182,9 +182,6 @@ class PaykillaConfig(ProviderEnvConfig):
|
|||||||
INVOICE_TYPE: Optional[str] = None
|
INVOICE_TYPE: Optional[str] = None
|
||||||
PAYMENT_CURRENCIES: str = Field(default=PAYKILLA_DEFAULT_PAYMENT_CURRENCIES)
|
PAYMENT_CURRENCIES: str = Field(default=PAYKILLA_DEFAULT_PAYMENT_CURRENCIES)
|
||||||
SUPPORTED_CURRENCIES: str = Field(default=PAYKILLA_DEFAULT_SUPPORTED_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)
|
LIFETIME_SECONDS: int = Field(default=3600)
|
||||||
RECV_WINDOW_MS: int = Field(default=5000)
|
RECV_WINDOW_MS: int = Field(default=5000)
|
||||||
USER_PAYS_SERVICE_FEE: bool = Field(default=True)
|
USER_PAYS_SERVICE_FEE: bool = Field(default=True)
|
||||||
@@ -219,9 +216,6 @@ class PaykillaConfig(ProviderEnvConfig):
|
|||||||
"API_KEY",
|
"API_KEY",
|
||||||
"SECRET_KEY",
|
"SECRET_KEY",
|
||||||
"INVOICE_TYPE",
|
"INVOICE_TYPE",
|
||||||
"RETURN_URL",
|
|
||||||
"SUCCESS_URL",
|
|
||||||
"CANCEL_URL",
|
|
||||||
"WEBHOOK_URL",
|
"WEBHOOK_URL",
|
||||||
mode="before",
|
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"
|
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(
|
return _clean_paykilla_text(
|
||||||
f"Minishop payment {payment_db_id}",
|
f"{project_title} payment {payment_db_id}",
|
||||||
fallback=f"Payment {payment_db_id}",
|
fallback=f"Payment {payment_db_id}",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -403,18 +398,6 @@ class PaykillaService(HttpClientMixin):
|
|||||||
def currency(self) -> str:
|
def currency(self) -> str:
|
||||||
return (self.config.CURRENCY or "RUB").upper()
|
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
|
@property
|
||||||
def verify_webhook_signature(self) -> bool:
|
def verify_webhook_signature(self) -> bool:
|
||||||
return self.config.VERIFY_WEBHOOK_SIGNATURE
|
return self.config.VERIFY_WEBHOOK_SIGNATURE
|
||||||
@@ -434,7 +417,7 @@ class PaykillaService(HttpClientMixin):
|
|||||||
description: str,
|
description: str,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
currency_code = normalize_payment_currency_code(currency or self.currency)
|
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] = {
|
body: Dict[str, Any] = {
|
||||||
"type": _invoice_type_for(self.config, currency_code),
|
"type": _invoice_type_for(self.config, currency_code),
|
||||||
"purpose": invoice_text,
|
"purpose": invoice_text,
|
||||||
@@ -451,16 +434,6 @@ class PaykillaService(HttpClientMixin):
|
|||||||
seconds=int(self.config.LIFETIME_SECONDS)
|
seconds=int(self.config.LIFETIME_SECONDS)
|
||||||
)
|
)
|
||||||
body["expiredAt"] = expires_at.isoformat().replace("+00:00", "Z")
|
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
|
return body
|
||||||
|
|
||||||
async def create_payment_link(
|
async def create_payment_link(
|
||||||
@@ -1053,15 +1026,6 @@ _CONFIG_MANIFEST = (
|
|||||||
("OPEN_AMOUNT", "OPEN_AMOUNT"),
|
("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(
|
ProviderManifestField(
|
||||||
"PAYKILLA_LIFETIME_SECONDS",
|
"PAYKILLA_LIFETIME_SECONDS",
|
||||||
"int",
|
"int",
|
||||||
|
|||||||
@@ -380,9 +380,6 @@ Webhook настраивается в PayKilla Dashboard: **Settings -> Webhooks
|
|||||||
| `PAYKILLA_PAYMENT_CURRENCIES` | Crypto tickers для оплаты, например `USDTTRC,BTC,ETH`. |
|
| `PAYKILLA_PAYMENT_CURRENCIES` | Crypto tickers для оплаты, например `USDTTRC,BTC,ETH`. |
|
||||||
| `PAYKILLA_SUPPORTED_CURRENCIES` | Валюты инвойса, разрешенные в этом магазине. |
|
| `PAYKILLA_SUPPORTED_CURRENCIES` | Валюты инвойса, разрешенные в этом магазине. |
|
||||||
| `PAYKILLA_INVOICE_TYPE` | Необязательный override: `FIAT_BASED`, `FIXED_AMOUNT` или `OPEN_AMOUNT`. |
|
| `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_LIFETIME_SECONDS` | TTL инвойса, отправляется как `expiredAt`. |
|
||||||
| `PAYKILLA_RECV_WINDOW_MS` | `recvWindow` для подписанных API-запросов. |
|
| `PAYKILLA_RECV_WINDOW_MS` | `recvWindow` для подписанных API-запросов. |
|
||||||
| `PAYKILLA_USER_PAYS_SERVICE_FEE` | `true`, если пользователь оплачивает service fee. |
|
| `PAYKILLA_USER_PAYS_SERVICE_FEE` | `true`, если пользователь оплачивает service fee. |
|
||||||
|
|||||||
@@ -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 используется для крипто-инвойсов 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 <id>`, а локализованное описание платежа оставляет только внутри Minishop. Дополнительно эти поля проходят ASCII-safe sanitizer: допускаются ASCII-буквы, цифры, пробелы, `_`, `.`, `,`.
|
PayKilla строго валидирует текстовые поля invoice. Поэтому Minishop отправляет в `purpose` и `description` простой английский текст `<WEBAPP_TITLE> payment <id>`, а локализованное описание платежа оставляет только внутри Minishop. Дополнительно эти поля проходят ASCII-safe sanitizer: допускаются ASCII-буквы, цифры, пробелы, `_`, `.`, `,`.
|
||||||
|
|
||||||
Какие полномочия нужны API key:
|
Какие полномочия нужны API key:
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ PayKilla строго валидирует текстовые поля invoice.
|
|||||||
1. Включите `PAYKILLA_ENABLED`.
|
1. Включите `PAYKILLA_ENABLED`.
|
||||||
2. Укажите `PAYKILLA_API_KEY` и `PAYKILLA_SECRET_KEY`.
|
2. Укажите `PAYKILLA_API_KEY` и `PAYKILLA_SECRET_KEY`.
|
||||||
3. Проверьте `PAYKILLA_CURRENCY` и `PAYKILLA_PAYMENT_CURRENCIES`, например `USDTTRC,BTC,ETH`.
|
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`, если хотите задать явный порядок кнопок.
|
5. Добавьте `paykilla` в `PAYMENT_METHODS_ORDER`, если хотите задать явный порядок кнопок.
|
||||||
|
|
||||||
Справочник переменных: [PayKilla](../configuration/env-vars.md#paykilla).
|
Справочник переменных: [PayKilla](../configuration/env-vars.md#paykilla).
|
||||||
|
|||||||
@@ -4090,81 +4090,6 @@
|
|||||||
"updated_at": null,
|
"updated_at": null,
|
||||||
"webhook_base_url_configured": false
|
"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",
|
"key": "PAYKILLA_LIFETIME_SECONDS",
|
||||||
"type": "int",
|
"type": "int",
|
||||||
|
|||||||
+5
-15
@@ -218,6 +218,7 @@ class PaykillaServiceTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
service.settings = SimpleNamespace(
|
service.settings = SimpleNamespace(
|
||||||
WEBHOOK_BASE_URL="https://shop.example",
|
WEBHOOK_BASE_URL="https://shop.example",
|
||||||
|
WEBAPP_TITLE="/minishop",
|
||||||
trusted_proxies=["127.0.0.1"],
|
trusted_proxies=["127.0.0.1"],
|
||||||
)
|
)
|
||||||
service._default_return_url = "test_bot"
|
service._default_return_url = "test_bot"
|
||||||
@@ -245,6 +246,7 @@ class PaykillaServiceTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_invoice_body_uses_english_purpose_and_description(self):
|
def test_invoice_body_uses_english_purpose_and_description(self):
|
||||||
service = self._make_service()
|
service = self._make_service()
|
||||||
|
service.settings.WEBAPP_TITLE = "Tunnel Shop"
|
||||||
|
|
||||||
body = service._invoice_body(
|
body = service._invoice_body(
|
||||||
payment_db_id=556,
|
payment_db_id=556,
|
||||||
@@ -253,15 +255,13 @@ class PaykillaServiceTests(unittest.TestCase):
|
|||||||
description="Оплата подписки на 1 мес. - тариф «Базовый» ✅",
|
description="Оплата подписки на 1 мес. - тариф «Базовый» ✅",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(body["purpose"], "Minishop payment 556")
|
self.assertEqual(body["purpose"], "Tunnel Shop payment 556")
|
||||||
self.assertEqual(body["description"], body["purpose"])
|
self.assertEqual(body["description"], body["purpose"])
|
||||||
self.assertRegex(body["purpose"], r"^[A-Za-z0-9_\s.,]+$")
|
self.assertRegex(body["purpose"], r"^[A-Za-z0-9_\s.,]+$")
|
||||||
self.assertNotIn("urls", body)
|
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 = 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(
|
body = service._invoice_body(
|
||||||
payment_db_id=556,
|
payment_db_id=556,
|
||||||
@@ -270,17 +270,7 @@ class PaykillaServiceTests(unittest.TestCase):
|
|||||||
description="ignored",
|
description="ignored",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertNotIn("urls", body)
|
||||||
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):
|
def test_verify_webhook_signature_accepts_raw_body_signature(self):
|
||||||
service = self._make_service()
|
service = self._make_service()
|
||||||
|
|||||||
Reference in New Issue
Block a user