Добавлен CI, добавлена отправка письма о получении оплаты, добавлен order_id в success_url
Deploy / deploy (push) Successful in 58s
Deploy / deploy (push) Successful in 58s
This commit is contained in:
+97
-17
@@ -3,10 +3,12 @@
|
||||
import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
from collections.abc import Iterable, Mapping, Sequence
|
||||
from collections.abc import Callable, Iterable, Mapping, Sequence
|
||||
from contextlib import AbstractAsyncContextManager
|
||||
from datetime import datetime, timezone
|
||||
from decimal import Decimal
|
||||
from typing import Protocol
|
||||
from uuid import uuid4
|
||||
|
||||
import structlog
|
||||
|
||||
@@ -113,7 +115,7 @@ class PaymentPriceValidationAdapterProtocol(Protocol):
|
||||
|
||||
class OrderRegistrationAdapterProtocol(Protocol):
|
||||
async def register_order(
|
||||
self, request: InitPaymentRequest
|
||||
self, request: InitPaymentRequest, order_uuid: str
|
||||
) -> CDEKOrderRegistrationResult: ...
|
||||
|
||||
|
||||
@@ -144,6 +146,16 @@ class OrderRepositoryProtocol(Protocol):
|
||||
) -> object | None: ...
|
||||
|
||||
|
||||
class EmailSenderProtocol(Protocol):
|
||||
async def send_email(
|
||||
self,
|
||||
*,
|
||||
to: str,
|
||||
subject: str,
|
||||
body: str,
|
||||
) -> None: ...
|
||||
|
||||
|
||||
class FilterAndSortPricesFn(Protocol):
|
||||
def __call__(
|
||||
self,
|
||||
@@ -165,12 +177,14 @@ class AggregatorService:
|
||||
) = None,
|
||||
order_repository: OrderRepositoryProtocol | None = None,
|
||||
order_registration_adapter: OrderRegistrationAdapterProtocol | None = None,
|
||||
email_sender: EmailSenderProtocol | None = None,
|
||||
address_suggestion_providers: Sequence[AddressSuggestionProvider] = (),
|
||||
address_suggestion_country_to_provider: Mapping[str, str] | None = None,
|
||||
*,
|
||||
weight_round_scale: int = DEFAULT_WEIGHT_ROUND_SCALE,
|
||||
provider_price_multiplier: Decimal = DEFAULT_PROVIDER_PRICE_MULTIPLIER,
|
||||
filter_and_sort_prices_fn: FilterAndSortPricesFn = filter_and_sort_prices,
|
||||
order_uuid_factory: Callable[[], str] = lambda: str(uuid4()),
|
||||
) -> None:
|
||||
self._providers = tuple(providers)
|
||||
self._cache = cache
|
||||
@@ -178,9 +192,11 @@ class AggregatorService:
|
||||
self._payment_price_validation_adapter = payment_price_validation_adapter
|
||||
self._order_repository = order_repository
|
||||
self._order_registration_adapter = order_registration_adapter
|
||||
self._email_sender = email_sender
|
||||
self._weight_round_scale = weight_round_scale
|
||||
self._provider_price_multiplier = provider_price_multiplier
|
||||
self._filter_and_sort_prices = filter_and_sort_prices_fn
|
||||
self._order_uuid_factory = order_uuid_factory
|
||||
self._address_suggestion_providers: dict[str, AddressSuggestionProvider] = {
|
||||
provider.name: provider for provider in address_suggestion_providers
|
||||
}
|
||||
@@ -259,17 +275,19 @@ class AggregatorService:
|
||||
if self._payment_adapter is None:
|
||||
raise InitPaymentUnavailableError("Payment adapter is not configured.")
|
||||
|
||||
await self._validate_init_payment_price(request)
|
||||
order_uuid = self._order_uuid_factory()
|
||||
|
||||
await self._validate_init_payment_price(request, order_uuid)
|
||||
|
||||
try:
|
||||
payment_url = await self._payment_adapter.create_payment_link(
|
||||
order_uuid=request.order_uuid,
|
||||
order_uuid=order_uuid,
|
||||
amount_kopecks=request.system_data.tariff.price,
|
||||
)
|
||||
except TBankPaymentRequestError as exc:
|
||||
logger.exception(
|
||||
"payment_init_rejected",
|
||||
order_uuid=request.order_uuid,
|
||||
order_uuid=order_uuid,
|
||||
provider_status_code=exc.status_code,
|
||||
provider_error_code=exc.error_code,
|
||||
provider_error_message=exc.provider_message,
|
||||
@@ -287,12 +305,15 @@ class AggregatorService:
|
||||
"Payment initialization is temporarily unavailable."
|
||||
) from exc
|
||||
|
||||
await self._persist_order(request=request, payment_url=payment_url)
|
||||
await self._persist_order(
|
||||
request=request, order_uuid=order_uuid, payment_url=payment_url
|
||||
)
|
||||
return InitPaymentResponse(payment_url=payment_url)
|
||||
|
||||
async def _validate_init_payment_price(
|
||||
self,
|
||||
request: InitPaymentRequest,
|
||||
order_uuid: str,
|
||||
) -> None:
|
||||
if self._payment_price_validation_adapter is None:
|
||||
raise InitPaymentUnavailableError(
|
||||
@@ -308,7 +329,7 @@ class AggregatorService:
|
||||
except ProviderRequestError as exc:
|
||||
logger.warning(
|
||||
"init_payment_price_validation_request_rejected",
|
||||
order_uuid=request.order_uuid,
|
||||
order_uuid=order_uuid,
|
||||
tariff_code=tariff_code,
|
||||
requested_price_kopecks=requested_price,
|
||||
error=str(exc),
|
||||
@@ -319,7 +340,7 @@ class AggregatorService:
|
||||
except ProviderClientError as exc:
|
||||
logger.warning(
|
||||
"init_payment_price_validation_unavailable",
|
||||
order_uuid=request.order_uuid,
|
||||
order_uuid=order_uuid,
|
||||
tariff_code=tariff_code,
|
||||
requested_price_kopecks=requested_price,
|
||||
error=str(exc),
|
||||
@@ -330,7 +351,7 @@ class AggregatorService:
|
||||
except Exception as exc:
|
||||
logger.exception(
|
||||
"init_payment_price_validation_unexpected_error",
|
||||
order_uuid=request.order_uuid,
|
||||
order_uuid=order_uuid,
|
||||
tariff_code=tariff_code,
|
||||
requested_price_kopecks=requested_price,
|
||||
)
|
||||
@@ -341,7 +362,7 @@ class AggregatorService:
|
||||
if provider_price is None:
|
||||
logger.warning(
|
||||
"init_payment_price_validation_tariff_not_found",
|
||||
order_uuid=request.order_uuid,
|
||||
order_uuid=order_uuid,
|
||||
tariff_code=tariff_code,
|
||||
requested_price_kopecks=requested_price,
|
||||
)
|
||||
@@ -360,7 +381,7 @@ class AggregatorService:
|
||||
):
|
||||
logger.warning(
|
||||
"init_payment_price_mismatch",
|
||||
order_uuid=request.order_uuid,
|
||||
order_uuid=order_uuid,
|
||||
tariff_code=tariff_code,
|
||||
requested_price_kopecks=requested_price,
|
||||
expected_price_kopecks=expected_amount_kopecks,
|
||||
@@ -405,13 +426,64 @@ class AggregatorService:
|
||||
if existing_cdek_order_uuid:
|
||||
return "OK"
|
||||
|
||||
registration_result = await self._register_cdek_order(order)
|
||||
registration_result = await self._register_cdek_order(
|
||||
order, order_uuid=notification.OrderId
|
||||
)
|
||||
await self._save_cdek_order_uuid(
|
||||
order_uuid=notification.OrderId,
|
||||
cdek_order_uuid=registration_result.order_uuid,
|
||||
)
|
||||
await self._send_payment_confirmation_email(
|
||||
order, order_uuid=notification.OrderId
|
||||
)
|
||||
return "OK"
|
||||
|
||||
async def _send_payment_confirmation_email(
|
||||
self,
|
||||
order: object,
|
||||
*,
|
||||
order_uuid: str,
|
||||
) -> None:
|
||||
if self._email_sender is None or self._order_repository is None:
|
||||
return
|
||||
|
||||
if getattr(order, "payment_email_sent_at", None) is not None:
|
||||
return
|
||||
|
||||
account_email = getattr(order, "account_email", None)
|
||||
if account_email is None:
|
||||
return
|
||||
|
||||
try:
|
||||
await self._email_sender.send_email(
|
||||
to=account_email,
|
||||
subject=f"Оплата по заказу {order_uuid} принята",
|
||||
body=(
|
||||
f"Здравствуйте!\n\n"
|
||||
f"Оплата по заказу {order_uuid} успешно принята.\n"
|
||||
f"Накладная будет отправлена на этот адрес в ближайшее время.\n\n"
|
||||
f"Спасибо за заказ!"
|
||||
),
|
||||
)
|
||||
async with self._order_repository.session() as session:
|
||||
await self._order_repository.record_payment_email_sent(
|
||||
session,
|
||||
order_uuid=order_uuid,
|
||||
sent_at=datetime.now(timezone.utc),
|
||||
)
|
||||
logger.info(
|
||||
"payment_confirmation_email_sent",
|
||||
order_uuid=order_uuid,
|
||||
account_email=account_email,
|
||||
)
|
||||
except Exception:
|
||||
logger.warning(
|
||||
"payment_confirmation_email_failed",
|
||||
order_uuid=order_uuid,
|
||||
account_email=account_email,
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
async def _load_order_and_mark_payment_status(
|
||||
self,
|
||||
notification: TBankPaymentNotification,
|
||||
@@ -464,7 +536,7 @@ class AggregatorService:
|
||||
) from exc
|
||||
|
||||
async def _register_cdek_order(
|
||||
self, order: object
|
||||
self, order: object, *, order_uuid: str
|
||||
) -> CDEKOrderRegistrationResult:
|
||||
if self._order_registration_adapter is None:
|
||||
raise TBankPaymentNotificationProcessingError(
|
||||
@@ -473,7 +545,9 @@ class AggregatorService:
|
||||
|
||||
try:
|
||||
request = self._to_init_payment_request_from_order(order)
|
||||
return await self._order_registration_adapter.register_order(request)
|
||||
return await self._order_registration_adapter.register_order(
|
||||
request, order_uuid
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.exception(
|
||||
"cdek_order_registration_failed",
|
||||
@@ -526,6 +600,7 @@ class AggregatorService:
|
||||
self,
|
||||
*,
|
||||
request: InitPaymentRequest,
|
||||
order_uuid: str,
|
||||
payment_url: str,
|
||||
) -> None:
|
||||
if self._order_repository is None:
|
||||
@@ -535,22 +610,27 @@ class AggregatorService:
|
||||
async with self._order_repository.session() as session:
|
||||
await self._order_repository.create_order(
|
||||
session,
|
||||
self._to_order_data(request=request, payment_url=payment_url),
|
||||
self._to_order_data(
|
||||
request=request,
|
||||
order_uuid=order_uuid,
|
||||
payment_url=payment_url,
|
||||
),
|
||||
)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"order_persistence_failed",
|
||||
order_uuid=request.order_uuid,
|
||||
order_uuid=order_uuid,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _to_order_data(
|
||||
*,
|
||||
request: InitPaymentRequest,
|
||||
order_uuid: str,
|
||||
payment_url: str,
|
||||
) -> OrderData:
|
||||
return OrderData(
|
||||
order_uuid=request.order_uuid,
|
||||
order_uuid=order_uuid,
|
||||
payment_url=payment_url,
|
||||
price=request.system_data.tariff.price,
|
||||
tariff_code=request.system_data.tariff.tariff_code,
|
||||
|
||||
Reference in New Issue
Block a user