Как исправить ошибку декодирования JSON при интеграции 1С с калькулятором доставки СДЭК

Программист 1С v8.3 (Управляемые формы) Управленческий учет Торговля и дистрибуция
← На главную

Интеграция систем учета на базе платформы 1С с логистическими сервисами — одна из самых частых задач в сфере электронной коммерции. СДЭК предоставляет удобный API для расчета стоимости и сроков доставки, однако при реализации низкоуровневых HTTP-запросов разработчики часто сталкиваются с неочевидными техническими трудностями. Одной из самых распространенных ошибок является получение ответа от сервера с кодом 12: "Ошибка декодирования данных. Ожидается json".

В этой статье мы подробно разберем, почему возникает эта ошибка, проанализируем неверные подходы в коде и построим правильный, надежный механизм отправки запросов к калькулятору СДЭК с использованием встроенных механизмов платформы 1С 8.3. Кроме того, мы рассмотрим современный контекст работы с API СДЭК, включая переход на версию протокола 2.0.

Выясняем причины: почему СДЭК выдает ошибку декодирования JSON

Давайте проанализируем ситуацию, в которой оказался автор темы. При отправке запроса к скрипту калькулятора сервер возвращает следующую ошибку:

{"error":[{"code":12,"text":"Ошибка декодирования данных. Ожидается json"}]}

Причиной такого поведения сервера является несовместимость передаваемых заголовков HTTP-запроса и фактической структуры тела (payload), отправляемого из 1С. Разберем три основные технические ошибки, которые были допущены при разработке:

  1. Несоответствие заголовка Content-Type и формата тела. В исходном коде устанавливается заголовок Content-Type = "application/x-www-form-urlencoded", но в тело запроса через метод УстановитьТелоИзСтроки передается строка вида "xml_request=" + Текст (где переменная Текст содержит JSON). Скрипт СДЭК ожидает в таком режиме URL-кодированные данные, где в параметре json_request (а не xml_request, как написал автор!) должна находиться сериализованная строка. Из-за некорректного имени параметра и отсутствия URL-кодирования сервер СДЭК не может распарсить входящий поток.
  2. Ошибки сериализации JSON через файлы. Использование временных файлов для генерации JSON-строки часто приводит к путанице. Метод ЗаписьJSON.Закрыть() возвращает готовую строку JSON только тогда, когда запись изначально производилась в память с использованием метода УстановитьСтроку(). Если же объект ЗаписьJSON был инициализирован через ОткрытьФайл(), то метод Закрыть() запишет данные на диск и вернет пустую строку. Попытка передать результат закрытия такого объекта напрямую в тело запроса приведет к отправке пустого значения.
  3. Использование устаревших скриптов. Обращение к скриптам с суффиксом _request.php требует специфического форматирования тела запроса в формате ключ-значение. Гораздо проще и надежнее использовать прямую отправку чистого JSON-документа на обработчик, работающий с типом контента application/json.

Шаг 1. Правильное формирование JSON в 1С без временных файлов

Для начала избавимся от работы с временными файлами на диске. Это ускорит работу системы и исключит ошибки блокировки файлов операционной системой. Мы будем использовать сериализацию 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.Закрыть();
    
    Возврат СтрокаРезультат;
    
КонецФункции

Шаг 2. Настройка HTTP-соединения и отправка POST-запроса

Для успешной отправки запроса нам необходимо правильно инициализировать 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);
Исключение
    ВызватьИсключение "Ошибка сетевого обмена при обращении к калькулятору доставки: " + ОписаниеОшибки();
КонецПопытки;

Шаг 3. Обработка и парсинг ответа от СДЭК

После получения ответа от сервера необходимо проанализировать HTTP-код состояния. Успешный запрос должен вернуть код 200. Полученную строку ответа (которая также является документом JSON) мы должны преобразовать обратно в структуру 1С для дальнейшего использования при расчете заказа покупателя.

Проанализируем пример парсинга ответа:


Если КодСостояния = 200 Тогда
    
    // Читаем полученный JSON-ответ
    ЧтениеJSON = Новый ЧтениеJSON;
    ЧтениеJSON.УстановитьСтроку(СтрокаОтвета);
    
    РезультатРасчета = ПрочитатьJSON(ЧтениеJSON);
    ЧтениеJSON.Закрыть();
    
    // Проверяем наличие ошибок бизнес-логики в ответе СДЭК
    Если РезультатРасчета.Свойство("error") Тогда
        // СДЭК вернул массив ошибок расчета
        Для Каждого Ошибка Из РезультатРасчета.error Цикл
            Сообщить("Ошибка расчета СДЭК (" + Ошибка.code + "): " + Ошибка.text);
        КонецЦикла;
    Иначе
        // Успешный расчет. Получаем стоимость и сроки доставки
        Если РезультатРасчета.Свойство("result") Тогда
            ДанныеДоставки = РезультатРасчета.result;
            СтоимостьДоставки = ДанныеДоставки.price;
            МинимальныйСрок  = ДанныеДоставки.deliveryPeriodMin;
            МаксимальныйСрок  = ДанныеДоставки.deliveryPeriodMax;
            
            Сообщить("Стоимость доставки рассчитана успешно: " + СтоимостьДоставки + " руб.");
            Сообщить("Срок доставки: " + МинимальныйСрок + " - " + МаксимальныйСрок + " дней.");
        КонецЕсли;
    EndIf;
    
Иначе
    Сообщить("Сервер вернул ошибку HTTP с кодом: " + КодСостояния);
    Сообщить("Тело ответа: " + СтрокаОтвета);
КонецЕсли;

Современный подход: Переход на API СДЭК версии 2.0

Описанный выше протокол (версия 1.0, работающая со скриптами .php) на сегодняшний день признан устаревшим. Хотя СДЭК сохраняет его поддержку для старых интеграций, новые проекты настоятельно рекомендуется разрабатывать на базе современной архитектуры API v2.0. Разберем ключевые отличия и особенности новой версии, которые уберегут вас от ошибок при проектировании современной интеграции.

Основными изменениями в API v2.0 по сравнению с наследуемым протоколом являются:

Рассмотрим пример получения авторизационного 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С:Доставка" или официальных готовых расширений интеграции от СДЭК позволяет значительно сэкономить время на разработку и поддержку актуальности интеграционного решения при плановых изменениях на стороне сервера транспортной компании.

← На главную