architect/_archive/2025-11-09-marketplace-old/marketplace-mvp/modules/delivery/README.md

📦 Модуль интеграции со службами доставки

Универсальная система интеграции с курьерскими службами через конфигурационные файлы.


✨ Возможности


📁 Структура

modules/delivery/
├── __init__.py           # Экспорты
├── universal.py          # Универсальный клиент (базовый)
├── yandex.py            # Яндекс Доставка (расширенный)
└── README.md            # Эта документация

config/delivery/
├── yandex.yaml          # Конфиг Яндекс Доставка
├── dpd.yaml             # Конфиг DPD
└── boxberry.yaml        # Конфиг Boxberry

🚀 Быстрый старт

1. Яндекс Доставка

from modules.delivery import YandexDeliveryAPI

# Инициализация
yandex = YandexDeliveryAPI(token="your_token_here")

# Создать заказ на доставку
order = yandex.create_delivery(
    pickup_address="Москва, ул. Ленина, д. 1",
    delivery_address="Москва, ул. Пушкина, д. 2, кв. 5",
    recipient_name="Иванов Иван Иванович",
    recipient_phone="+79001234567",
    comment="Позвонить за 30 минут",
    tariff="same_day"
)

print(f"Заказ создан: {order['order_id']}")
print(f"Трек-номер: {order['tracking_number']}")

# Отследить заказ
statuses = yandex.track(order['tracking_number'])
for status in statuses:
    print(f"{status['timestamp']}: {status['status']}")

# Рассчитать стоимость
price = yandex.estimate_price(route_points=[
    {"coordinates": [37.6, 55.7], "address": "Москва, ул. Ленина, 1"},
    {"coordinates": [37.7, 55.8], "address": "Москва, ул. Пушкина, 2"}
])
print(f"Стоимость доставки: {price['price']} руб")

# Информация о курьере
courier = yandex.get_courier_info(order['order_id'])
if courier:
    print(f"Курьер: {courier['name']}, тел: {courier['phone']}")

2. DPD (через универсальный модуль)

from modules.delivery import UniversalDeliveryAPI

# Загружается config/delivery/dpd.yaml
dpd = UniversalDeliveryAPI(service_name='dpd')

# Создать заказ (стандартный формат)
order = dpd.create_order({
    "order_number": "ORDER-123",
    "recipient_name": "Петров П.П.",
    "recipient_phone": "+79009876543",
    "address": "Санкт-Петербург, Невский пр., 1",
    "weight": 1000,  # граммы
    "dimensions": {"length": 30, "width": 20, "height": 10}
})

print(f"DPD заказ: {order['tracking_number']}")

3. Boxberry

boxberry = UniversalDeliveryAPI(service_name='boxberry')

order = boxberry.create_order({
    "order_number": "ORDER-456",
    "recipient_name": "Сидоров С.С.",
    "recipient_phone": "+79005556677",
    "city": "77000000000",  # Код города (Москва)
    "weight": 500
})

🔧 Добавление новой службы доставки

Шаг 1: Создать конфиг

Создайте файл config/delivery/my_delivery.yaml:

name: "My Delivery Service"

base_url: "https://api.mydelivery.com/v1"

# Тип авторизации: basic | bearer | oauth2 | api_key | none
auth_type: "bearer"

auth:
  token: "${MY_DELIVERY_TOKEN}"

endpoints:
  create_order: "/orders"
  get_order: "/orders/{order_id}"
  get_label: "/labels/{order_id}.pdf"
  track: "/tracking/{tracking_number}"

# Маппинг: ваши поля -> поля API
mapping:
  request:
    order_number: "external_id"
    recipient_name: "customer.name"
    recipient_phone: "customer.phone"
    address: "delivery.address"
    weight: "parcel.weight_g"

  response:
    order_id: "id"
    tracking_number: "track_code"
    status: "state"

status_mapping:
  created: "created"
  processing: "pending"
  on_way: "in_transit"
  completed: "delivered"

Шаг 2: Использовать

from modules.delivery import UniversalDeliveryAPI

my_delivery = UniversalDeliveryAPI(service_name='my_delivery')

order = my_delivery.create_order({
    "order_number": "ORDER-789",
    "recipient_name": "Клиент",
    "recipient_phone": "+79001112233",
    "address": "Адрес доставки",
    "weight": 1500
})

Готово! Не нужно писать ни строчки кода.


📝 Формат конфигурации

Полный пример YAML

# Название службы
name: "Service Name"

# Base URL API
base_url: "https://api.example.com"

# Тип авторизации
auth_type: "oauth2"  # basic | bearer | oauth2 | api_key | none

# Учетные данные
auth:
  # Для basic:
  username: "login"
  password: "password"

  # Для bearer:
  token: "your_token"

  # Для oauth2:
  client_id: "client_id"
  client_secret: "client_secret"
  token_url: "https://api.example.com/oauth/token"

  # Для api_key:
  header: "X-API-Key"  # Название заголовка
  key: "api_key_value"

# Дополнительные заголовки
headers:
  Content-Type: "application/json"
  Accept-Language: "ru"

# Endpoints
endpoints:
  create_order: "/orders"
  get_order: "/orders/{order_id}"
  cancel_order: "/orders/{order_id}/cancel"
  get_label: "/labels/{order_id}.pdf"
  track: "/tracking/{tracking_number}"

