Добавлен адаптер к tbank и формирование ссылки на оплату
This commit is contained in:
@@ -17,7 +17,7 @@ from app.adapters.delivery_providers.cdek.order_mapper import (
|
||||
)
|
||||
from app.cities import cities_map
|
||||
from app.config import AdapterConfig
|
||||
from app.schemas.order import OrderCreateRequest, OrderCreateResponse
|
||||
from app.schemas.payment import InitPaymentRequest
|
||||
from app.schemas.request import DeliveryCalculationRequest
|
||||
from app.schemas.response import DeliveryPrice
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -94,7 +94,7 @@ class CDEKClient:
|
||||
|
||||
raise CDEKClientError("CDEK tariff request failed unexpectedly.")
|
||||
|
||||
async def register_order(self, request: OrderCreateRequest) -> OrderCreateResponse:
|
||||
async def register_order(self, request: InitPaymentRequest) -> str:
|
||||
payload = map_cdek_order_request(request)
|
||||
for attempt in range(self._retry_attempts + 1):
|
||||
try:
|
||||
@@ -241,5 +241,5 @@ class CDEKProvider(DeliveryProvider):
|
||||
raw_payload = await self._client.get_raw_price(request)
|
||||
return map_cdek_response(raw_payload)
|
||||
|
||||
async def register_order(self, request: OrderCreateRequest) -> OrderCreateResponse:
|
||||
async def register_order(self, request: InitPaymentRequest) -> str:
|
||||
return await self._client.register_order(request)
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
from typing import Any
|
||||
|
||||
from app.schemas.order import (
|
||||
OrderCreateRequest,
|
||||
OrderCreateResponse,
|
||||
OrderPackage,
|
||||
OrderParty,
|
||||
from app.schemas.payment import (
|
||||
InitPaymentRequest,
|
||||
PaymentPackage,
|
||||
PaymentParty,
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +13,7 @@ class CDEKOrderMappingError(ValueError):
|
||||
"""Raised when CDEK order payload cannot be mapped."""
|
||||
|
||||
|
||||
def map_cdek_order_request(request: OrderCreateRequest) -> dict[str, Any]:
|
||||
def map_cdek_order_request(request: InitPaymentRequest) -> dict[str, Any]:
|
||||
payload: dict[str, Any] = {
|
||||
"type": request.type,
|
||||
"tariff_code": request.tariff_code,
|
||||
@@ -33,7 +32,7 @@ def map_cdek_order_request(request: OrderCreateRequest) -> dict[str, Any]:
|
||||
return payload
|
||||
|
||||
|
||||
def map_cdek_order_response(payload: dict[str, Any]) -> OrderCreateResponse:
|
||||
def map_cdek_order_response(payload: dict[str, Any]) -> str:
|
||||
entity = payload.get("entity")
|
||||
if not isinstance(entity, dict):
|
||||
raise CDEKOrderMappingError("CDEK order response must include entity object.")
|
||||
@@ -42,10 +41,10 @@ def map_cdek_order_response(payload: dict[str, Any]) -> OrderCreateResponse:
|
||||
if not isinstance(order_uuid, str) or not order_uuid:
|
||||
raise CDEKOrderMappingError("CDEK order response must include entity.uuid.")
|
||||
|
||||
return OrderCreateResponse(provider="cdek", order_uuid=order_uuid)
|
||||
return order_uuid
|
||||
|
||||
|
||||
def _map_order_party(party: OrderParty) -> dict[str, Any]:
|
||||
def _map_order_party(party: PaymentParty) -> dict[str, Any]:
|
||||
return {
|
||||
"name": party.name,
|
||||
"email": party.email,
|
||||
@@ -53,7 +52,7 @@ def _map_order_party(party: OrderParty) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def _map_order_package(package: OrderPackage) -> dict[str, Any]:
|
||||
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
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
"""TBank payment adapter package."""
|
||||
|
||||
from app.adapters.tbank.client import TBankAdapter
|
||||
|
||||
__all__ = ["TBankAdapter"]
|
||||
@@ -0,0 +1,9 @@
|
||||
"""TBank payment adapter errors."""
|
||||
|
||||
|
||||
class TBankPaymentAdapterError(RuntimeError):
|
||||
"""Raised when a TBank payment adapter call fails."""
|
||||
|
||||
|
||||
class TBankPaymentRequestError(TBankPaymentAdapterError):
|
||||
"""Raised when TBank rejects payment request data."""
|
||||
@@ -0,0 +1,157 @@
|
||||
"""TBank payment adapter."""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
import hashlib
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from app.adapters.tbank.base import (
|
||||
TBankPaymentAdapterError,
|
||||
TBankPaymentRequestError,
|
||||
)
|
||||
from app.config import TBankPaymentConfig
|
||||
|
||||
|
||||
class TBankAdapter:
|
||||
def __init__(
|
||||
self,
|
||||
http_client: httpx.AsyncClient,
|
||||
*,
|
||||
init_url: str,
|
||||
terminal_key: str,
|
||||
password: str,
|
||||
timeout_seconds: float = 10.0,
|
||||
retry_attempts: int = 2,
|
||||
retry_backoff_seconds: float = 0.2,
|
||||
sleep: Callable[[float], Awaitable[None]] = asyncio.sleep,
|
||||
) -> None:
|
||||
self._http_client = http_client
|
||||
self._init_url = init_url
|
||||
self._terminal_key = terminal_key
|
||||
self._password = password
|
||||
self._timeout_seconds = timeout_seconds
|
||||
self._retry_attempts = retry_attempts
|
||||
self._retry_backoff_seconds = retry_backoff_seconds
|
||||
self._sleep = sleep
|
||||
|
||||
@classmethod
|
||||
def from_config(
|
||||
cls,
|
||||
*,
|
||||
http_client: httpx.AsyncClient,
|
||||
config: TBankPaymentConfig,
|
||||
) -> "TBankAdapter":
|
||||
return cls(
|
||||
http_client=http_client,
|
||||
init_url=config.init_url,
|
||||
terminal_key=config.auth.terminal_key,
|
||||
password=config.auth.password,
|
||||
timeout_seconds=config.timeout_seconds,
|
||||
retry_attempts=config.retry_attempts,
|
||||
retry_backoff_seconds=config.retry_backoff_seconds,
|
||||
)
|
||||
|
||||
async def create_payment_link(self, order_uuid: str, amount_kopecks: int) -> str:
|
||||
payload = self._build_payload(
|
||||
order_uuid=order_uuid,
|
||||
amount_kopecks=amount_kopecks,
|
||||
)
|
||||
|
||||
for attempt in range(self._retry_attempts + 1):
|
||||
try:
|
||||
response = await self._http_client.post(
|
||||
self._init_url,
|
||||
json=payload,
|
||||
headers={
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
timeout=self._timeout_seconds,
|
||||
)
|
||||
except (httpx.TimeoutException, httpx.TransportError) as exc:
|
||||
if attempt < self._retry_attempts:
|
||||
await self._sleep(self._retry_delay(attempt))
|
||||
continue
|
||||
raise TBankPaymentAdapterError(
|
||||
"TBank payment init request failed after retry attempts."
|
||||
) from exc
|
||||
|
||||
if self._should_retry(response.status_code):
|
||||
if attempt < self._retry_attempts:
|
||||
await self._sleep(self._retry_delay(attempt))
|
||||
continue
|
||||
raise TBankPaymentAdapterError(
|
||||
"TBank payment init request failed with retriable status "
|
||||
f"{response.status_code}."
|
||||
)
|
||||
|
||||
if 400 <= response.status_code < 500:
|
||||
raise TBankPaymentRequestError(
|
||||
"TBank payment init request was rejected with status "
|
||||
f"{response.status_code}."
|
||||
)
|
||||
|
||||
try:
|
||||
response.raise_for_status()
|
||||
raw_payload = response.json()
|
||||
except (httpx.HTTPError, TypeError, ValueError) as exc:
|
||||
raise TBankPaymentAdapterError(
|
||||
"TBank payment init returned invalid payload."
|
||||
) from exc
|
||||
|
||||
return self._map_payment_url(raw_payload)
|
||||
|
||||
raise TBankPaymentAdapterError("TBank payment init request failed unexpectedly.")
|
||||
|
||||
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.")
|
||||
if amount_kopecks <= 0:
|
||||
raise TBankPaymentRequestError("TBank payment amount must be positive.")
|
||||
|
||||
payload: dict[str, Any] = {
|
||||
"TerminalKey": self._terminal_key,
|
||||
"Amount": amount_kopecks,
|
||||
"OrderId": order_uuid,
|
||||
}
|
||||
payload["Token"] = _build_tbank_token(payload, password=self._password)
|
||||
return payload
|
||||
|
||||
def _retry_delay(self, attempt: int) -> float:
|
||||
return self._retry_backoff_seconds * (attempt + 1)
|
||||
|
||||
@staticmethod
|
||||
def _should_retry(status_code: int) -> bool:
|
||||
return status_code == 429 or status_code >= 500
|
||||
|
||||
@staticmethod
|
||||
def _map_payment_url(payload: object) -> str:
|
||||
if not isinstance(payload, dict):
|
||||
raise TBankPaymentAdapterError(
|
||||
"TBank payment init payload must be a JSON object."
|
||||
)
|
||||
|
||||
if payload.get("Success") is False:
|
||||
raise TBankPaymentRequestError("TBank payment init request was rejected.")
|
||||
|
||||
payment_url = payload.get("PaymentURL")
|
||||
if not isinstance(payment_url, str) or not payment_url.strip():
|
||||
raise TBankPaymentAdapterError(
|
||||
"TBank payment init response must include PaymentURL."
|
||||
)
|
||||
return payment_url.strip()
|
||||
|
||||
|
||||
def _build_tbank_token(payload: dict[str, Any], *, password: str) -> str:
|
||||
token_payload = {
|
||||
key: value
|
||||
for key, value in payload.items()
|
||||
if key != "Token" and not isinstance(value, (dict, list))
|
||||
}
|
||||
token_payload["Password"] = password
|
||||
token_source = "".join(
|
||||
str(token_payload[key]) for key in sorted(token_payload)
|
||||
)
|
||||
return hashlib.sha256(token_source.encode("utf-8")).hexdigest()
|
||||
@@ -48,6 +48,19 @@ class AdapterConfig(BaseModel):
|
||||
cdek_cache_ttl_seconds: int = Field(default=900, gt=0)
|
||||
|
||||
|
||||
class TBankPaymentAuthConfig(BaseModel):
|
||||
terminal_key: str = Field(..., min_length=1)
|
||||
password: str = Field(..., min_length=1)
|
||||
|
||||
|
||||
class TBankPaymentConfig(BaseModel):
|
||||
init_url: str = Field(..., min_length=1)
|
||||
auth: TBankPaymentAuthConfig
|
||||
timeout_seconds: float = Field(default=10.0, gt=0)
|
||||
retry_attempts: int = Field(default=2, ge=0)
|
||||
retry_backoff_seconds: float = Field(default=0.2, ge=0)
|
||||
|
||||
|
||||
class DadataAddressSuggestionsConfig(BaseModel):
|
||||
url: str = "https://suggestions.dadata.ru/suggestions/api/4_1/rs/suggest/address"
|
||||
api_key: str = ""
|
||||
@@ -107,6 +120,7 @@ class Settings(BaseSettings):
|
||||
business_logic: BusinessLogicConfig = Field(default_factory=BusinessLogicConfig)
|
||||
repository: RepositoryConfig = Field(default_factory=RepositoryConfig)
|
||||
adapter: AdapterConfig = Field(default_factory=AdapterConfig)
|
||||
tbank_payment: TBankPaymentConfig
|
||||
address_suggestions: AddressSuggestionsConfig = Field(
|
||||
default_factory=AddressSuggestionsConfig
|
||||
)
|
||||
@@ -138,6 +152,7 @@ class _RequiredYamlSections(BaseModel):
|
||||
business_logic: dict[str, Any]
|
||||
repository: dict[str, Any]
|
||||
adapter: dict[str, Any]
|
||||
tbank_payment: dict[str, Any]
|
||||
address_suggestions: dict[str, Any]
|
||||
observability: dict[str, Any]
|
||||
|
||||
|
||||
@@ -8,10 +8,11 @@ from app.adapters.address_suggestions.yandex_geosuggest import (
|
||||
YandexGeosuggestAddressSuggestionProvider,
|
||||
)
|
||||
from app.adapters.delivery_providers.cdek import CDEKProvider
|
||||
from app.adapters.tbank import TBankAdapter
|
||||
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.schemas.order import OrderCreateRequest, OrderCreateResponse
|
||||
from app.schemas.payment import InitPaymentRequest, InitPaymentResponse
|
||||
from app.schemas.request import AddressSuggestRequest, DeliveryCalculationRequest
|
||||
from app.schemas.response import AddressSuggestion, DeliveryPrice
|
||||
from app.services.aggregator import (
|
||||
@@ -20,7 +21,8 @@ from app.services.aggregator import (
|
||||
AggregatorServiceError,
|
||||
InvalidAddressSuggestRequestError,
|
||||
InvalidDeliveryRequestError,
|
||||
InvalidOrderCreateRequestError,
|
||||
InvalidInitPaymentRequestError,
|
||||
InitPaymentUnavailableError,
|
||||
UnsupportedAddressSuggestionCountryError,
|
||||
)
|
||||
|
||||
@@ -38,6 +40,10 @@ def _build_aggregator_service(settings: Settings) -> AggregatorService:
|
||||
http_client=http_client,
|
||||
adapter_config=settings.adapter,
|
||||
)
|
||||
payment_adapter = TBankAdapter.from_config(
|
||||
http_client=http_client,
|
||||
config=settings.tbank_payment,
|
||||
)
|
||||
dadata_provider = DadataAddressSuggestionProvider.from_config(
|
||||
http_client=http_client,
|
||||
config=settings.address_suggestions.dadata,
|
||||
@@ -55,7 +61,7 @@ def _build_aggregator_service(settings: Settings) -> AggregatorService:
|
||||
service = AggregatorService(
|
||||
providers=providers,
|
||||
cache=cache,
|
||||
order_adapter=cdek_provider,
|
||||
payment_adapter=payment_adapter,
|
||||
address_suggestion_providers=(
|
||||
dadata_provider,
|
||||
yandex_geosuggest_provider,
|
||||
@@ -146,28 +152,36 @@ async def suggest_addresses(
|
||||
|
||||
|
||||
@router.post(
|
||||
"/order",
|
||||
response_model=OrderCreateResponse,
|
||||
"/init-payment",
|
||||
response_model=InitPaymentResponse,
|
||||
)
|
||||
async def create_delivery_order(
|
||||
order_request: OrderCreateRequest,
|
||||
async def init_payment(
|
||||
payment_request: InitPaymentRequest,
|
||||
service: AggregatorService = Depends(get_aggregator_service),
|
||||
) -> OrderCreateResponse:
|
||||
) -> InitPaymentResponse:
|
||||
try:
|
||||
return await service.create_order(order_request)
|
||||
except InvalidOrderCreateRequestError as exc:
|
||||
return await service.init_payment(payment_request)
|
||||
except InvalidInitPaymentRequestError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"code": "invalid_order_create_request",
|
||||
"message": "Order request contains invalid or unsupported CDEK data.",
|
||||
"code": "invalid_init_payment_request",
|
||||
"message": "Payment request contains invalid or unsupported TBank data.",
|
||||
},
|
||||
) from exc
|
||||
except InitPaymentUnavailableError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail={
|
||||
"code": "init_payment_unavailable",
|
||||
"message": "Payment initialization is temporarily unavailable.",
|
||||
},
|
||||
) from exc
|
||||
except AggregatorServiceError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail={
|
||||
"code": "order_creation_unavailable",
|
||||
"message": "CDEK order creation is temporarily unavailable.",
|
||||
"code": "init_payment_unavailable",
|
||||
"message": "Payment initialization is temporarily unavailable.",
|
||||
},
|
||||
) from exc
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
"""Schemas for CDEK order registration."""
|
||||
"""Schemas for delivery payment initialization."""
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
|
||||
class OrderPhone(BaseModel):
|
||||
class PaymentPhone(BaseModel):
|
||||
number: str = Field(min_length=1)
|
||||
|
||||
|
||||
class OrderParty(BaseModel):
|
||||
class PaymentParty(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
name: str = Field(min_length=1)
|
||||
email: str = Field(min_length=1)
|
||||
phone: OrderPhone
|
||||
phone: PaymentPhone
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
@@ -24,18 +24,18 @@ class OrderParty(BaseModel):
|
||||
return value
|
||||
|
||||
|
||||
class OrderLocation(BaseModel):
|
||||
class DeliveryLocation(BaseModel):
|
||||
address: str = Field(min_length=1)
|
||||
city: str = Field(min_length=1)
|
||||
country_code: str = Field(min_length=2, max_length=2)
|
||||
|
||||
|
||||
class OrderService(BaseModel):
|
||||
class PaymentService(BaseModel):
|
||||
code: str = Field(min_length=1)
|
||||
parameter: str = Field(min_length=1)
|
||||
|
||||
|
||||
class OrderPackage(BaseModel):
|
||||
class PaymentPackage(BaseModel):
|
||||
number: str = Field(min_length=1)
|
||||
weight: int = Field(gt=0)
|
||||
length: int = Field(gt=0)
|
||||
@@ -44,18 +44,19 @@ class OrderPackage(BaseModel):
|
||||
comment: str | None = None
|
||||
|
||||
|
||||
class OrderCreateRequest(BaseModel):
|
||||
class InitPaymentRequest(BaseModel):
|
||||
order_uuid: str = Field(min_length=1)
|
||||
price: int = Field(gt=0, strict=True, description="Payment amount in kopecks.")
|
||||
type: Literal[2]
|
||||
tariff_code: Literal[535]
|
||||
comment: str | None = None
|
||||
sender: OrderParty
|
||||
recipient: OrderParty
|
||||
from_location: OrderLocation
|
||||
to_location: OrderLocation
|
||||
services: list[OrderService] | None = Field(default=None, min_length=1)
|
||||
packages: list[OrderPackage] = Field(min_length=1)
|
||||
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)
|
||||
|
||||
|
||||
class OrderCreateResponse(BaseModel):
|
||||
provider: str = Field(min_length=1)
|
||||
order_uuid: str = Field(min_length=1)
|
||||
class InitPaymentResponse(BaseModel):
|
||||
payment_url: str = Field(min_length=1)
|
||||
+32
-28
@@ -13,6 +13,10 @@ from app.adapters.address_suggestions.base import (
|
||||
AddressSuggestionRequestError,
|
||||
)
|
||||
from app.adapters.delivery_providers.base import DeliveryProvider, ProviderRequestError
|
||||
from app.adapters.tbank.base import (
|
||||
TBankPaymentAdapterError,
|
||||
TBankPaymentRequestError,
|
||||
)
|
||||
from app.domain.price import (
|
||||
DEFAULT_PROVIDER_PRICE_MULTIPLIER,
|
||||
DEFAULT_WEIGHT_ROUND_SCALE,
|
||||
@@ -20,7 +24,7 @@ from app.domain.price import (
|
||||
filter_and_sort_prices,
|
||||
normalize_delivery_request,
|
||||
)
|
||||
from app.schemas.order import OrderCreateRequest, OrderCreateResponse
|
||||
from app.schemas.payment import InitPaymentRequest, InitPaymentResponse
|
||||
from app.schemas.request import AddressSuggestRequest, DeliveryCalculationRequest
|
||||
from app.schemas.response import AddressSuggestion, DeliveryPrice
|
||||
|
||||
@@ -45,12 +49,12 @@ class AddressSuggestionsUnavailableError(AggregatorServiceError):
|
||||
"""Raised when address suggestions cannot be completed."""
|
||||
|
||||
|
||||
class InvalidOrderCreateRequestError(AggregatorServiceError):
|
||||
"""Raised when provider rejects order creation payload as invalid."""
|
||||
class InvalidInitPaymentRequestError(AggregatorServiceError):
|
||||
"""Raised when provider rejects payment init payload as invalid."""
|
||||
|
||||
|
||||
class OrderCreationUnavailableError(AggregatorServiceError):
|
||||
"""Raised when order creation cannot be completed."""
|
||||
class InitPaymentUnavailableError(AggregatorServiceError):
|
||||
"""Raised when payment initialization cannot be completed."""
|
||||
|
||||
|
||||
class PriceCacheProtocol(Protocol):
|
||||
@@ -59,6 +63,10 @@ class PriceCacheProtocol(Protocol):
|
||||
async def set(self, key: str, value: object, ttl: int | None = None) -> None: ...
|
||||
|
||||
|
||||
class PaymentAdapterProtocol(Protocol):
|
||||
async def create_payment_link(self, order_uuid: str, amount_kopecks: int) -> str: ...
|
||||
|
||||
|
||||
class FilterAndSortPricesFn(Protocol):
|
||||
def __call__(
|
||||
self,
|
||||
@@ -69,18 +77,12 @@ class FilterAndSortPricesFn(Protocol):
|
||||
) -> list[object]: ...
|
||||
|
||||
|
||||
class OrderRegistrationAdapterProtocol(Protocol):
|
||||
async def register_order(
|
||||
self, request: OrderCreateRequest
|
||||
) -> OrderCreateResponse: ...
|
||||
|
||||
|
||||
class AggregatorService:
|
||||
def __init__(
|
||||
self,
|
||||
providers: Sequence[DeliveryProvider],
|
||||
cache: PriceCacheProtocol | None = None,
|
||||
order_adapter: OrderRegistrationAdapterProtocol | None = None,
|
||||
payment_adapter: PaymentAdapterProtocol | None = None,
|
||||
address_suggestion_providers: Sequence[AddressSuggestionProvider] = (),
|
||||
address_suggestion_country_to_provider: Mapping[str, str] | None = None,
|
||||
*,
|
||||
@@ -90,7 +92,7 @@ class AggregatorService:
|
||||
) -> None:
|
||||
self._providers = tuple(providers)
|
||||
self._cache = cache
|
||||
self._order_adapter = order_adapter
|
||||
self._payment_adapter = payment_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
|
||||
@@ -168,27 +170,29 @@ class AggregatorService:
|
||||
|
||||
return [self._coerce_address_suggestion(item) for item in suggestions]
|
||||
|
||||
async def create_order(self, request: OrderCreateRequest) -> OrderCreateResponse:
|
||||
if self._order_adapter is None:
|
||||
raise OrderCreationUnavailableError(
|
||||
"Order registration adapter is not configured."
|
||||
)
|
||||
async def init_payment(self, request: InitPaymentRequest) -> InitPaymentResponse:
|
||||
if self._payment_adapter is None:
|
||||
raise InitPaymentUnavailableError("Payment adapter is not configured.")
|
||||
|
||||
try:
|
||||
created_order = await self._order_adapter.register_order(request)
|
||||
except ProviderRequestError as exc:
|
||||
raise InvalidOrderCreateRequestError(
|
||||
"Order request is invalid for the configured provider."
|
||||
payment_url = await self._payment_adapter.create_payment_link(
|
||||
order_uuid=request.order_uuid,
|
||||
amount_kopecks=request.price,
|
||||
)
|
||||
except TBankPaymentRequestError as exc:
|
||||
raise InvalidInitPaymentRequestError(
|
||||
"Payment init request is invalid for the configured provider."
|
||||
) from exc
|
||||
except TBankPaymentAdapterError as exc:
|
||||
raise InitPaymentUnavailableError(
|
||||
"Payment initialization is temporarily unavailable."
|
||||
) from exc
|
||||
except Exception as exc:
|
||||
raise OrderCreationUnavailableError(
|
||||
"Order creation is temporarily unavailable."
|
||||
raise InitPaymentUnavailableError(
|
||||
"Payment initialization is temporarily unavailable."
|
||||
) from exc
|
||||
|
||||
return OrderCreateResponse.model_validate(
|
||||
created_order,
|
||||
from_attributes=True,
|
||||
)
|
||||
return InitPaymentResponse(payment_url=payment_url)
|
||||
|
||||
async def _get_provider_prices(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user