Добавлена валидация цены
This commit is contained in:
@@ -3,13 +3,21 @@
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any
|
||||
import logging
|
||||
|
||||
import httpx
|
||||
import structlog
|
||||
|
||||
from app.adapters.delivery_providers.base import DeliveryProvider, ProviderRequestError
|
||||
from app.adapters.delivery_providers.base import (
|
||||
DeliveryProvider,
|
||||
ProviderClientError,
|
||||
ProviderRequestError,
|
||||
)
|
||||
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.mapper import (
|
||||
CDEKMappingError,
|
||||
map_cdek_response,
|
||||
map_cdek_response_for_tariff_code,
|
||||
)
|
||||
from app.adapters.delivery_providers.cdek.order_mapper import (
|
||||
CDEKOrderMappingError,
|
||||
map_cdek_existing_order_response,
|
||||
@@ -21,10 +29,10 @@ from app.config import AdapterConfig
|
||||
from app.schemas.payment import InitPaymentRequest
|
||||
from app.schemas.request import DeliveryCalculationRequest
|
||||
from app.schemas.response import DeliveryPrice
|
||||
log = logging.getLogger(__name__)
|
||||
log = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class CDEKClientError(RuntimeError):
|
||||
class CDEKClientError(ProviderClientError):
|
||||
"""Raised when CDEK tariff request fails."""
|
||||
|
||||
|
||||
@@ -58,6 +66,29 @@ class CDEKClient:
|
||||
self, request: DeliveryCalculationRequest
|
||||
) -> dict[str, Any]:
|
||||
payload = await self._build_payload(request)
|
||||
return await self._post_tariff_payload(
|
||||
payload,
|
||||
request_error_message=None,
|
||||
)
|
||||
|
||||
async def get_raw_payment_price(
|
||||
self,
|
||||
request: InitPaymentRequest,
|
||||
) -> dict[str, Any]:
|
||||
payload = self._build_payment_price_payload(request)
|
||||
return await self._post_tariff_payload(
|
||||
payload,
|
||||
request_error_message=(
|
||||
"CDEK payment price validation request was rejected with status"
|
||||
),
|
||||
)
|
||||
|
||||
async def _post_tariff_payload(
|
||||
self,
|
||||
payload: dict[str, Any],
|
||||
*,
|
||||
request_error_message: str | None,
|
||||
) -> dict[str, Any]:
|
||||
for attempt in range(self._retry_attempts + 1):
|
||||
try:
|
||||
token = await self._auth_client.get_access_token()
|
||||
@@ -79,10 +110,27 @@ class CDEKClient:
|
||||
if attempt < self._retry_attempts:
|
||||
await self._sleep(self._retry_delay(attempt))
|
||||
continue
|
||||
log.warning(
|
||||
"cdek_tariff_request_server_error",
|
||||
status_code=response.status_code,
|
||||
response_body=_response_text_or_none(response),
|
||||
request_payload=payload,
|
||||
)
|
||||
raise CDEKClientError(
|
||||
f"CDEK tariff request failed with status {response.status_code}."
|
||||
)
|
||||
|
||||
if request_error_message is not None and 400 <= response.status_code < 500:
|
||||
log.warning(
|
||||
"cdek_tariff_request_rejected",
|
||||
status_code=response.status_code,
|
||||
response_body=_response_text_or_none(response),
|
||||
request_payload=payload,
|
||||
)
|
||||
raise CDEKRequestError(
|
||||
f"{request_error_message} {response.status_code}."
|
||||
)
|
||||
|
||||
try:
|
||||
response.raise_for_status()
|
||||
raw_payload = response.json()
|
||||
@@ -182,6 +230,30 @@ class CDEKClient:
|
||||
],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _build_payment_price_payload(
|
||||
request: InitPaymentRequest,
|
||||
) -> dict[str, Any]:
|
||||
payload: dict[str, Any] = {
|
||||
"type": request.type,
|
||||
"from_location": request.from_location.model_dump(mode="python"),
|
||||
"to_location": request.to_location.model_dump(mode="python"),
|
||||
"packages": [
|
||||
{
|
||||
"weight": package.weight * 1000,
|
||||
"length": package.length,
|
||||
"width": package.width,
|
||||
"height": package.height,
|
||||
}
|
||||
for package in request.packages
|
||||
],
|
||||
}
|
||||
if request.services is not None:
|
||||
payload["services"] = [
|
||||
service.model_dump(mode="python") for service in request.services
|
||||
]
|
||||
return payload
|
||||
|
||||
@staticmethod
|
||||
def _get_cdek_city_code(city_id: int) -> int:
|
||||
city_entry = cities_map.get(str(city_id))
|
||||
@@ -247,6 +319,21 @@ class CDEKProvider(DeliveryProvider):
|
||||
raw_payload = await self._client.get_raw_price(request)
|
||||
return map_cdek_response(raw_payload)
|
||||
|
||||
async def get_payment_price(
|
||||
self,
|
||||
request: InitPaymentRequest,
|
||||
) -> DeliveryPrice | None:
|
||||
raw_payload = await self._client.get_raw_payment_price(request)
|
||||
try:
|
||||
return map_cdek_response_for_tariff_code(
|
||||
raw_payload,
|
||||
tariff_code=request.tariff_code,
|
||||
)
|
||||
except CDEKMappingError as exc:
|
||||
raise CDEKClientError(
|
||||
"CDEK payment price validation response payload is invalid."
|
||||
) from exc
|
||||
|
||||
async def register_order(self, request: InitPaymentRequest) -> str:
|
||||
return await self._client.register_order(request)
|
||||
|
||||
@@ -256,3 +343,10 @@ def _response_json_or_none(response: httpx.Response) -> object | None:
|
||||
return response.json()
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _response_text_or_none(response: httpx.Response) -> str | None:
|
||||
try:
|
||||
return response.text
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user