Перед вами полное SDK для интеграции с программным комплексом СДЭК.
Возможности:
- расчёт тарифов и обращения к справочникам
- управление заказами
- формирование новых заказов от ИМ 🔐
- оформление заказов на доставку
- получение квитанции в PDF
- получение почтовых этикеток в PDF
- удаление заказов
- изменение заказов
- получение информации по заказам (отчёт «Информация по заказам»)
- трекинг заказов (отчёт «Статусы заказов»)
- прозвон получателя
- вызов курьера
- создание преалерта
- выбор базового URL интерфейса
- сервис-провайдер для Laravel 5.1+
- отладка получаемых ответов и посылаемых запросов
- Чего-то нет в списке? Напишите, сообщите.
Работа с большинством методов API возможна только при наличии договора со СДЭК.
🔓 | Методы, отмеченные значком слева, доступны без договора в ограниченном объеме (рассчитываются только публичные тарифы, без скидок, без тарифов для ИМ). |
🔐 | Методы, отмеченные таким знаком, недоступны с тестовой учетной записью. |
composer require sanmai/cdek-sdk
Требования — минимальны. Нужен PHP 7.0 или выше. Работа протестирована под PHP 7.0, 7.1, 7.2.
При разработке этой библиотеки большое внимание уделяется обратной совместимости API в пределах основной версии. Если вы установили когда-то версию ветки 0.6, например 0.6.7, то после обновления до 0.6.8 или даже до 0.6.12 вы можете рассчитывать что весь ваш код будет работать точно так же как раньше, без необходимости что-то менять, при условии, конечно, что API самих СДЭК не поменялось. Такого же принципа работы с версиями по умолчанию придерживается Composer.
Гарантии обратной совместимости в части возвращаемых типов распостраняются только на имплементируемые ими интерфейсы. Если вы получали объект имплементирующий Psr\Http\Message\ResponseInterface
, то такой же объёкт вы продолжите получать. Если у возвращенного объёкта был какой-то метод, то такой же метод будет у объекта в следующей неосновной версии. Конкретный тип может быть другим, рассчитывать на это не нужно, проверять принадлежность конкретному типу также не следует. Как проверять ответы на ошибки.
Такие строгие гарантии обратной совместимости API были бы невозможны без идущей рука об руку с ними минимизации точек для расширения API: наследование для большинства классов не только не предусмотрено, но и просто невозможно. Впрочем, для удобства композиции есть необходимые интерфейсы. Мы исходим из того что добавить ещё интерфейсы проблемы не представляет, новые интерфейсы не ломают обратную совместимость.
После выхода версии 1.0 обратная совместимость будет поддерживаться в пределах мажорной версии.
require_once 'vendor/autoload.php';
$client = new \CdekSDK\CdekClient('account', 'password');
Реквизиты доступа следует запросить у СДЭК. Обычные логин и пароль не подходят. Если авторизация не нужна, логин и пароль можно указать пустые или пропустить вовсе.
Далее для всей работы с API используются методы объёкта $client
, который мы получили выше.
Для подготовки запросов и ответов используются аннотации из Doctrine. Если вы не знаете что это, то ничего не нужно делать. Иначе обратите внимание на замечания к совместному использованию AnnotationRegistry.
Перечень основных методов класса CdekClient
ниже.
Задача | Метод | Аргумент |
---|---|---|
Удаление заказа | sendDeleteRequest |
DeleteRequest |
Получение списка ПВЗ | sendPvzListRequest |
PvzListRequest |
Список субъектов РФ | sendRegionsRequest |
RegionsRequest |
Список городов | sendCitiesRequest |
CitiesRequest |
Регистрация заказа от ИМ | sendDeliveryRequest |
DeliveryRequest |
Регистрация заказа на доставку | sendAddDeliveryRequest |
AddDeliveryRequest |
Изменение заказа | sendUpdateRequest |
UpdateRequest |
Регистрация результата прозвона | sendScheduleRequest |
ScheduleRequest |
Вызов курьера | sendCallCourierRequest |
CallCourierRequest |
Создание преалерта | sendPreAlertRequest |
PreAlertRequest |
Отчет "Информация по заказам" | sendInfoReportRequest |
InfoReportRequest |
Расчёт стоимости доставки | sendCalculationRequest |
CalculationRequest |
Отчет "Статусы заказов" | sendStatusReportRequest |
StatusReportRequest |
Печать квитанции к заказу | sendPrintReceiptsRequest |
PrintReceiptsRequest |
Печать ШК-мест | sendPrintLabelsRequest |
PrintLabelsRequest |
Все возвращаемые ответы содержат методы для проверки на ошибку, также для получения списка сообщений включая сообщения об ошибках.
/** @var \CdekSDK\Contracts\Response $response */
$response = $client->sendSomeRequest($request);
if ($response->hasErrors()) {
// Обрабатываем ошибки
foreach ($response->getMessages() as $message) {
if ($message->getErrorCode() !== '') {
// Это ошибка
$message->getMessage();
}
}
}
В редких случаях при запросе могут возникнуть исключения. Это недоразумение будет исправлено в следующих версиях.
use CdekSDK\Requests;
$request = new Requests\PvzListRequest();
$request->setCityId(250);
$request->setType(PvzListRequest::TYPE_ALL);
$request->setCashless(true);
$request->setCodAllowed(true);
$request->setDressingRoom(true);
$response = $client->sendPvzListRequest($request);
if ($response->hasErrors()) {
// обработка ошибок
}
/** @var \CdekSDK\Responses\PvzListResponse $response */
foreach ($response as $item) {
/** @var \CdekSDK\Common\Pvz $item */
// всевозможные параметры соответствуют полям из API СДЭК
$item->Code;
$item->Name;
$item->Address;
foreach ($item->OfficeImages as $image) {
$image->getUrl();
}
}
use CdekSDK\Requests;
// для выполнения авторизованного запроса используется
// $request = Requests\CalculationRequest::withAuthorization();
// $request->set...() и так далее
$request = new Requests\CalculationRequest();
$request->setSenderCityPostCode('295000')
->setReceiverCityPostCode('652632')
->setTariffId(1)
->addPackage([
'weight' => 0.2,
'length' => 25,
'width' => 15,
'height' => 10,
]);
$response = $client->sendCalculationRequest($request);
if ($response->hasErrors()) {
// обработка ошибок
}
/** @var \CdekSDK\Responses\CalculationResponse $response */
$response->getPrice();
// double(1250)
use CdekSDK\Requests;
$request = new Requests\RegionsRequest();
$request->setPage(0)->setSize(10);
$response = $client->sendRegionsRequest($request);
if ($response->hasErrors()) {
// обработка ошибок
}
foreach ($response as $region) {
/** @var \CdekSDK\Common\Region $region */
$region->getUuid();
$region->getName();
$region->getPrefix();
$region->getCode();
$region->getCodeExt();
$region->getFiasGuid();
$region->getCountryName();
$region->getCountryCode();
$region->getCountryCodeExt();
}
use CdekSDK\Requests;
$request = new Requests\CitiesRequest();
$request->setPage(0)->setSize(10)->setRegionCode(50);
$response = $client->sendCitiesRequest($request);
if ($response->hasErrors()) {
// обработка ошибок
}
foreach ($response as $location) {
/** @var \CdekSDK\Common\Location $location */
$location->getCityName();
$location->getCityCode();
$location->getCityUuid();
$location->getCountry();
$location->getCountryCode();
$location->getRegion();
$location->getRegionCode();
$location->getRegionCodeExt();
$location->getSubRegion();
$location->getPaymentLimit();
$location->getLatitude();
$location->getLongitude();
$location->getKladr();
$location->getFiasGuid();
}
Названия полей соответствуют названиям полей в официальной документации.
use CdekSDK\Common;
use CdekSDK\Requests;
$order = new Common\Order([
'Number' => 'TEST-123456',
'SendCity' => Common\City::create([
'Code' => 44, // Москва
]),
'RecCity' => Common\City::create([
'PostCode' => '630001', // Новосибирск
]),
'RecipientName' => 'Иван Петров',
'RecipientEmail' => 'petrov@test.ru',
'Phone' => '+7 (383) 202-22-50',
'TariffTypeCode' => 139, // Посылка дверь-дверь от ИМ
]);
$order->setAddress(Common\Address::create([
'Street' => 'Холодильная улица',
'House' => '16',
'Flat' => '22',
]));
$package = Common\Package::create([
'Number' => 'TEST-123456',
'BarCode' => 'TEST-123456',
'Weight' => 500, // Общий вес (в граммах)
'SizeA' => 10, // Длина (в сантиметрах), в пределах от 1 до 1500
'SizeB' => 10,
'SizeC' => 10,
]);
$package->addItem(new Common\Item([
'WareKey' => 'NN0001', // Идентификатор/артикул товара/вложения
'Cost' => 500, // Объявленная стоимость товара (за единицу товара)
'Payment' => 0, // Оплата за товар при получении (за единицу товара)
'Weight' => 120, // Вес (за единицу товара, в граммах)
'Amount' => 2, // Количество единиц одноименного товара (в штуках)
'Comment' => 'Test item',
]));
$order->addPackage($package);
$request = new Requests\DeliveryRequest([
'Number' => 'TESTING123',
]);
$request->addOrder($order);
$response = $client->sendDeliveryRequest($request);
if ($response->hasErrors()) {
// обработка ошибок
}
foreach ($response->getOrders() as $order) {
// сверяем данные заказа, записываем номер
$order->getNumber();
$order->getDispatchNumber();
break;
}
Отличается необходимость указывать тип клиента, адрес забора груза. Без необходимости указывать состав посылок, но с указанием описания вложения.
use CdekSDK\Common;
use CdekSDK\Requests;
$order = new Common\Order([
'ClientSide' => Common\Order::CLIENT_SIDE_SENDER,
'Number' => 'TEST-123456',
'SendCity' => Common\City::create([
'Code' => 44, // Москва
]),
'RecCity' => Common\City::create([
'PostCode' => '630001', // Новосибирск
]),
'RecipientName' => 'Иван Петров',
'RecipientEmail' => 'petrov@test.ru',
'Phone' => '+7 (383) 202-22-50',
'TariffTypeCode' => 1,
'RecipientCompany' => 'Петров и партнёры, ООО',
'Comment' => 'Это тестовый заказ',
]);
$order->setSender(Common\Sender::create([
'Company' => 'ЗАО «Рога и Копыта»',
'Name' => 'Петр Иванов',
'Phone' => '+7 (283) 101-11-20',
])->setAddress(Common\Address::create([
'Street' => 'Морозильная улица',
'House' => '2',
'Flat' => '101',
])));
$order->setAddress(Common\Address::create([
'Street' => 'Холодильная улица',
'House' => '16',
'Flat' => '22',
]));
$package = Common\Package::create([
'Number' => 'TEST-123456',
'BarCode' => 'TEST-123456',
'Weight' => 500, // Общий вес (в граммах)
'SizeA' => 10, // Длина (в сантиметрах), в пределах от 1 до 1500
'SizeB' => 10,
'SizeC' => 10,
'Comment' => 'Обязательное описание вложения',
]);
$order->addPackage($package);
$order->addService(Common\AdditionalService::create(Common\AdditionalService::SERVICE_DELIVERY_TO_DOOR));
$request = new Requests\AddDeliveryRequest([
'Number' => 'TESTING123',
'ForeignDelivery' => false,
'Currency' => 'RUB',
]);
$request->addOrder($order);
$response = $client->sendAddDeliveryRequest($request);
if ($response->hasErrors()) {
// обработка ошибок
}
foreach ($response->getOrders() as $order) {
// сверяем данные заказа, записываем номер
$order->getNumber();
$order->getDispatchNumber();
}
Для подготовки документов необходимо указывать или номер заказа СДЭК, DispatchNumber, или номер заказа ИМ и дату через объёкт заказа.
use CdekSDK\Common;
use CdekSDK\Requests;
$request = new Requests\PrintReceiptsRequest([
'CopyCount' => 4,
]);
$request->addOrder(Common\Order::withDispatchNumber($dispatchNumber));
$response = $client->sendPrintReceiptsRequest($request);
if ($response->hasErrors()) {
// обработка ошибок
}
// Или возвращаем содержимое PDF файла...
return (string) $response->getBody();
Также можно указывать в запросе сами объекты заказов, полученные из других методов. Или же можно создать заказ прямо на месте, имея известные Number
и Date
:
$request = new Requests\PrintReceiptsRequest();
$request->addOrder($orderFromAnotherResponse);
$request->addOrder(Common\Order::withNumberAndDate($number, new \DateTime($dateString)));
Печать ШК-мест производится по такому же алгоритму что и печать квитанций.
use CdekSDK\Common;
use CdekSDK\Requests;
$request = new Requests\PrintLabelsRequest([
'PrintFormat' => Requests\PrintLabelsRequest::PRINT_FORMAT_A5,
]);
$request->addOrder(Common\Order::withDispatchNumber($dispatchNumber));
$response = $client->sendPrintLabelsRequest($request);
if ($response->hasErrors()) {
// обработка ошибок
}
// Или возвращаем содержимое PDF файла...
return (string) $response->getBody();
use CdekSDK\Common;
use CdekSDK\Requests;
$request = Requests\DeleteRequest::create([
'Number' => 'TESTING123',
])->addOrder(new Common\Order([
'Number' => 'TEST-123456',
]));
$response = $client->sendDeleteRequest($request);
if ($response->hasErrors()) {
// обработка ошибок
}
foreach ($response->getOrders() as $order) {
// проверяем номера удалённых заказов
$order->getNumber(); // должно быть 'TEST-123456'
}
use CdekSDK\Common;
use CdekSDK\Requests;
$request = Requests\UpdateRequest::create([
'Number' => 'TESTING123',
])->addOrder(new Common\Order([
'Number' => 'TEST-123456',
]));
$response = $client->sendUpdateRequest($request);
if ($response->hasErrors()) {
// обработка ошибок
}
foreach ($response->getOrders() as $order) {
// проверяем номера изменённых заказов
$order->getNumber(); // должно быть 'TEST-123456'
}
use CdekSDK\Common;
use CdekSDK\Requests;
$request = Requests\CallCourierRequest::create()->addCall(Common\CallCourier::create([
'Date' => new \DateTime('tomorrow'),
'DispatchNumber' => $dispatchNumber,
'TimeBeg' => new \DateTime('10:00'),
'TimeEnd' => new \DateTime('17:00'),
'SendCityCode' => 44,
'SenderName' => 'Проверка Тестович',
'SendPhone' => '+78001001010',
])->setAddress(Common\Address::create([
'Street' => 'Тестовая',
'House' => '8',
'Flat' => '32',
])));
$response = $client->sendCallCourierRequest($request);
if ($response->hasErrors()) {
// обработка ошибок
}
// Получаем номера заявок
foreach ($response->getNumbers() as $number) {
$number; // ...
}
use CdekSDK\Common;
use CdekSDK\Requests;
$request = new Requests\ScheduleRequest();
$request = $request->addOrder(Common\Order::create([
'DispatchNumber' => '123456',
])->addAttempt(Common\Attempt::create([
'ID' => 500,
'Date' => new \DateTime('next Monday'),
])->addPackage(Common\Package::create([
'Number' => 'TEST-123456',
'BarCode' => 'TEST-123456',
'Weight' => 500,
])->addItem(new Common\Item([
'WareKey' => 'NN0001',
'Cost' => 500,
'Payment' => 0,
'Weight' => 120,
'Amount' => 2,
'Comment' => 'Test item',
])))));
$response = $client->sendScheduleRequest($request);
if ($response->hasErrors()) {
// обработка ошибок
}
use CdekSDK\Common;
use CdekSDK\Requests;
$request = new Requests\PreAlertRequest([
'PvzCode' => 'NSK333',
'PlannedMeetingDate' => new \DateTime('2017-10-12'),
]);
$request->addOrder(Common\Order::withDispatchNumber('12345678'));
$response = $client->sendPreAlertRequest($request);
if ($response->hasErrors()) {
// обработка ошибок
}
Он же отчет "Статусы заказов", используется для получения отчета по статусам заказов, включая историю изменения статусов.
use CdekSDK\Common;
use CdekSDK\Requests;
$request = new Requests\StatusReportRequest();
// можно указывать или всё сразу, или только диапазоны дат, или только конкретные заказы
$request->setChangePeriod(new Common\ChangePeriod(new \DateTime('-1 day'), new \DateTime('+1 day')));
$request->addOrder(Common\Order::withDispatchNumber($dispatchNumber));
// попросим показать историю изменения статусов заказов
$request->setShowHistory();
$response = $client->sendStatusReportRequest($request);
if ($response->hasErrors()) {
// обработка ошибок
}
foreach ($response as $order) {
$order->getActNumber();
$order->getNumber();
$order->getDispatchNumber();
$order->getDeliveryDate();
$order->getRecipientName();
if ($status = $order->getStatus()) {
$status->getDescription();
$status->getDate();
$status->getCode();
$status->getCityCode();
$status->getCityName();
}
$order->getReason()->getCode();
$order->getReason()->getDescription();
$order->getReason()->getDate();
$order->getDelayReason()->getCode();
$order->getDelayReason()->getDescription();
$order->getDelayReason()->getDate();
}
Отчет используется для получения детальной информации по заказам.
use CdekSDK\Common;
use CdekSDK\Requests;
$request = new Requests\InfoReportRequest();
$request->setChangePeriod(new Common\ChangePeriod(new \DateTime('-1 day'), new \DateTime('+1 day')));
// можно искать только по номерам, без дат
$request->addOrder(Common\Order::withDispatchNumber($dispatchNumber));
$response = $client->sendInfoReportRequest($request);
if ($response->hasErrors()) {
// обработка ошибок
}
foreach ($response as $order) {
/** @var \CdekSDK\Common\Order $order */
$order->getNumber();
$order->getSenderCity()->getName();
$order->getRecipientCity()->getName();
foreach ($order->getPackages() as $package) {
$package->getBarCode();
$package->getVolumeWeight();
}
foreach ($order->getAdditionalServices() as $service) {
$service->getServiceCode();
$service->getSum();
}
}
Перечень возможных URL в документации.
$account = getenv('CDEK_ACCOUNT');
$password = getenv('CDEK_PASSWORD');
$baseUri = getenv('CDEK_BASE_URL');
// Например, это может быть https://integration.cdek-asia.cn
$client = new \CdekSDK\CdekClient($account, $password, new \GuzzleHttp\Client([
'base_uri' => $baseUri,
]));
// config/app.php
'providers' => [
// ...
\CdekSDK\LaravelCdekServiceProvider::class
// ...
]
// config/services.php
'cdek' => [
'account' => env('CDEK_ACCOUNT', ''),
'password' => env('CDEK_PASSWORD', ''),
'guzzle_options' => [ // необязательные параметры
'base_uri' => 'https://integration.cdek-asia.cn',
'timeout' => 5,
],
],
Посмотреть, что конкретно отвечает СДЭК на наши запросы и какие запросы мы посылаем сами можно используя стандартный PSR-3 логгер, так, как, например, Monolog.
$client->setLogger($monolog);
Текстовые запросы и ответы в исходном виде идут с уровнем DEBUG
.
-
Общие инструкции по работе с GitHub. Если это ваш первый PR, очень рекомендуем ознакомиться.
Для указания даты и времени в запросах везде можно использовать ровно как DateTime
, так и DateTimeImmutable
.
Если вы не используете AnnotationRegistry
где-то ещё, то никакой дополнительной настройки делать не требуется.
Если же вы используете AnnotationRegistry
и в ней не настроен обычный автозагрузчик классов, то его следует подключить где-то до создания CdekClient
следующим образом:
\Doctrine\Common\Annotations\AnnotationRegistry::registerLoader('class_exists');
Если же нежелательно использовать обычный загрузчик классов, то можно отключить его автоматическую настройку:
\CdekSDK\Serialization\Serializer::doNotConfigureAnnotationRegistry();
Обычно ничего этого делать не нужно, всё должно работать и так.
Эта библиотека - хард-форк библиотеки appwilio/cdek-sdk с поддержкой более старых версий PHP и расширенной поддержкой API. Обратная совместимость с исходной библиотекой не гарантируется, но фичи и исправления будут переноситься оттуда сюда по мере возможности. Если что-то пропустили, дайте знать.
Авторы-создатели исходной библиотеки: JhaoDa и greabock.
Данный SDK распространяется под лицензией MIT.
This project is licensed under the terms of the MIT license.