Добавлена обработка уведомлений через ручку
This commit is contained in:
+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