Интеграция систем учета на базе платформы 1С с логистическими сервисами — одна из самых частых задач в сфере электронной коммерции. СДЭК предоставляет удобный API для расчета стоимости и сроков доставки, однако при реализации низкоуровневых HTTP-запросов разработчики часто сталкиваются с неочевидными техническими трудностями. Одной из самых распространенных ошибок является получение ответа от сервера с кодом 12: "Ошибка декодирования данных. Ожидается json".
В этой статье мы подробно разберем, почему возникает эта ошибка, проанализируем неверные подходы в коде и построим правильный, надежный механизм отправки запросов к калькулятору СДЭК с использованием встроенных механизмов платформы 1С 8.3. Кроме того, мы рассмотрим современный контекст работы с API СДЭК, включая переход на версию протокола 2.0.
Давайте проанализируем ситуацию, в которой оказался автор темы. При отправке запроса к скрипту калькулятора сервер возвращает следующую ошибку:
{"error":[{"code":12,"text":"Ошибка декодирования данных. Ожидается json"}]}
Причиной такого поведения сервера является несовместимость передаваемых заголовков HTTP-запроса и фактической структуры тела (payload), отправляемого из 1С. Разберем три основные технические ошибки, которые были допущены при разработке:
Content-Type = "application/x-www-form-urlencoded", но в тело запроса через метод УстановитьТелоИзСтроки передается строка вида "xml_request=" + Текст (где переменная Текст содержит JSON). Скрипт СДЭК ожидает в таком режиме URL-кодированные данные, где в параметре json_request (а не xml_request, как написал автор!) должна находиться сериализованная строка. Из-за некорректного имени параметра и отсутствия URL-кодирования сервер СДЭК не может распарсить входящий поток.ЗаписьJSON.Закрыть() возвращает готовую строку JSON только тогда, когда запись изначально производилась в память с использованием метода УстановитьСтроку(). Если же объект ЗаписьJSON был инициализирован через ОткрытьФайл(), то метод Закрыть() запишет данные на диск и вернет пустую строку. Попытка передать результат закрытия такого объекта напрямую в тело запроса приведет к отправке пустого значения._request.php требует специфического форматирования тела запроса в формате ключ-значение. Гораздо проще и надежнее использовать прямую отправку чистого JSON-документа на обработчик, работающий с типом контента application/json.Для начала избавимся от работы с временными файлами на диске. Это ускорит работу системы и исключит ошибки блокировки файлов операционной системой. Мы будем использовать сериализацию JSON напрямую в строку оперативной памяти. Для этого задействуем объекты платформы ЗаписьJSON и Вставить для заполнения структуры данных.
Рассмотрим пример формирования структуры параметров запроса для калькулятора СДЭК:
// Создаем структуру параметров запроса согласно спецификации СДЭК
ПараметрыЗапроса = Новый Структура;
ПараметрыЗапроса.Вставить("version", "1.0");
ПараметрыЗапроса.Вставить("authLogin", "Ваш_Логин");
ПараметрыЗапроса.Вставить("secure", "Ваш_Пароль_Или_Хэш");
ПараметрыЗапроса.Вставить("dateExecute", Формат(ТекущаяДата(), "ДФ=ЛЛЛЛ-ММ-ДД"));
ПараметрыЗапроса.Вставить("senderCityId", "47409"); // Код города отправителя (например, Новосибирск)
ПараметрыЗапроса.Вставить("receiverCityId", "270"); // Код города получателя (например, Москва)
ПараметрыЗапроса.Вставить("tariffId", 136); // ID тарифа доставки (например, Посылка склад-склад)
// Формируем список мест (товаров)
МассивМест = Новый Массив;
ГабаритыМеста = Новый Структура;
ГабаритыМеста.Вставить("weight", 0.5); // Вес в кг
ГабаритыМеста.Вставить("length", 10); // Длина в см
ГабаритыМеста.Вставить("width", 15); // Ширина в см
ГабаритыМеста.Вставить("height", 20); // Высота в см
МассивМест.Добавить(ГабаритыМеста);
ПараметрыЗапроса.Вставить("goods", МассивМест);
Теперь создадим вспомогательную функцию, которая преобразует нашу многоуровневую структуру 1С в чистую строку JSON:
Функция ПреобразоватьСтруктуруВJsonСтроку(ОбъектПараметров) Экспорт
НастройкиСериализации = Новый НастройкиСериализацииJSON();
НастройкиСериализации.ВариантЗаписиДаты = ВариантЗаписиДатыJSON.УниверсальнаяДата;
НастройкиСериализации.ФорматСериализацииДаты = ФорматДатыJSON.ISO;
ЗаписьJSON = Новый ЗаписьJSON;
// Указываем запись в память (в строку), а не в файл!
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ОбъектПараметров, НастройкиСериализации);
СтрокаРезультат = ЗаписьJSON.Закрыть();
Возврат СтрокаРезультат;
КонецФункции
Для успешной отправки запроса нам необходимо правильно инициализировать HTTPСоединение, сформировать HTTPЗапрос, установить корректные заголовки и передать тело в кодировке UTF-8. Разберем этот процесс по шагам.
Обратите внимание, что для работы по защищенному протоколу HTTPS нам потребуется объект ЗащищенноеСоединениеOpenSSL. Настоятельно рекомендуется выполнять все интеграционные запросы к СДЭК именно через HTTPS, так как в запросах передаются конфиденциальные данные авторизации вашего личного кабинета.
Посмотрим на пример кода отправки запроса:
// 1. Создаем защищенное SSL соединение
Попытка
ИспользоватьSSL = Новый ЗащищенноеСоединениеOpenSSL();
// Обращаемся к официальному хосту API СДЭК
Соединение = Новый HTTPСоединение("api.cdek.ru", , , , , , ИспользоватьSSL);
Исключение
ВызватьИсключение "Не удалось установить защищенное соединение с сервером СДЭК: " + ОписаниеОшибки();
КонецПопытки;
// 2. Определяем путь к скрипту калькулятора (используем прямой метод работы с JSON)
АдресСкрипта = "/calculator/calculate_price_by_json.php";
// 3. Формируем заголовки запроса
Заголовки = Новый Соответствие();
// Указываем, что передаем именно чистый JSON в кодировке UTF-8
Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
HTTPЗапрос = Новый HTTPЗапрос(АдресСкрипта, Заголовки);
// 4. Генерируем тело запроса из нашей структуры параметров
СтрокаТелаЗапроса = ПреобразоватьСтруктуруВJsonСтроку(ПараметрыЗапроса);
// Передаем строку в тело запроса с явным указанием кодировки и отключением BOM
HTTPЗапрос.УстановитьТелоИзСтроки(СтрокаТелаЗапроса, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
// 5. Отправляем запрос методом POST и получаем ответ сервера
Попытка
HTTPОтвет = Соединение.ВызватьHTTPМетод("POST", HTTPЗапрос);
КодСостояния = HTTPОтвет.КодСостояния;
СтрокаОтвета = HTTPОтвет.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
Исключение
ВызватьИсключение "Ошибка сетевого обмена при обращении к калькулятору доставки: " + ОписаниеОшибки();
КонецПопытки;
После получения ответа от сервера необходимо проанализировать HTTP-код состояния. Успешный запрос должен вернуть код 200. Полученную строку ответа (которая также является документом JSON) мы должны преобразовать обратно в структуру 1С для дальнейшего использования при расчете заказа покупателя.
Проанализируем пример парсинга ответа:
Если КодСостояния = 200 Тогда
// Читаем полученный JSON-ответ
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаОтвета);
РезультатРасчета = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
// Проверяем наличие ошибок бизнес-логики в ответе СДЭК
Если РезультатРасчета.Свойство("error") Тогда
// СДЭК вернул массив ошибок расчета
Для Каждого Ошибка Из РезультатРасчета.error Цикл
Сообщить("Ошибка расчета СДЭК (" + Ошибка.code + "): " + Ошибка.text);
КонецЦикла;
Иначе
// Успешный расчет. Получаем стоимость и сроки доставки
Если РезультатРасчета.Свойство("result") Тогда
ДанныеДоставки = РезультатРасчета.result;
СтоимостьДоставки = ДанныеДоставки.price;
МинимальныйСрок = ДанныеДоставки.deliveryPeriodMin;
МаксимальныйСрок = ДанныеДоставки.deliveryPeriodMax;
Сообщить("Стоимость доставки рассчитана успешно: " + СтоимостьДоставки + " руб.");
Сообщить("Срок доставки: " + МинимальныйСрок + " - " + МаксимальныйСрок + " дней.");
КонецЕсли;
EndIf;
Иначе
Сообщить("Сервер вернул ошибку HTTP с кодом: " + КодСостояния);
Сообщить("Тело ответа: " + СтрокаОтвета);
КонецЕсли;
Описанный выше протокол (версия 1.0, работающая со скриптами .php) на сегодняшний день признан устаревшим. Хотя СДЭК сохраняет его поддержку для старых интеграций, новые проекты настоятельно рекомендуется разрабатывать на базе современной архитектуры API v2.0. Разберем ключевые отличия и особенности новой версии, которые уберегут вас от ошибок при проектировании современной интеграции.
Основными изменениями в API v2.0 по сравнению с наследуемым протоколом являются:
https://api.cdek.ru/v2/oauth/token с передачей параметров авторизации. В ответ сервер возвращает временный токен доступа (access token), который необходимо подставлять в заголовок Authorization: Bearer [токен] для всех последующих запросов./v2/calculator/tariff — расчет стоимости по конкретному выбранному тарифу./v2/calculator/tarifflist — расчет стоимости по всем доступным тарифам одновременно (позволяет менеджеру в 1С интерактивно выбирать оптимальный вариант).goods теперь используется массив упаковок packages, где жестко регламентированы типы передаваемых данных (вес передается в граммах, а не в килограммах, габариты — строго целыми числами в сантиметрах).Рассмотрим пример получения авторизационного JWT-токена в 1С для API v2.0 по шагам:
Функция ПолучитьТокенАвторизацииСДЭК(ИдентификаторКлиента, СекретныйКлюч)
SSL = Новый ЗащищенноеСоединениеOpenSSL();
Соединение = Новый HTTPСоединение("api.cdek.ru", , , , , , SSL);
Заголовки = Новый Соответствие();
Заголовки.Вставить("Content-Type", "application/x-www-form-urlencoded");
Запрос = Новый HTTPЗапрос("/v2/oauth/token", Заголовки);
// Параметры авторизации передаются в формате urlencoded
СтрокаПараметров = "grant_type=client_credentials&client_id=" + ИдентификаторКлиента + "&client_secret=" + СекретныйКлюч;
Запрос.УстановитьТелоИзСтроки(СтрокаПараметров, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
Попытка
Ответ = Соединение.Post(Запрос);
Если Ответ.КодСостояния = 200 Тогда
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(Ответ.ПолучитьТелоКакСтроку());
ДанныеТокена = ПрочитатьJSON(Чтение);
Чтение.Закрыть();
Возврат ДанныеТокена.access_token; // Возвращаем JWT-токен
Иначе
ЗаписатьЛогОшибки("Ошибка авторизации СДЭК v2: " + Ответ.ПолучитьТелоКакСтроку());
Возврат "";
КонецЕсли;
Исключение
ЗаписатьЛогОшибки("Ошибка сети при авторизации СДЭК v2: " + ОписаниеОшибки());
Возврат "";
КонецПопытки;
КонецФункции
Полученный JWT-токен сохраняется в сессии (или константе информационной базы с проверкой времени его жизни) и передается при расчете стоимости следующим образом:
// ... код подготовки структуры параметров v2 ...
СтрокаJSON_v2 = ПреобразоватьСтруктуруВJsonСтроку(ПараметрыЗапроса_v2);
Заголовки = Новый Соответствие();
Заголовки.Вставить("Content-Type", "application/json");
// Добавляем токен авторизации Bearer
Заголовки.Вставить("Authorization", "Bearer " + ПолученныйТокенДоступа);
Запрос = Новый HTTPЗапрос("/v2/calculator/tariff", Заголовки);
Запрос.УстановитьТелоИзСтроки(СтрокаJSON_v2, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
// Отправляем запрос на сервер СДЭК v2
ОтветКалькулятора = Соединение.Post(Запрос);
Если перед вами стоит задача масштабной интеграции СДЭК с типовыми конфигурациями 1С (такими как 1С:Управление торговлей, 1С:ERP или 1С:УНФ), проанализируйте целесообразность написания кода с нуля. В составе современной БСП (Библиотеки стандартных подсистем) поставляется готовый функционал "1С:Доставка". Этот встроенный сервис уже содержит полностью отлаженный, регулярно обновляемый разработчиками фирмы "1С" программный код взаимодействия со СДЭК, включая подсистемы авторизации, сопоставления адресных классификаторов (ФИАС/КЛАДР) и автоматического создания заказов на отправку.
Использование встроенного сервиса "1С:Доставка" или официальных готовых расширений интеграции от СДЭК позволяет значительно сэкономить время на разработку и поддержку актуальности интеграционного решения при плановых изменениях на стороне сервера транспортной компании.