Универсальная система интеграции с курьерскими службами через конфигурационные файлы.
modules/delivery/
├── __init__.py # Экспорты
├── universal.py # Универсальный клиент (базовый)
├── yandex.py # Яндекс Доставка (расширенный)
└── README.md # Эта документация
config/delivery/
├── yandex.yaml # Конфиг Яндекс Доставка
├── dpd.yaml # Конфиг DPD
└── boxberry.yaml # Конфиг Boxberry
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']}")
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']}")
boxberry = UniversalDeliveryAPI(service_name='boxberry')
order = boxberry.create_order({
"order_number": "ORDER-456",
"recipient_name": "Сидоров С.С.",
"recipient_phone": "+79005556677",
"city": "77000000000", # Код города (Москва)
"weight": 500
})
Создайте файл 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"
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
})
Готово! Не нужно писать ни строчки кода.
# Название службы
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"
auth_type: "basic"
auth:
username: "login"
password: "password"
auth_type: "bearer"
auth:
token: "your_bearer_token"
auth_type: "oauth2"
auth:
client_id: "your_client_id"
client_secret: "your_secret"
token_url: "https://api.example.com/oauth/token" # Опционально
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": "Иванов Иван"
# }
# }
# }
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
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]
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]
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']}")
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