Добавлена валидация цены

This commit is contained in:
Раис Юсупалиев
2026-05-13 16:35:22 +03:00
parent 6b66af5eb3
commit 39cd5ddc7a
24 changed files with 1021 additions and 56 deletions
+99 -1
View File
@@ -15,7 +15,11 @@ from app.adapters.address_suggestions.base import (
AddressSuggestionProvider,
AddressSuggestionRequestError,
)
from app.adapters.delivery_providers.base import DeliveryProvider, ProviderRequestError
from app.adapters.delivery_providers.base import (
DeliveryProvider,
ProviderClientError,
ProviderRequestError,
)
from app.adapters.tbank.base import (
TBankPaymentAdapterError,
TBankPaymentNotificationTokenError,
@@ -29,7 +33,9 @@ from app.domain.price import (
DEFAULT_PROVIDER_PRICE_MULTIPLIER,
DEFAULT_WEIGHT_ROUND_SCALE,
NormalizedDeliveryRequest,
calculate_expected_payment_amount_kopecks,
filter_and_sort_prices,
is_init_payment_price_valid,
normalize_delivery_request,
)
from app.repositories.order import OrderData
@@ -95,6 +101,13 @@ class PaymentAdapterProtocol(Protocol):
) -> None: ...
class PaymentPriceValidationAdapterProtocol(Protocol):
async def get_payment_price(
self,
request: InitPaymentRequest,
) -> DeliveryPrice | None: ...
class OrderRegistrationAdapterProtocol(Protocol):
async def register_order(self, request: InitPaymentRequest) -> str: ...
@@ -142,6 +155,9 @@ class AggregatorService:
providers: Sequence[DeliveryProvider],
cache: PriceCacheProtocol | None = None,
payment_adapter: PaymentAdapterProtocol | None = None,
payment_price_validation_adapter: (
PaymentPriceValidationAdapterProtocol | None
) = None,
order_repository: OrderRepositoryProtocol | None = None,
order_registration_adapter: OrderRegistrationAdapterProtocol | None = None,
address_suggestion_providers: Sequence[AddressSuggestionProvider] = (),
@@ -154,6 +170,7 @@ class AggregatorService:
self._providers = tuple(providers)
self._cache = cache
self._payment_adapter = payment_adapter
self._payment_price_validation_adapter = payment_price_validation_adapter
self._order_repository = order_repository
self._order_registration_adapter = order_registration_adapter
self._weight_round_scale = weight_round_scale
@@ -237,6 +254,8 @@ class AggregatorService:
if self._payment_adapter is None:
raise InitPaymentUnavailableError("Payment adapter is not configured.")
await self._validate_init_payment_price(request)
try:
payment_url = await self._payment_adapter.create_payment_link(
order_uuid=request.order_uuid,
@@ -266,6 +285,85 @@ class AggregatorService:
await self._persist_order(request=request, payment_url=payment_url)
return InitPaymentResponse(payment_url=payment_url)
async def _validate_init_payment_price(
self,
request: InitPaymentRequest,
) -> None:
if self._payment_price_validation_adapter is None:
raise InitPaymentUnavailableError(
"Payment price validation adapter is not configured."
)
try:
provider_price = await self._payment_price_validation_adapter.get_payment_price(
request
)
except ProviderRequestError as exc:
logger.warning(
"init_payment_price_validation_request_rejected",
order_uuid=request.order_uuid,
tariff_code=request.tariff_code,
requested_price_kopecks=request.price,
error=str(exc),
)
raise InvalidInitPaymentRequestError(
"Payment init request is invalid for CDEK price validation."
) from exc
except ProviderClientError as exc:
logger.warning(
"init_payment_price_validation_unavailable",
order_uuid=request.order_uuid,
tariff_code=request.tariff_code,
requested_price_kopecks=request.price,
error=str(exc),
)
raise InitPaymentUnavailableError(
"Payment price validation is temporarily unavailable."
) from exc
except Exception as exc:
logger.exception(
"init_payment_price_validation_unexpected_error",
order_uuid=request.order_uuid,
tariff_code=request.tariff_code,
requested_price_kopecks=request.price,
)
raise InitPaymentUnavailableError(
"Payment price validation is temporarily unavailable."
) from exc
if provider_price is None:
logger.warning(
"init_payment_price_validation_tariff_not_found",
order_uuid=request.order_uuid,
tariff_code=request.tariff_code,
requested_price_kopecks=request.price,
)
raise InvalidInitPaymentRequestError(
"CDEK did not return the requested tariff for payment validation."
)
expected_amount_kopecks = calculate_expected_payment_amount_kopecks(
provider_price,
price_multiplier=self._provider_price_multiplier,
)
if not is_init_payment_price_valid(
request.price,
provider_price,
price_multiplier=self._provider_price_multiplier,
):
logger.warning(
"init_payment_price_mismatch",
order_uuid=request.order_uuid,
tariff_code=request.tariff_code,
requested_price_kopecks=request.price,
expected_price_kopecks=expected_amount_kopecks,
provider_currency=getattr(provider_price, "currency", None),
provider_price=str(getattr(provider_price, "price", None)),
)
raise InvalidInitPaymentRequestError(
"Payment amount does not match CDEK validated delivery price."
)
async def handle_tbank_payment_notification(
self,
notification: TBankPaymentNotification,