Интеграция службы доставки Boxberry на сайт
Boxberry — сеть доставки с упором на пункты выдачи: более 15 000 точек по России. API достаточно прямолинейное: токен в заголовке или параметре, JSON в ответе, никакого OAuth. Это упрощает интеграцию, но требует внимания к обработке ошибок — Boxberry иногда возвращает ошибки в теле 200-ответа вместо HTTP-статусов.
Базовая структура клиента
class BoxberryClient
{
private string $baseUrl = 'https://api.boxberry.ru/json.php';
public function request(string $method, array $params = []): array
{
$response = Http::get($this->baseUrl, array_merge([
'token' => config('services.boxberry.token'),
'method' => $method,
], $params));
$data = $response->json();
// Boxberry возвращает ошибки как {"err":"текст ошибки"}
if (isset($data['err'])) {
throw new BoxberryApiException("Boxberry API error [{$method}]: {$data['err']}");
}
return $data;
}
}
Расчёт стоимости доставки
Метод DeliveryCosts считает стоимость по коду города или индексу ПВЗ:
public function calculateDelivery(
string $targetCity, // код города Boxberry
float $weightKg,
array $dimensions, // ['length'=>20, 'width'=>15, 'height'=>10], см
float $declaredValue = 0,
bool $toPickupPoint = true
): array {
$params = [
'token' => config('services.boxberry.token'),
'method' => $toPickupPoint ? 'DeliveryCosts' : 'DeliveryCostsD2D',
'weight' => (int)($weightKg * 1000), // граммы
'target' => $targetCity,
'OrderSum' => (int)$declaredValue,
'height' => $dimensions['height'],
'width' => $dimensions['width'],
'depth' => $dimensions['length'],
];
$response = Http::get($this->baseUrl, $params)->json();
if (isset($response['err'])) {
throw new BoxberryApiException($response['err']);
}
return [
'price' => (float)$response['price'],
'price_base' => (float)($response['price_base'] ?? $response['price']),
'min_days' => (int)$response['delivery_period'],
];
}
Boxberry разделяет доставку до ПВЗ (DeliveryCosts) и курьерскую до двери (DeliveryCostsD2D). Второй метод доступен не везде — перед показом опции курьерской доставки стоит проверить доступность.
Список городов с ПВЗ
Справочник городов загружается один раз и кешируется:
public function getCitiesWithPoints(): array
{
return Cache::remember('boxberry_cities', now()->addDay(), function () {
return $this->request('ListCitiesShort');
// Возвращает: [{"Code":"77","Name":"Москва","Prefix":"77"}, ...]
});
}
Пункты выдачи
public function getPickupPoints(?string $cityCode = null, bool $prepaid = true): array
{
$params = ['prepaid' => $prepaid ? 1 : 0];
if ($cityCode) {
$params['CityCode'] = $cityCode;
}
$points = $this->request('ListPoints', $params);
return collect($points)->map(fn($p) => [
'code' => $p['Code'],
'name' => $p['Name'],
'address' => $p['Address'],
'city' => $p['CityName'],
'lat' => (float)$p['GPS']['Latitude'],
'lng' => (float)$p['GPS']['Longitude'],
'work_time' => $p['WorkShedule'],
'phone' => $p['Phone'] ?? null,
'metro' => $p['Metro'] ?? null,
'cash_allowed'=> (bool)($p['HaveCash'] ?? false),
'card_allowed'=> (bool)($p['HaveCardPayment'] ?? false),
'fitting_room'=> (bool)($p['ForeignOnlineStore'] ?? false),
])->toArray();
}
Метод ListPoints без параметра города возвращает все ПВЗ России — это несколько мегабайт JSON. Лучше фильтровать по городу или загружать всё сразу и хранить локально.
Создание посылки
public function createParcel(Order $order): string
{
$items = $order->items->map(fn($item) => [
'id' => (string)$item->product_id,
'name' => $item->product->name,
'UnitName' => 'шт.',
'nds' => '20',
'price' => $item->price,
'quantity'=> $item->quantity,
])->toArray();
$payload = [
'order_id' => (string)$order->id,
'barcode' => '', // Boxberry присвоит сам
'targetstart' => $order->pickup_point_code,
'price' => $order->delivery_cost,
'payment_sum' => $order->is_prepaid ? 0 : $order->total, // наложенный платёж
'delivery_sum' => $order->delivery_cost,
'vid' => 1, // 1=ПВЗ, 2=курьер
'kurdost' => ['idx' => $order->zip, 'citi' => $order->city, 'address' => $order->address],
'customer' => [
'fio' => $order->recipient_name,
'phone' => $order->recipient_phone,
'email' => $order->recipient_email,
],
'items' => $items,
'weights' => [
'weight' => (int)($order->total_weight_kg * 1000),
'x' => $order->package_length,
'y' => $order->package_width,
'z' => $order->package_height,
],
];
$response = Http::withHeaders(['Content-Type' => 'application/json'])
->post('https://api.boxberry.ru/json.php?token=' . config('services.boxberry.token') . '&method=ParselCreate',
$payload
)->json();
if (isset($response['err'])) {
throw new BoxberryApiException('ParselCreate: ' . $response['err']);
}
return $response['track']; // трек-номер
}
Отслеживание заказа
public function trackParcel(string $trackNumber): array
{
$response = $this->request('ListStatuses', ['ImId' => $trackNumber]);
return collect($response)->map(fn($s) => [
'date' => $s['Date'],
'name' => $s['Name'],
'comment' => $s['Comment'] ?? '',
'city' => $s['CityName'] ?? '',
])->toArray();
}
Создание акта приёма-передачи
Перед передачей посылок в Boxberry нужно сформировать акт:
public function createTransferAct(array $trackNumbers): array
{
$response = Http::post(
'https://api.boxberry.ru/json.php?token=' . config('services.boxberry.token') . '&method=ParselSend',
['ImIds' => $trackNumbers]
)->json();
// Возвращает {'label': 'номер акта', 'sticker': 'base64 PDF со стикерами'}
return $response;
}
Получение этикеток
public function getLabel(string $trackNumber): string
{
// Возвращает base64-encoded PDF
$response = $this->request('ParselGetLabel', ['ImId' => $trackNumber]);
return base64_decode($response['pdf']);
}
Особенности и ограничения
Boxberry работает только с российскими адресами. Максимальный вес посылки — 31 кг, максимальный размер стороны — 150 см. Наложенный платёж доступен не во всех городах.
При тестировании используется тот же URL, но тестовый токен (выдаётся менеджером). Реальные посылки не создаются — данные уходят в тестовую среду.
Сроки
Расчёт стоимости + список ПВЗ на карте + создание заказов — 4–6 рабочих дней. Тестирование с реальным тестовым токеном, проверка граничных случаев (нестандартный вес, недоступный город) — 1–2 дня.