# Маппинг полей
mapping:
  request:
    # Ваше поле: "путь.в.api.структуре"
    order_number: "number"
    recipient_name: "receiver.name"
    recipient_phone: "receiver.phone"
    address: "receiver.address.full"
    weight: "package.weight"

  response:
    # Стандартное поле: "путь.в.ответе.api"
    order_id: "id"
    tracking_number: "track_number"
    status: "status"
    price: "cost.total"

# Соответствие статусов
status_mapping:
  api_status_1: "created"
  api_status_2: "in_transit"
  api_status_3: "delivered"

# Параметры по умолчанию
defaults:
  tariff: "standard"
  payment_type: "prepaid"

🎯 Типы авторизации

Basic Auth

auth_type: "basic"
auth:
  username: "login"
  password: "password"

Bearer Token

auth_type: "bearer"
auth:
  token: "your_bearer_token"

OAuth2

auth_type: "oauth2"
auth:
  client_id: "your_client_id"
  client_secret: "your_secret"
  token_url: "https://api.example.com/oauth/token"  # Опционально

API Key в заголовке

auth_type: "api_key"
auth:
  header: "X-API-Key"  # Название заголовка
  key: "your_api_key"

Без авторизации

auth_type: "none"

📊 Маппинг полей

Маппинг позволяет преобразовать ваш стандартный формат в формат конкретного API.

Простое соответствие

mapping:
  request:
    order_number: "order_id"  # Ваше поле -> Поле API

Вложенные поля

Используйте точку для вложенности:

mapping:
  request:
    recipient_name: "customer.contact.name"
    # Преобразуется в:
    # {
    #   "customer": {
    #     "contact": {
    #       "name": "Иванов Иван"
    #     }
    #   }
    # }

Обратный маппинг (response)

mapping:
  response:
    order_id: "data.order.id"
    # Из ответа API: {"data": {"order": {"id": "12345"}}}
    # Получим: {"order_id": "12345"}

🧪 Тестирование

# Тест Яндекс Доставка
python tests/test_yandex_delivery.py

# Тест DPD
python tests/test_dpd_delivery.py

🔐 Переменные окружения

Вместо хардкода токенов используйте переменные окружения:

auth:
  token: "${YANDEX_DELIVERY_TOKEN}"

В .env файле:

YANDEX_DELIVERY_TOKEN=your_actual_token_here
DPD_USERNAME=your_dpd_login
DPD_PASSWORD=your_dpd_password
BOXBERRY_API_KEY=your_boxberry_key

📚 API Reference

UniversalDeliveryAPI

class UniversalDeliveryAPI:
    def __init__(
        self,
        config_file: Optional[str] = None,
        config_dict: Optional[Dict] = None,
        service_name: Optional[str] = None
    )

    def create_order(self, order_data: Dict) -> Dict
    def get_order(self, order_id: str) -> Dict
    def get_label(self, order_id: str) -> bytes
    def track(self, tracking_number: str) -> List[Dict]

YandexDeliveryAPI

class YandexDeliveryAPI(UniversalDeliveryAPI):
    def __init__(self, token: Optional[str] = None, config_file: Optional[str] = None)

    def create_delivery(
        self,
        pickup_address: str,
        delivery_address: str,
        recipient_name: str,
        recipient_phone: str,
        comment: Optional[str] = None,
        items: Optional[List[Dict]] = None,
        tariff: str = "same_day"
    ) -> Dict

    def estimate_price(self, route_points: List[Dict]) -> Dict
    def cancel_order(self, order_id: str, reason: str = "Отмена клиентом") -> bool
    def get_courier_info(self, order_id: str) -> Optional[Dict]

🎓 Примеры использования

Интеграция с Order (база данных)

from database.models import Order, get_session
from modules.delivery import YandexDeliveryAPI

session = get_session()

# Получаем заказ из БД
order = session.query(Order).filter_by(id=123).first()

# Создаем доставку
yandex = YandexDeliveryAPI(token="...")
delivery = yandex.create_delivery(
    pickup_address="Ваш склад",
    delivery_address=order.delivery_address,
    recipient_name=order.customer_name,
    recipient_phone=order.customer_phone,
    comment=f"Заказ #{order.number}"
)

# Сохраняем трек-номер
order.tracking_number = delivery['tracking_number']
order.delivery_service = "yandex"
session.commit()

print(f"Доставка создана: {delivery['tracking_number']}")

❓ FAQ

Q: Как добавить новую службу доставки?
A: Создайте YAML конфиг в config/delivery/ и используйте UniversalDeliveryAPI(service_name='...')

Q: Поддерживается ли SOAP?
A: Нет, только REST API. Для SOAP создайте отдельный модуль.

Q: Можно ли переопределить маппинг в коде?
A: Да, передайте config_dict с нужными настройками:

custom_config = {
    "name": "Custom",
    "base_url": "...",
    "mapping": {...}
}
api = UniversalDeliveryAPI(config_dict=custom_config)

Q: Как обрабатывать ошибки?
A: Используйте try/except:

try:
    order = api.create_order(data)
except DeliveryAuthError:
    print("Ошибка авторизации")
except DeliveryRateLimitError:
    print("Превышен лимит запросов")
except DeliveryError as e:
    print(f"Ошибка: {e}")

Автор: Marketplace MVP Team
Версия: 1.0.0
Дата: 2025-11-08