Добавлена обработка уведомлений через ручку

This commit is contained in:
Раис Юсупалиев
2026-04-18 21:27:15 +03:00
parent 78e9ad4fa9
commit 6b66af5eb3
38 changed files with 1519 additions and 32 deletions
+4
View File
@@ -22,3 +22,7 @@ class TBankPaymentRequestError(TBankPaymentAdapterError):
self.error_code = error_code
self.provider_message = provider_message
self.details = details
class TBankPaymentNotificationTokenError(TBankPaymentRequestError):
"""Raised when a TBank payment notification token is invalid."""
+21 -1
View File
@@ -3,15 +3,18 @@
import asyncio
from collections.abc import Awaitable, Callable
import hashlib
import hmac
from typing import Any
import httpx
from app.adapters.tbank.base import (
TBankPaymentAdapterError,
TBankPaymentNotificationTokenError,
TBankPaymentRequestError,
)
from app.config import TBankPaymentConfig
from app.schemas.payment import TBankPaymentNotification
class TBankAdapter:
@@ -112,6 +115,17 @@ class TBankAdapter:
raise TBankPaymentAdapterError("TBank payment init request failed unexpectedly.")
def verify_payment_notification(
self,
notification: TBankPaymentNotification,
) -> None:
payload = notification.model_dump(mode="python")
expected_token = _build_tbank_token(payload, password=self._password)
if not hmac.compare_digest(expected_token, notification.Token):
raise TBankPaymentNotificationTokenError(
"TBank payment notification token is invalid."
)
def _build_payload(self, *, order_uuid: str, amount_kopecks: int) -> dict[str, Any]:
if not order_uuid.strip():
raise TBankPaymentRequestError("TBank payment order id must be non-empty.")
@@ -164,11 +178,17 @@ def _build_tbank_token(payload: dict[str, Any], *, password: str) -> str:
}
token_payload["Password"] = password
token_source = "".join(
str(token_payload[key]) for key in sorted(token_payload)
_stringify_token_value(token_payload[key]) for key in sorted(token_payload)
)
return hashlib.sha256(token_source.encode("utf-8")).hexdigest()
def _stringify_token_value(value: Any) -> str:
if isinstance(value, bool):
return "true" if value else "false"
return str(value)
def _response_json_or_none(response: httpx.Response) -> object | None:
try:
return response.json()