Добавлена обработка уведомлений через ручку
This commit is contained in:
@@ -12,6 +12,7 @@ from app.adapters.delivery_providers.cdek.auth import CDEKAuthClient
|
||||
from app.adapters.delivery_providers.cdek.mapper import map_cdek_response
|
||||
from app.adapters.delivery_providers.cdek.order_mapper import (
|
||||
CDEKOrderMappingError,
|
||||
map_cdek_existing_order_response,
|
||||
map_cdek_order_request,
|
||||
map_cdek_order_response,
|
||||
)
|
||||
@@ -123,6 +124,11 @@ class CDEKClient:
|
||||
)
|
||||
|
||||
if 400 <= response.status_code < 500:
|
||||
existing_order_uuid = map_cdek_existing_order_response(
|
||||
_response_json_or_none(response)
|
||||
)
|
||||
if existing_order_uuid is not None:
|
||||
return existing_order_uuid
|
||||
raise CDEKRequestError(
|
||||
"CDEK order registration request was rejected with status "
|
||||
f"{response.status_code}."
|
||||
@@ -243,3 +249,10 @@ class CDEKProvider(DeliveryProvider):
|
||||
|
||||
async def register_order(self, request: InitPaymentRequest) -> str:
|
||||
return await self._client.register_order(request)
|
||||
|
||||
|
||||
def _response_json_or_none(response: httpx.Response) -> object | None:
|
||||
try:
|
||||
return response.json()
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
@@ -15,6 +15,7 @@ class CDEKOrderMappingError(ValueError):
|
||||
|
||||
def map_cdek_order_request(request: InitPaymentRequest) -> dict[str, Any]:
|
||||
payload: dict[str, Any] = {
|
||||
"number": request.order_uuid,
|
||||
"type": request.type,
|
||||
"tariff_code": request.tariff_code,
|
||||
"sender": _map_order_party(request.sender),
|
||||
@@ -44,6 +45,22 @@ def map_cdek_order_response(payload: dict[str, Any]) -> str:
|
||||
return order_uuid
|
||||
|
||||
|
||||
_CDEK_DUPLICATE_ORDER_ERROR_CODES = frozenset({"v2_entity_already_exists"})
|
||||
|
||||
|
||||
def map_cdek_existing_order_response(payload: object) -> str | None:
|
||||
if not isinstance(payload, dict):
|
||||
return None
|
||||
|
||||
if not _contains_cdek_duplicate_error_code(payload):
|
||||
return None
|
||||
|
||||
try:
|
||||
return map_cdek_order_response(payload)
|
||||
except CDEKOrderMappingError:
|
||||
return _find_first_uuid(payload)
|
||||
|
||||
|
||||
def _map_order_party(party: PaymentParty) -> dict[str, Any]:
|
||||
return {
|
||||
"name": party.name,
|
||||
@@ -56,3 +73,34 @@ def _map_order_package(package: PaymentPackage) -> dict[str, Any]:
|
||||
payload = package.model_dump(mode="python", exclude_none=True)
|
||||
payload["weight"] = package.weight * 1000
|
||||
return payload
|
||||
|
||||
|
||||
def _contains_cdek_duplicate_error_code(payload: object) -> bool:
|
||||
if isinstance(payload, dict):
|
||||
code = payload.get("code")
|
||||
if isinstance(code, str) and code in _CDEK_DUPLICATE_ORDER_ERROR_CODES:
|
||||
return True
|
||||
return any(
|
||||
_contains_cdek_duplicate_error_code(value)
|
||||
for value in payload.values()
|
||||
)
|
||||
if isinstance(payload, list):
|
||||
return any(_contains_cdek_duplicate_error_code(item) for item in payload)
|
||||
return False
|
||||
|
||||
|
||||
def _find_first_uuid(payload: object) -> str | None:
|
||||
if isinstance(payload, dict):
|
||||
value = payload.get("uuid")
|
||||
if isinstance(value, str) and value:
|
||||
return value
|
||||
for nested_value in payload.values():
|
||||
nested_uuid = _find_first_uuid(nested_value)
|
||||
if nested_uuid is not None:
|
||||
return nested_uuid
|
||||
if isinstance(payload, list):
|
||||
for item in payload:
|
||||
nested_uuid = _find_first_uuid(item)
|
||||
if nested_uuid is not None:
|
||||
return nested_uuid
|
||||
return None
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Delivery API controller skeleton."""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from fastapi.responses import PlainTextResponse
|
||||
|
||||
from app.adapters.postgres.engine import create_postgres_engine, create_postgres_session_factory
|
||||
from app.adapters.address_suggestions.dadata import DadataAddressSuggestionProvider
|
||||
@@ -14,7 +15,11 @@ from app.config import Settings
|
||||
from app.controllers.http_client import build_controller_http_client
|
||||
from app.repositories.cache.redis_cache import PriceCache
|
||||
from app.repositories.order import OrderRepository
|
||||
from app.schemas.payment import InitPaymentRequest, InitPaymentResponse
|
||||
from app.schemas.payment import (
|
||||
InitPaymentRequest,
|
||||
InitPaymentResponse,
|
||||
TBankPaymentNotification,
|
||||
)
|
||||
from app.schemas.request import AddressSuggestRequest, DeliveryCalculationRequest
|
||||
from app.schemas.response import AddressSuggestion, DeliveryPrice
|
||||
from app.services.aggregator import (
|
||||
@@ -24,7 +29,9 @@ from app.services.aggregator import (
|
||||
InvalidAddressSuggestRequestError,
|
||||
InvalidDeliveryRequestError,
|
||||
InvalidInitPaymentRequestError,
|
||||
InvalidTBankPaymentNotificationError,
|
||||
InitPaymentUnavailableError,
|
||||
TBankPaymentNotificationProcessingError,
|
||||
UnsupportedAddressSuggestionCountryError,
|
||||
)
|
||||
|
||||
@@ -68,6 +75,7 @@ def _build_aggregator_service(settings: Settings) -> AggregatorService:
|
||||
cache=cache,
|
||||
payment_adapter=payment_adapter,
|
||||
order_repository=order_repository,
|
||||
order_registration_adapter=cdek_provider,
|
||||
address_suggestion_providers=(
|
||||
dadata_provider,
|
||||
yandex_geosuggest_provider,
|
||||
@@ -191,3 +199,40 @@ async def init_payment(
|
||||
"message": "Payment initialization is temporarily unavailable.",
|
||||
},
|
||||
) from exc
|
||||
|
||||
|
||||
@router.post(
|
||||
"/tbank/notifications",
|
||||
response_class=PlainTextResponse,
|
||||
)
|
||||
async def handle_tbank_payment_notification(
|
||||
notification: TBankPaymentNotification,
|
||||
service: AggregatorService = Depends(get_aggregator_service),
|
||||
) -> PlainTextResponse:
|
||||
try:
|
||||
response_body = await service.handle_tbank_payment_notification(notification)
|
||||
except InvalidTBankPaymentNotificationError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"code": "invalid_tbank_payment_notification",
|
||||
"message": "TBank payment notification token is invalid.",
|
||||
},
|
||||
) from exc
|
||||
except TBankPaymentNotificationProcessingError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail={
|
||||
"code": "tbank_payment_notification_processing_unavailable",
|
||||
"message": "TBank payment notification processing is temporarily unavailable.",
|
||||
},
|
||||
) from exc
|
||||
except AggregatorServiceError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail={
|
||||
"code": "tbank_payment_notification_processing_unavailable",
|
||||
"message": "TBank payment notification processing is temporarily unavailable.",
|
||||
},
|
||||
) from exc
|
||||
return PlainTextResponse(response_body)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
"""Pure rules for TBank payment notification handling."""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class TBankPaymentNotificationAction(str, Enum):
|
||||
ACKNOWLEDGE_ONLY = "acknowledge_only"
|
||||
REGISTER_CDEK_ORDER = "register_cdek_order"
|
||||
|
||||
|
||||
def resolve_tbank_payment_notification_action(
|
||||
*,
|
||||
status: str,
|
||||
success: bool,
|
||||
error_code: str,
|
||||
) -> TBankPaymentNotificationAction:
|
||||
if status == "CONFIRMED" and success is True and error_code == "0":
|
||||
return TBankPaymentNotificationAction.REGISTER_CDEK_ORDER
|
||||
return TBankPaymentNotificationAction.ACKNOWLEDGE_ONLY
|
||||
@@ -4,7 +4,7 @@ from datetime import datetime
|
||||
from typing import Any
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from sqlalchemy import DateTime, Integer, String, UniqueConstraint, Uuid, func
|
||||
from sqlalchemy import BigInteger, DateTime, Integer, String, UniqueConstraint, Uuid, func
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||
from sqlalchemy.types import JSON
|
||||
@@ -56,8 +56,17 @@ class Order(Base):
|
||||
nullable=True,
|
||||
)
|
||||
comment: Mapped[str | None] = mapped_column(String(1024), nullable=True)
|
||||
payment_status: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
||||
tbank_payment_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
|
||||
cdek_order_uuid: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
nullable=False,
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ from contextlib import AbstractAsyncContextManager
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
|
||||
|
||||
from app.repositories.order.models import Order
|
||||
@@ -51,3 +52,43 @@ class OrderRepository:
|
||||
await session.flush()
|
||||
await session.refresh(order)
|
||||
return order
|
||||
|
||||
async def get_order_by_order_uuid(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
order_uuid: str,
|
||||
) -> Order | None:
|
||||
result = await session.execute(
|
||||
select(Order).where(Order.order_uuid == order_uuid)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def mark_payment_status(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
order_uuid: str,
|
||||
status: str,
|
||||
payment_id: int,
|
||||
) -> Order | None:
|
||||
order = await self.get_order_by_order_uuid(session, order_uuid)
|
||||
if order is None:
|
||||
return None
|
||||
|
||||
order.payment_status = status
|
||||
order.tbank_payment_id = payment_id
|
||||
await session.flush()
|
||||
return order
|
||||
|
||||
async def mark_cdek_order_registered(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
order_uuid: str,
|
||||
cdek_order_uuid: str,
|
||||
) -> Order | None:
|
||||
order = await self.get_order_by_order_uuid(session, order_uuid)
|
||||
if order is None:
|
||||
return None
|
||||
|
||||
order.cdek_order_uuid = cdek_order_uuid
|
||||
await session.flush()
|
||||
return order
|
||||
|
||||
@@ -21,7 +21,7 @@ def configure_logging(
|
||||
foreign_pre_chain=list(_shared_processors()),
|
||||
processors=[
|
||||
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
|
||||
structlog.processors.JSONRenderer(sort_keys=True),
|
||||
structlog.processors.JSONRenderer(sort_keys=True, ensure_ascii=False),
|
||||
],
|
||||
)
|
||||
handler = logging.StreamHandler(resolved_stream)
|
||||
|
||||
@@ -60,3 +60,16 @@ class InitPaymentRequest(BaseModel):
|
||||
|
||||
class InitPaymentResponse(BaseModel):
|
||||
payment_url: str = Field(min_length=1)
|
||||
|
||||
|
||||
class TBankPaymentNotification(BaseModel):
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
TerminalKey: str = Field(min_length=1)
|
||||
OrderId: str = Field(min_length=1)
|
||||
Success: bool
|
||||
Status: str = Field(min_length=1)
|
||||
PaymentId: int = Field(gt=0, strict=True)
|
||||
ErrorCode: str = Field(min_length=1)
|
||||
Amount: int
|
||||
Token: str = Field(min_length=1)
|
||||
|
||||
+215
-1
@@ -18,8 +18,13 @@ from app.adapters.address_suggestions.base import (
|
||||
from app.adapters.delivery_providers.base import DeliveryProvider, ProviderRequestError
|
||||
from app.adapters.tbank.base import (
|
||||
TBankPaymentAdapterError,
|
||||
TBankPaymentNotificationTokenError,
|
||||
TBankPaymentRequestError,
|
||||
)
|
||||
from app.domain.payment_notifications import (
|
||||
TBankPaymentNotificationAction,
|
||||
resolve_tbank_payment_notification_action,
|
||||
)
|
||||
from app.domain.price import (
|
||||
DEFAULT_PROVIDER_PRICE_MULTIPLIER,
|
||||
DEFAULT_WEIGHT_ROUND_SCALE,
|
||||
@@ -28,7 +33,11 @@ from app.domain.price import (
|
||||
normalize_delivery_request,
|
||||
)
|
||||
from app.repositories.order import OrderData
|
||||
from app.schemas.payment import InitPaymentRequest, InitPaymentResponse
|
||||
from app.schemas.payment import (
|
||||
InitPaymentRequest,
|
||||
InitPaymentResponse,
|
||||
TBankPaymentNotification,
|
||||
)
|
||||
from app.schemas.request import AddressSuggestRequest, DeliveryCalculationRequest
|
||||
from app.schemas.response import AddressSuggestion, DeliveryPrice
|
||||
|
||||
@@ -63,6 +72,14 @@ class InitPaymentUnavailableError(AggregatorServiceError):
|
||||
"""Raised when payment initialization cannot be completed."""
|
||||
|
||||
|
||||
class InvalidTBankPaymentNotificationError(AggregatorServiceError):
|
||||
"""Raised when a TBank payment notification is invalid."""
|
||||
|
||||
|
||||
class TBankPaymentNotificationProcessingError(AggregatorServiceError):
|
||||
"""Raised when a TBank payment notification cannot be processed."""
|
||||
|
||||
|
||||
class PriceCacheProtocol(Protocol):
|
||||
async def get(self, key: str) -> object | None: ...
|
||||
|
||||
@@ -72,12 +89,42 @@ class PriceCacheProtocol(Protocol):
|
||||
class PaymentAdapterProtocol(Protocol):
|
||||
async def create_payment_link(self, order_uuid: str, amount_kopecks: int) -> str: ...
|
||||
|
||||
def verify_payment_notification(
|
||||
self,
|
||||
notification: TBankPaymentNotification,
|
||||
) -> None: ...
|
||||
|
||||
|
||||
class OrderRegistrationAdapterProtocol(Protocol):
|
||||
async def register_order(self, request: InitPaymentRequest) -> str: ...
|
||||
|
||||
|
||||
class OrderRepositoryProtocol(Protocol):
|
||||
def session(self) -> AbstractAsyncContextManager[object]: ...
|
||||
|
||||
async def create_order(self, session: object, order_data: OrderData) -> object: ...
|
||||
|
||||
async def get_order_by_order_uuid(
|
||||
self,
|
||||
session: object,
|
||||
order_uuid: str,
|
||||
) -> object | None: ...
|
||||
|
||||
async def mark_payment_status(
|
||||
self,
|
||||
session: object,
|
||||
order_uuid: str,
|
||||
status: str,
|
||||
payment_id: int,
|
||||
) -> object | None: ...
|
||||
|
||||
async def mark_cdek_order_registered(
|
||||
self,
|
||||
session: object,
|
||||
order_uuid: str,
|
||||
cdek_order_uuid: str,
|
||||
) -> object | None: ...
|
||||
|
||||
|
||||
class FilterAndSortPricesFn(Protocol):
|
||||
def __call__(
|
||||
@@ -96,6 +143,7 @@ class AggregatorService:
|
||||
cache: PriceCacheProtocol | None = None,
|
||||
payment_adapter: PaymentAdapterProtocol | None = None,
|
||||
order_repository: OrderRepositoryProtocol | None = None,
|
||||
order_registration_adapter: OrderRegistrationAdapterProtocol | None = None,
|
||||
address_suggestion_providers: Sequence[AddressSuggestionProvider] = (),
|
||||
address_suggestion_country_to_provider: Mapping[str, str] | None = None,
|
||||
*,
|
||||
@@ -107,6 +155,7 @@ class AggregatorService:
|
||||
self._cache = cache
|
||||
self._payment_adapter = payment_adapter
|
||||
self._order_repository = order_repository
|
||||
self._order_registration_adapter = order_registration_adapter
|
||||
self._weight_round_scale = weight_round_scale
|
||||
self._provider_price_multiplier = provider_price_multiplier
|
||||
self._filter_and_sort_prices = filter_and_sort_prices_fn
|
||||
@@ -217,6 +266,155 @@ class AggregatorService:
|
||||
await self._persist_order(request=request, payment_url=payment_url)
|
||||
return InitPaymentResponse(payment_url=payment_url)
|
||||
|
||||
async def handle_tbank_payment_notification(
|
||||
self,
|
||||
notification: TBankPaymentNotification,
|
||||
) -> str:
|
||||
if self._payment_adapter is None:
|
||||
raise TBankPaymentNotificationProcessingError(
|
||||
"Payment adapter is not configured."
|
||||
)
|
||||
|
||||
try:
|
||||
self._payment_adapter.verify_payment_notification(notification)
|
||||
except TBankPaymentNotificationTokenError as exc:
|
||||
raise InvalidTBankPaymentNotificationError(
|
||||
"TBank payment notification token is invalid."
|
||||
) from exc
|
||||
except TBankPaymentAdapterError as exc:
|
||||
raise TBankPaymentNotificationProcessingError(
|
||||
"TBank payment notification verification failed."
|
||||
) from exc
|
||||
|
||||
action = resolve_tbank_payment_notification_action(
|
||||
status=notification.Status,
|
||||
success=notification.Success,
|
||||
error_code=notification.ErrorCode,
|
||||
)
|
||||
order = await self._load_order_and_mark_payment_status(notification)
|
||||
|
||||
if action is TBankPaymentNotificationAction.ACKNOWLEDGE_ONLY:
|
||||
return "OK"
|
||||
|
||||
existing_cdek_order_uuid = getattr(order, "cdek_order_uuid", None)
|
||||
if existing_cdek_order_uuid:
|
||||
return "OK"
|
||||
|
||||
cdek_order_uuid = await self._register_cdek_order(order)
|
||||
await self._save_cdek_order_uuid(
|
||||
order_uuid=notification.OrderId,
|
||||
cdek_order_uuid=cdek_order_uuid,
|
||||
)
|
||||
return "OK"
|
||||
|
||||
async def _load_order_and_mark_payment_status(
|
||||
self,
|
||||
notification: TBankPaymentNotification,
|
||||
) -> object:
|
||||
if self._order_repository is None:
|
||||
raise TBankPaymentNotificationProcessingError(
|
||||
"Order repository is not configured."
|
||||
)
|
||||
|
||||
try:
|
||||
async with self._order_repository.session() as session:
|
||||
order = await self._order_repository.get_order_by_order_uuid(
|
||||
session,
|
||||
notification.OrderId,
|
||||
)
|
||||
if order is None:
|
||||
logger.warning(
|
||||
"tbank_payment_notification_order_not_found",
|
||||
order_uuid=notification.OrderId,
|
||||
)
|
||||
raise TBankPaymentNotificationProcessingError(
|
||||
"Order was not found for TBank payment notification."
|
||||
)
|
||||
updated_order = await self._order_repository.mark_payment_status(
|
||||
session,
|
||||
notification.OrderId,
|
||||
notification.Status,
|
||||
notification.PaymentId,
|
||||
)
|
||||
if updated_order is None:
|
||||
logger.warning(
|
||||
"tbank_payment_notification_order_not_found",
|
||||
order_uuid=notification.OrderId,
|
||||
)
|
||||
raise TBankPaymentNotificationProcessingError(
|
||||
"Order was not found for TBank payment notification."
|
||||
)
|
||||
return order
|
||||
except TBankPaymentNotificationProcessingError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
logger.exception(
|
||||
"tbank_payment_notification_order_update_failed",
|
||||
order_uuid=notification.OrderId,
|
||||
payment_status=notification.Status,
|
||||
tbank_payment_id=notification.PaymentId,
|
||||
)
|
||||
raise TBankPaymentNotificationProcessingError(
|
||||
"TBank payment notification order update failed."
|
||||
) from exc
|
||||
|
||||
async def _register_cdek_order(self, order: object) -> str:
|
||||
if self._order_registration_adapter is None:
|
||||
raise TBankPaymentNotificationProcessingError(
|
||||
"CDEK order registration adapter is not configured."
|
||||
)
|
||||
|
||||
try:
|
||||
request = self._to_init_payment_request_from_order(order)
|
||||
return await self._order_registration_adapter.register_order(request)
|
||||
except Exception as exc:
|
||||
logger.exception(
|
||||
"cdek_order_registration_failed",
|
||||
order_uuid=getattr(order, "order_uuid", None),
|
||||
)
|
||||
raise TBankPaymentNotificationProcessingError(
|
||||
"CDEK order registration failed."
|
||||
) from exc
|
||||
|
||||
async def _save_cdek_order_uuid(
|
||||
self,
|
||||
*,
|
||||
order_uuid: str,
|
||||
cdek_order_uuid: str,
|
||||
) -> None:
|
||||
if self._order_repository is None:
|
||||
raise TBankPaymentNotificationProcessingError(
|
||||
"Order repository is not configured."
|
||||
)
|
||||
|
||||
try:
|
||||
async with self._order_repository.session() as session:
|
||||
order = await self._order_repository.mark_cdek_order_registered(
|
||||
session,
|
||||
order_uuid,
|
||||
cdek_order_uuid,
|
||||
)
|
||||
if order is None:
|
||||
logger.warning(
|
||||
"cdek_order_uuid_order_not_found",
|
||||
order_uuid=order_uuid,
|
||||
cdek_order_uuid=cdek_order_uuid,
|
||||
)
|
||||
raise TBankPaymentNotificationProcessingError(
|
||||
"Order was not found while saving CDEK order UUID."
|
||||
)
|
||||
except TBankPaymentNotificationProcessingError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
logger.exception(
|
||||
"cdek_order_uuid_persistence_failed",
|
||||
order_uuid=order_uuid,
|
||||
cdek_order_uuid=cdek_order_uuid,
|
||||
)
|
||||
raise TBankPaymentNotificationProcessingError(
|
||||
"CDEK order UUID persistence failed."
|
||||
) from exc
|
||||
|
||||
async def _persist_order(
|
||||
self,
|
||||
*,
|
||||
@@ -260,6 +458,22 @@ class AggregatorService:
|
||||
comment=request.comment,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _to_init_payment_request_from_order(order: object) -> InitPaymentRequest:
|
||||
return InitPaymentRequest(
|
||||
order_uuid=getattr(order, "order_uuid"),
|
||||
price=getattr(order, "price"),
|
||||
type=getattr(order, "delivery_type"),
|
||||
tariff_code=getattr(order, "tariff_code"),
|
||||
comment=getattr(order, "comment"),
|
||||
sender=getattr(order, "sender"),
|
||||
recipient=getattr(order, "recipient"),
|
||||
from_location=getattr(order, "from_location"),
|
||||
to_location=getattr(order, "to_location"),
|
||||
services=getattr(order, "services"),
|
||||
packages=getattr(order, "packages"),
|
||||
)
|
||||
|
||||
async def _get_provider_prices(
|
||||
self,
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user