Интеграция службы доставки Почты России на сайт
Почта России — самая широкая сеть доставки в стране: отделения есть даже там, где нет других служб. API Почты России (pochta.ru) позволяет рассчитать стоимость, создать заказы, заказать вызов курьера и отслеживать посылки. API реализован на REST + JSON, авторизация через токен.
Авторизация
Почта России использует токены, которые выдаются в личном кабинете. Для отдельных методов — базовая авторизация (логин + пароль в base64), для других — Authorization: AccessToken.
class RussianPostClient
{
private string $baseUrl = 'https://otpravka-api.pochta.ru/1.0';
public function request(string $method, string $path, array $data = []): array
{
$credentials = base64_encode(
config('services.russian_post.login') . ':' . config('services.russian_post.password')
);
$response = Http::withHeaders([
'Authorization' => 'AccessToken ' . config('services.russian_post.token'),
'X-User-Authorization' => 'Basic ' . $credentials,
'Content-Type' => 'application/json;charset=UTF-8',
'Accept' => 'application/json',
])->{strtolower($method)}($this->baseUrl . $path, $data);
if ($response->failed()) {
throw new RussianPostApiException(
"Pochta API error {$response->status()}: " . $response->body()
);
}
return $response->json() ?? [];
}
}
Нормализация адреса
Перед созданием заказа адрес нужно нормализовать — Почта России требует стандартизированных данных:
public function normalizeAddress(string $rawAddress): array
{
$response = Http::withHeaders($this->headers())
->post($this->baseUrl . '/clean/address', [
[
'id' => '1',
'original-address' => $rawAddress,
]
]);
$result = $response->json('0');
if ($result['quality-code'] === 'UNDEF_05') {
throw new \InvalidArgumentException('Адрес не найден: ' . $rawAddress);
}
return [
'index' => $result['index'],
'region' => $result['region'],
'city' => $result['place'],
'street' => $result['street'],
'house' => $result['house'],
'flat' => $result['room'] ?? '',
'raw_name' => $result['raw-address'],
];
}
Коды качества: GOOD — адрес точно определён, POSTAL_BOX — а/я, UNDEF_05 — не определён. Отображать покупателю форму с уточнением если код не GOOD.
Расчёт стоимости
public function calculateDelivery(
string $fromIndex, // почтовый индекс отправителя
string $toIndex, // почтовый индекс получателя
string $mailType, // POSTAL_PARCEL, ECOM_MARKETPLACE и др.
int $weightGrams,
int $declaredValueKopecks = 0
): array {
$response = $this->request('POST', '/tariff', [
'index-from' => $fromIndex,
'index-to' => $toIndex,
'mail-category' => 'ORDINARY', // ORDINARY, ORDERED, WITH_DECLARED_VALUE
'mail-type' => $mailType,
'mass' => $weightGrams,
'payment' => $declaredValueKopecks,
'with-order-of-notice' => false,
'with-simple-notice' => false,
]);
return [
'total_kopecks' => $response['total-rate'] + ($response['total-vat'] ?? 0),
'total_rubles' => ($response['total-rate'] + ($response['total-vat'] ?? 0)) / 100,
'delivery_days_min' => $response['delivery-time']['min-days'] ?? null,
'delivery_days_max' => $response['delivery-time']['max-days'] ?? null,
];
}
Тип отправления POSTAL_PARCEL — обычная посылка. ECOM_MARKETPLACE — для маркетплейсов (нужен отдельный договор). EMS — ускоренная почта.
Создание заказа
public function createOrder(Order $order): array
{
$payload = [[
'order-num' => (string)$order->id,
'address-type-to' => 'DEFAULT',
'index-to' => $order->normalized_index,
'region-to' => $order->normalized_region,
'place-to' => $order->normalized_city,
'street-to' => $order->normalized_street,
'house-to' => $order->normalized_house,
'room-to' => $order->normalized_flat ?? '',
'mail-category' => 'ORDINARY',
'mail-type' => 'POSTAL_PARCEL',
'mass' => (int)($order->total_weight_kg * 1000),
'dimension' => [
'height' => $order->package_height * 10, // мм
'length' => $order->package_length * 10,
'width' => $order->package_width * 10,
],
'recipient-name' => $order->recipient_name,
'tel-address' => preg_replace('/\D/', '', $order->recipient_phone),
'payment' => 0, // объявленная ценность в копейках
'fragile' => false,
'with-fitting' => false,
]];
$response = $this->request('PUT', '/user/backlog', $payload);
if (isset($response[0]['errors'])) {
throw new RussianPostOrderException(
'Order creation failed: ' . json_encode($response[0]['errors'])
);
}
return [
'result_id' => $response[0]['result-id'],
'barcode' => $response[0]['barcode'] ?? null,
];
}
Перевод в отправку и получение ШПИ
Созданный заказ находится в «бэклоге». Для получения штрихкода (ШПИ) нужно создать партию и добавить заказы в неё:
public function createBatch(string $mailType, string $mailCategory, string $fromIndex): string
{
$response = $this->request('POST', '/batch', [
'mail-type' => $mailType,
'mail-category' => $mailCategory,
'send-date' => now()->format('Y-m-d'),
'shipment-point-index' => $fromIndex,
]);
return $response['batch-name'];
}
public function addOrdersToBatch(string $batchName, array $orderIds): void
{
$this->request('POST', "/batch/{$batchName}/backlog", $orderIds);
}
После добавления в партию заказам присваиваются ШПИ (14-значный штрихкод), который можно распечатать и наклеить на посылку.
Отслеживание по трек-номеру
Почта России предоставляет отдельный API для отслеживания (tracking.pochta.ru):
public function trackParcel(string $barcode): array
{
// Трекинг-API использует SOAP или отдельный REST
$response = Http::withToken(config('services.russian_post.tracking_token'))
->get('https://tracking.pochta.ru/tracking/api/v1/operations-history', [
'Barcode' => $barcode,
'Language' => 'RUS',
]);
return collect($response->json('OperationHistoryData.historyRecord'))
->map(fn($op) => [
'date' => $op['OperationParameters']['OperDate'],
'type' => $op['OperationParameters']['OperType']['Name'],
'attribute' => $op['OperationParameters']['OperAttr']['Name'],
'city' => $op['AddressParameters']['DestinationAddress']['PostalAddress']['City'] ?? '',
'index' => $op['AddressParameters']['DestinationAddress']['PostalAddress']['Index'] ?? '',
])
->toArray();
}
Трекинговый API — отдельные ключи доступа. Бесплатная квота — 100 запросов в сутки на один трек-номер, платные пакеты — от 1 000 до 100 000 запросов.
Партионная печать этикеток
public function printLabels(array $orderIds, string $sendingType = 'ONE_SIDED'): string
{
$response = Http::withHeaders($this->headers())
->post($this->baseUrl . '/forms/orders/backlog?' . http_build_query([
'print-type' => $sendingType,
]), $orderIds, ['Accept' => 'application/pdf']);
return $response->body(); // PDF
}
Особенности работы с API
Индексы пунктов доставки нужно нормализовать через их же API — вручную вводимые покупателями индексы часто содержат опечатки или устаревшие данные. Максимальный вес посылки — 20 кг, объём — не более 2 м³.
Sandbox для тестирования: https://otpravka-api.pochta.ru/1.0 — тот же URL, тестовые credentials выдаются отдельно. Реальных посылок не создаётся, но ШПИ генерируются.
Сроки
Расчёт тарифа + нормализация адреса + создание заказов + этикетки — 6–8 рабочих дней. Сложнее, чем СДЭК или Boxberry, из-за двухшаговой модели (бэклог → партия), нормализации адресов и разделения трекинг-API. Полное тестирование — 2–3 дня.







