Добавлен заказ накладной

This commit is contained in:
Раис Юсупалиев
2026-05-23 17:29:56 +03:00
parent 39cd5ddc7a
commit c494d50566
21 changed files with 1384 additions and 1126 deletions
+49 -25
View File
@@ -20,9 +20,13 @@ from app.adapters.delivery_providers.cdek.mapper import (
)
from app.adapters.delivery_providers.cdek.order_mapper import (
CDEKOrderMappingError,
CDEKOrderRegistrationResult,
centimeters_string_to_int,
kilograms_string_to_grams,
map_cdek_existing_order_response,
map_cdek_order_request,
map_cdek_order_response,
resolve_cdek_city_code,
)
from app.cities import cities_map
from app.config import AdapterConfig
@@ -143,7 +147,9 @@ class CDEKClient:
raise CDEKClientError("CDEK tariff request failed unexpectedly.")
async def register_order(self, request: InitPaymentRequest) -> str:
async def register_order(
self, request: InitPaymentRequest
) -> CDEKOrderRegistrationResult:
payload = map_cdek_order_request(request)
for attempt in range(self._retry_attempts + 1):
try:
@@ -172,11 +178,17 @@ class CDEKClient:
)
if 400 <= response.status_code < 500:
existing_order_uuid = map_cdek_existing_order_response(
_response_json_or_none(response)
response_payload = _response_json_or_none(response)
existing_result = map_cdek_existing_order_response(response_payload)
if existing_result is not None:
return existing_result
log.warning(
"cdek_order_register_rejected",
status_code=response.status_code,
request_payload=payload,
response_payload=response_payload,
response_body=_response_text_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}."
@@ -195,6 +207,12 @@ class CDEKClient:
"CDEK order registration payload must be a JSON object."
)
log.info(
"cdek_order_register_raw_response",
status_code=response.status_code,
response_payload=raw_payload,
)
try:
return map_cdek_order_response(raw_payload)
except CDEKOrderMappingError as exc:
@@ -234,25 +252,29 @@ class CDEKClient:
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
],
try:
from_code = resolve_cdek_city_code(request.sender_address.city_id)
to_code = resolve_cdek_city_code(request.receiver_address.city_id)
weight_grams = kilograms_string_to_grams(request.system_data.weight)
except CDEKOrderMappingError as exc:
raise CDEKRequestError(str(exc)) from exc
package: dict[str, Any] = {"weight": weight_grams}
dimensions = request.system_data.dimensions
if request.system_data.parcel_type == "parcel" and dimensions is not None:
try:
package["length"] = centimeters_string_to_int(dimensions.length, "length")
package["width"] = centimeters_string_to_int(dimensions.width, "width")
package["height"] = centimeters_string_to_int(dimensions.height, "height")
except CDEKOrderMappingError as exc:
raise CDEKRequestError(str(exc)) from exc
return {
"type": 2,
"from_location": {"code": from_code},
"to_location": {"code": to_code},
"packages": [package],
}
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:
@@ -327,14 +349,16 @@ class CDEKProvider(DeliveryProvider):
try:
return map_cdek_response_for_tariff_code(
raw_payload,
tariff_code=request.tariff_code,
tariff_code=request.system_data.tariff.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:
async def register_order(
self, request: InitPaymentRequest
) -> CDEKOrderRegistrationResult:
return await self._client.register_order(request)
@@ -1,11 +1,16 @@
"""CDEK order registration payload mappers."""
from dataclasses import dataclass
from decimal import ROUND_HALF_UP, Decimal, InvalidOperation
from typing import Any
from app.cities import cities_map
from app.schemas.payment import (
Address,
Contact,
Dimensions,
InitPaymentRequest,
PaymentPackage,
PaymentParty,
SystemData,
)
@@ -13,27 +18,35 @@ class CDEKOrderMappingError(ValueError):
"""Raised when CDEK order payload cannot be mapped."""
_CDEK_WAYBILL_PRINT_TYPE = "WAYBILL"
_CDEK_WAYBILL_RELATED_ENTITY_TYPE = "waybill"
@dataclass(frozen=True)
class CDEKOrderRegistrationResult:
order_uuid: str
waybill_uuid: str | None
waybill_url: str | None
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),
"recipient": _map_order_party(request.recipient),
"from_location": request.from_location.model_dump(mode="python"),
"to_location": request.to_location.model_dump(mode="python"),
"packages": [_map_order_package(package) for package in request.packages],
"type": 2,
"tariff_code": request.system_data.tariff.tariff_code,
"print": _CDEK_WAYBILL_PRINT_TYPE,
"sender": _map_party(request.sender_contact),
"recipient": _map_party(request.receiver_contact),
"from_location": _map_location(request.sender_address),
"to_location": _map_location(request.receiver_address),
"packages": [_map_package(request)],
}
if request.comment is not None:
payload["comment"] = request.comment
if request.services is not None:
payload["services"] = [
service.model_dump(mode="python") for service in request.services
]
if request.content.description:
payload["comment"] = request.content.description
return payload
def map_cdek_order_response(payload: dict[str, Any]) -> str:
def map_cdek_order_response(payload: dict[str, Any]) -> CDEKOrderRegistrationResult:
entity = payload.get("entity")
if not isinstance(entity, dict):
raise CDEKOrderMappingError("CDEK order response must include entity object.")
@@ -42,13 +55,20 @@ def map_cdek_order_response(payload: dict[str, Any]) -> str:
if not isinstance(order_uuid, str) or not order_uuid:
raise CDEKOrderMappingError("CDEK order response must include entity.uuid.")
return order_uuid
waybill_uuid, waybill_url = _extract_waybill(payload)
return CDEKOrderRegistrationResult(
order_uuid=order_uuid,
waybill_uuid=waybill_uuid,
waybill_url=waybill_url,
)
_CDEK_DUPLICATE_ORDER_ERROR_CODES = frozenset({"v2_entity_already_exists"})
def map_cdek_existing_order_response(payload: object) -> str | None:
def map_cdek_existing_order_response(
payload: object,
) -> CDEKOrderRegistrationResult | None:
if not isinstance(payload, dict):
return None
@@ -58,21 +78,156 @@ def map_cdek_existing_order_response(payload: object) -> str | None:
try:
return map_cdek_order_response(payload)
except CDEKOrderMappingError:
return _find_first_uuid(payload)
order_uuid = _find_first_uuid(payload)
if order_uuid is None:
return None
waybill_uuid, waybill_url = _extract_waybill(payload)
return CDEKOrderRegistrationResult(
order_uuid=order_uuid,
waybill_uuid=waybill_uuid,
waybill_url=waybill_url,
)
def _map_order_party(party: PaymentParty) -> dict[str, Any]:
def _extract_waybill(payload: dict[str, Any]) -> tuple[str | None, str | None]:
related_entities = payload.get("related_entities")
if not isinstance(related_entities, list):
return None, None
for entry in related_entities:
if not isinstance(entry, dict):
continue
if entry.get("type") != _CDEK_WAYBILL_RELATED_ENTITY_TYPE:
continue
uuid_value = entry.get("uuid")
url_value = entry.get("url")
waybill_uuid = uuid_value if isinstance(uuid_value, str) and uuid_value else None
waybill_url = url_value if isinstance(url_value, str) and url_value else None
if waybill_uuid is None and waybill_url is None:
continue
return waybill_uuid, waybill_url
return None, None
def compose_address_line(address: Address) -> str:
"""Build a CDEK address line from structured fields."""
parts = [address.city, address.street, address.house]
line = ", ".join(part for part in parts if part)
if address.apartment:
line = f"{line}, кв. {address.apartment}"
return line
def resolve_cdek_city_code(city_id: int) -> int:
"""Resolve CDEK city code from cities_map by city identifier."""
city_entry = cities_map.get(str(city_id))
if not isinstance(city_entry, dict):
raise CDEKOrderMappingError(
f"CDEK city mapping is not configured for city id {city_id}."
)
cdek_data = city_entry.get("cdek")
if not isinstance(cdek_data, dict):
raise CDEKOrderMappingError(
f"CDEK city mapping is not configured for city id {city_id}."
)
raw_city_code = cdek_data.get("code")
if raw_city_code is None or isinstance(raw_city_code, bool):
raise CDEKOrderMappingError(
f"CDEK city code is invalid for city id {city_id}."
)
try:
return int(raw_city_code)
except (TypeError, ValueError) as exc:
raise CDEKOrderMappingError(
f"CDEK city code is invalid for city id {city_id}."
) from exc
_GRAMS_IN_KILOGRAM = Decimal(1000)
_INTEGER_QUANTIZER = Decimal(1)
def kilograms_string_to_grams(value: str) -> int:
kilograms = _parse_positive_decimal(value, field_name="weight")
return int(
(kilograms * _GRAMS_IN_KILOGRAM).quantize(
_INTEGER_QUANTIZER, rounding=ROUND_HALF_UP
)
)
def centimeters_string_to_int(value: str, field_name: str) -> int:
measurement = _parse_positive_decimal(value, field_name=field_name)
return int(measurement.quantize(_INTEGER_QUANTIZER, rounding=ROUND_HALF_UP))
def _parse_positive_decimal(value: str, *, field_name: str) -> Decimal:
try:
parsed = Decimal(value)
except (InvalidOperation, TypeError, ValueError) as exc:
raise CDEKOrderMappingError(
f"{field_name} is not a valid decimal: {value!r}."
) from exc
if not parsed.is_finite() or parsed <= 0:
raise CDEKOrderMappingError(
f"{field_name} must be a positive decimal: {value!r}."
)
return parsed
def _map_party(contact: Contact) -> dict[str, Any]:
phone: dict[str, Any] = {"number": contact.phone}
if contact.phone_ext:
phone["additional"] = contact.phone_ext
party: dict[str, Any] = {
"name": contact.full_name,
"phones": [phone],
}
if contact.email:
party["email"] = contact.email
if contact.is_company:
party["contragent_type"] = "LEGAL_ENTITY"
party["company"] = contact.company_name
party["inn"] = contact.inn
party["kpp"] = contact.kpp
else:
party["company"] = contact.full_name
return party
def _map_location(address: Address) -> dict[str, Any]:
return {
"name": party.name,
"email": party.email,
"phones": [party.phone.model_dump(mode="python")],
"code": resolve_cdek_city_code(address.city_id),
"address": compose_address_line(address),
"postal_code": address.zip,
}
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 _map_package(request: InitPaymentRequest) -> dict[str, Any]:
system_data: SystemData = request.system_data
weight_grams = kilograms_string_to_grams(request.system_data.weight)
description = request.content.description or request.order_uuid
package: dict[str, Any] = {
"number": request.order_uuid,
"weight": weight_grams,
"comment": description,
}
if system_data.parcel_type == "parcel" and system_data.dimensions is not None:
package.update(_map_dimensions(system_data.dimensions))
return package
def _map_dimensions(dimensions: Dimensions) -> dict[str, int]:
return {
"length": centimeters_string_to_int(dimensions.length, "length"),
"width": centimeters_string_to_int(dimensions.width, "width"),
"height": centimeters_string_to_int(dimensions.height, "height"),
}
def _contains_cdek_duplicate_error_code(payload: object) -> bool:
+4 -20
View File
@@ -32,33 +32,17 @@ class Order(Base):
order_uuid: Mapped[str] = mapped_column(String(128), nullable=False)
payment_url: Mapped[str] = mapped_column(String(2048), nullable=False)
price: Mapped[int] = mapped_column(Integer, nullable=False)
delivery_type: Mapped[int] = mapped_column(Integer, nullable=False)
tariff_code: Mapped[int] = mapped_column(Integer, nullable=False)
sender: Mapped[dict[str, Any]] = mapped_column(_json_payload_type(), nullable=False)
recipient: Mapped[dict[str, Any]] = mapped_column(
account_email: Mapped[str] = mapped_column(String(320), nullable=False)
payload: Mapped[dict[str, Any]] = mapped_column(
_json_payload_type(),
nullable=False,
)
from_location: Mapped[dict[str, Any]] = mapped_column(
_json_payload_type(),
nullable=False,
)
to_location: Mapped[dict[str, Any]] = mapped_column(
_json_payload_type(),
nullable=False,
)
packages: Mapped[list[dict[str, Any]]] = mapped_column(
_json_payload_type(),
nullable=False,
)
services: Mapped[list[dict[str, Any]] | None] = mapped_column(
_json_payload_type(),
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)
cdek_waybill_uuid: Mapped[str | None] = mapped_column(String(128), nullable=True)
cdek_waybill_url: Mapped[str | None] = mapped_column(String(2048), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
server_default=func.now(),
+8 -16
View File
@@ -15,15 +15,9 @@ class OrderData:
order_uuid: str
payment_url: str
price: int
delivery_type: int
tariff_code: int
sender: dict[str, Any]
recipient: dict[str, Any]
from_location: dict[str, Any]
to_location: dict[str, Any]
packages: list[dict[str, Any]]
services: list[dict[str, Any]] | None
comment: str | None
account_email: str
payload: dict[str, Any]
class OrderRepository:
@@ -38,15 +32,9 @@ class OrderRepository:
order_uuid=order_data.order_uuid,
payment_url=order_data.payment_url,
price=order_data.price,
delivery_type=order_data.delivery_type,
tariff_code=order_data.tariff_code,
sender=order_data.sender,
recipient=order_data.recipient,
from_location=order_data.from_location,
to_location=order_data.to_location,
packages=order_data.packages,
services=order_data.services,
comment=order_data.comment,
account_email=order_data.account_email,
payload=order_data.payload,
)
session.add(order)
await session.flush()
@@ -84,11 +72,15 @@ class OrderRepository:
session: AsyncSession,
order_uuid: str,
cdek_order_uuid: str,
cdek_waybill_uuid: str | None = None,
cdek_waybill_url: str | None = None,
) -> 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
order.cdek_waybill_uuid = cdek_waybill_uuid
order.cdek_waybill_url = cdek_waybill_url
await session.flush()
return order
+104 -43
View File
@@ -1,61 +1,122 @@
"""Schemas for delivery payment initialization."""
from datetime import datetime
from decimal import Decimal, InvalidOperation
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, model_validator
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from pydantic.alias_generators import to_camel
class PaymentPhone(BaseModel):
number: str = Field(min_length=1)
def _validate_positive_decimal_string(value: str) -> str:
try:
parsed = Decimal(value)
except (InvalidOperation, TypeError, ValueError) as exc:
raise ValueError("must be a positive decimal number") from exc
if not parsed.is_finite() or parsed <= 0:
raise ValueError("must be a positive decimal number")
return value
class PaymentParty(BaseModel):
model_config = ConfigDict(extra="forbid")
name: str = Field(min_length=1)
email: str = Field(min_length=1)
phone: PaymentPhone
@model_validator(mode="before")
@classmethod
def reject_company_field(cls, value: object) -> object:
if isinstance(value, dict) and "company" in value:
raise ValueError("company is not allowed")
return value
class _CamelModel(BaseModel):
model_config = ConfigDict(
alias_generator=to_camel,
populate_by_name=False,
extra="forbid",
)
class DeliveryLocation(BaseModel):
address: str = Field(min_length=1)
class Address(_CamelModel):
city_id: int = Field(gt=0, strict=True)
city: str = Field(min_length=1)
country_code: str = Field(min_length=2, max_length=2)
class PaymentService(BaseModel):
code: str = Field(min_length=1)
parameter: str = Field(min_length=1)
class PaymentPackage(BaseModel):
number: str = Field(min_length=1)
weight: int = Field(gt=0)
length: int = Field(gt=0)
width: int = Field(gt=0)
height: int = Field(gt=0)
street: str = Field(min_length=1)
house: str = Field(min_length=1)
apartment: str | None = None
zip: str = Field(min_length=1)
comment: str | None = None
class InitPaymentRequest(BaseModel):
order_uuid: str = Field(min_length=1)
class Contact(_CamelModel):
full_name: str = Field(min_length=1)
email: str | None = None
phone: str = Field(min_length=1)
phone_ext: str | None = None
is_company: bool
company_name: str | None = None
inn: str | None = None
kpp: str | None = None
@model_validator(mode="after")
def _validate_company_requisites(self) -> "Contact":
if self.is_company:
missing = [
name
for name, value in (
("companyName", self.company_name),
("inn", self.inn),
("kpp", self.kpp),
)
if value is None or value == ""
]
if missing:
raise ValueError(
f"{', '.join(missing)} are required when isCompany is true"
)
return self
class Content(_CamelModel):
description: str | None = None
class SystemDataTariff(_CamelModel):
provider: str = Field(min_length=1)
service_name: str = Field(min_length=1)
price: int = Field(gt=0, strict=True, description="Payment amount in kopecks.")
type: Literal[2]
tariff_code: int
comment: str | None = None
sender: PaymentParty
recipient: PaymentParty
from_location: DeliveryLocation
to_location: DeliveryLocation
services: list[PaymentService] | None = Field(default=None, min_length=1)
packages: list[PaymentPackage] = Field(min_length=1)
delivery_days_min: int = Field(ge=0, strict=True)
delivery_days_max: int = Field(ge=0, strict=True)
tariff_code: int = Field(gt=0, strict=True)
class Dimensions(_CamelModel):
length: str = Field(min_length=1)
width: str = Field(min_length=1)
height: str = Field(min_length=1)
_validate_dimensions = field_validator("length", "width", "height")(
_validate_positive_decimal_string
)
class SystemData(_CamelModel):
tariff: SystemDataTariff
parcel_type: Literal["doc", "parcel"]
doc_packaging: Literal["envelope", "bag"] | None = None
weight: str = Field(min_length=1)
dimensions: Dimensions | None = None
_validate_weight = field_validator("weight")(_validate_positive_decimal_string)
@model_validator(mode="after")
def _validate_dimensions_for_parcel_type(self) -> "SystemData":
if self.parcel_type == "parcel" and self.dimensions is None:
raise ValueError("dimensions are required when parcelType is 'parcel'")
if self.parcel_type == "doc" and self.doc_packaging is None:
raise ValueError("docPackaging is required when parcelType is 'doc'")
return self
class InitPaymentRequest(_CamelModel):
order_uuid: str = Field(min_length=1)
sender_address: Address
sender_contact: Contact
receiver_address: Address
receiver_contact: Contact
content: Content
pickup_date: datetime
delivery_date: datetime | None = None
account_email: str = Field(min_length=1)
system_data: SystemData
class InitPaymentResponse(BaseModel):
+39 -40
View File
@@ -20,6 +20,9 @@ from app.adapters.delivery_providers.base import (
ProviderClientError,
ProviderRequestError,
)
from app.adapters.delivery_providers.cdek.order_mapper import (
CDEKOrderRegistrationResult,
)
from app.adapters.tbank.base import (
TBankPaymentAdapterError,
TBankPaymentNotificationTokenError,
@@ -109,7 +112,9 @@ class PaymentPriceValidationAdapterProtocol(Protocol):
class OrderRegistrationAdapterProtocol(Protocol):
async def register_order(self, request: InitPaymentRequest) -> str: ...
async def register_order(
self, request: InitPaymentRequest
) -> CDEKOrderRegistrationResult: ...
class OrderRepositoryProtocol(Protocol):
@@ -136,6 +141,8 @@ class OrderRepositoryProtocol(Protocol):
session: object,
order_uuid: str,
cdek_order_uuid: str,
cdek_waybill_uuid: str | None = None,
cdek_waybill_url: str | None = None,
) -> object | None: ...
@@ -259,7 +266,7 @@ class AggregatorService:
try:
payment_url = await self._payment_adapter.create_payment_link(
order_uuid=request.order_uuid,
amount_kopecks=request.price,
amount_kopecks=request.system_data.tariff.price,
)
except TBankPaymentRequestError as exc:
logger.exception(
@@ -294,6 +301,8 @@ class AggregatorService:
"Payment price validation adapter is not configured."
)
tariff_code = request.system_data.tariff.tariff_code
requested_price = request.system_data.tariff.price
try:
provider_price = await self._payment_price_validation_adapter.get_payment_price(
request
@@ -302,8 +311,8 @@ class AggregatorService:
logger.warning(
"init_payment_price_validation_request_rejected",
order_uuid=request.order_uuid,
tariff_code=request.tariff_code,
requested_price_kopecks=request.price,
tariff_code=tariff_code,
requested_price_kopecks=requested_price,
error=str(exc),
)
raise InvalidInitPaymentRequestError(
@@ -313,8 +322,8 @@ class AggregatorService:
logger.warning(
"init_payment_price_validation_unavailable",
order_uuid=request.order_uuid,
tariff_code=request.tariff_code,
requested_price_kopecks=request.price,
tariff_code=tariff_code,
requested_price_kopecks=requested_price,
error=str(exc),
)
raise InitPaymentUnavailableError(
@@ -324,8 +333,8 @@ class AggregatorService:
logger.exception(
"init_payment_price_validation_unexpected_error",
order_uuid=request.order_uuid,
tariff_code=request.tariff_code,
requested_price_kopecks=request.price,
tariff_code=tariff_code,
requested_price_kopecks=requested_price,
)
raise InitPaymentUnavailableError(
"Payment price validation is temporarily unavailable."
@@ -335,8 +344,8 @@ class AggregatorService:
logger.warning(
"init_payment_price_validation_tariff_not_found",
order_uuid=request.order_uuid,
tariff_code=request.tariff_code,
requested_price_kopecks=request.price,
tariff_code=tariff_code,
requested_price_kopecks=requested_price,
)
raise InvalidInitPaymentRequestError(
"CDEK did not return the requested tariff for payment validation."
@@ -347,15 +356,15 @@ class AggregatorService:
price_multiplier=self._provider_price_multiplier,
)
if not is_init_payment_price_valid(
request.price,
requested_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,
tariff_code=tariff_code,
requested_price_kopecks=requested_price,
expected_price_kopecks=expected_amount_kopecks,
provider_currency=getattr(provider_price, "currency", None),
provider_price=str(getattr(provider_price, "price", None)),
@@ -398,10 +407,12 @@ class AggregatorService:
if existing_cdek_order_uuid:
return "OK"
cdek_order_uuid = await self._register_cdek_order(order)
registration_result = await self._register_cdek_order(order)
await self._save_cdek_order_uuid(
order_uuid=notification.OrderId,
cdek_order_uuid=cdek_order_uuid,
cdek_order_uuid=registration_result.order_uuid,
cdek_waybill_uuid=registration_result.waybill_uuid,
cdek_waybill_url=registration_result.waybill_url,
)
return "OK"
@@ -456,7 +467,9 @@ class AggregatorService:
"TBank payment notification order update failed."
) from exc
async def _register_cdek_order(self, order: object) -> str:
async def _register_cdek_order(
self, order: object
) -> CDEKOrderRegistrationResult:
if self._order_registration_adapter is None:
raise TBankPaymentNotificationProcessingError(
"CDEK order registration adapter is not configured."
@@ -479,6 +492,8 @@ class AggregatorService:
*,
order_uuid: str,
cdek_order_uuid: str,
cdek_waybill_uuid: str | None,
cdek_waybill_url: str | None,
) -> None:
if self._order_repository is None:
raise TBankPaymentNotificationProcessingError(
@@ -491,6 +506,8 @@ class AggregatorService:
session,
order_uuid,
cdek_order_uuid,
cdek_waybill_uuid,
cdek_waybill_url,
)
if order is None:
logger.warning(
@@ -540,37 +557,19 @@ class AggregatorService:
request: InitPaymentRequest,
payment_url: str,
) -> OrderData:
payload = request.model_dump(mode="json")
return OrderData(
order_uuid=request.order_uuid,
payment_url=payment_url,
price=request.price,
delivery_type=request.type,
tariff_code=request.tariff_code,
sender=payload["sender"],
recipient=payload["recipient"],
from_location=payload["from_location"],
to_location=payload["to_location"],
packages=payload["packages"],
services=payload["services"],
comment=request.comment,
price=request.system_data.tariff.price,
tariff_code=request.system_data.tariff.tariff_code,
account_email=request.account_email,
payload=request.model_dump(mode="json", by_alias=True),
)
@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"),
)
payload = getattr(order, "payload")
return InitPaymentRequest.model_validate(payload)
async def _get_provider_prices(
self,