1С и Яндекс Маркет связывают через API Маркета для продавцов (Partner API): 1С отправляет остатки и цены, забирает заказы FBS и DBS, а менеджер работает с обычным заказом клиента и не переносит данные из кабинета вручную. Ниже — взгляд программиста, который дорабатывает 1С 8.3: варианты подключения, что синхронизировать, своя интеграция на HTTP-запросах и регламентном задании, разбор ошибок API. Отражение продаж в учёте и отчёт комиссионера — отдельная бухгалтерская задача, здесь её не касаемся.
Дальше разбираем третий вариант: на нём видно, как интеграция устроена внутри, и проще оценивать первые два.
| Данные | Направление | Как |
|---|---|---|
| Товары | Маркет ↔ 1С | ваш SKU хранится в 1С рядом с номенклатурой и характеристикой |
| Остатки | 1С → Маркет | PUT /v2/campaigns/{campaignId}/offers/stocks |
| Цены | 1С → Маркет | методы обновления цен для кабинета или отдельного магазина |
| Заказы FBS/DBS | Маркет → 1С | POST /v1/businesses/{businessId}/orders |
| Статусы | в обе стороны | 1С сообщает о сборке, по DBS — об этапах доставки; от Маркета приходят отмены |
| Отчёты | Маркет → 1С | асинхронно: запрос формирования, ожидание, скачивание файла |
Ключ схемы — ваш SKU (offerId): артикул продавца, по которому Маркет принимает остатки и присылает позиции заказа. Если у товара есть характеристики, SKU получает каждая, поэтому одного реквизита «Артикул» мало — нужен регистр сопоставления. Первично его заполняют по списку товаров кабинета, сверяя артикулы или штрихкоды. При резком изменении цены Маркет может отправить товар в карантин до подтверждения — учтите это в выгрузке цен. Для FBY остатки ведёт склад Маркета: метод остатков доступен только для FBS, DBS и Экспресс.
Объекты расширения: справочник МагазиныЯндексМаркета (идентификатор кабинета — businessId, идентификатор кампании — campaignId, склад, организация, контрагент), регистр сведений ТоварыЯндексМаркета (магазин и SKU → номенклатура и характеристика) и регистр ЗаказыЯндексМаркета (магазин и номер заказа Маркета → заказ клиента). Все имена объектов конфигурации в листингах условные. Расширение подключайте без безопасного режима или с профилем безопасности: в безопасном режиме обращение к интернету и привилегированный режим недоступны.
Авторизация — токен Api-Key, созданный в кабинете продавца, в заголовке Api-Key. OAuth документация Маркета называет устаревшим способом. Токену можно открыть только нужные группы методов, а хранить его стоит в безопасном хранилище БСП (ОбщегоНазначения.ЗаписатьДанныеВБезопасноеХранилище из формы магазина), а не в реквизите справочника.
Все вызовы идут через одну обёртку. Общие принципы HTTPСоединение и защищённого соединения — в статье HTTP-запрос в 1С; здесь только специфика API Яндекс Маркета: заголовок авторизации, JSON без BOM, разбор массива errors и решение, продолжать ли обмен.
// Общий модуль ЯндексМаркетОбмен: флажок «Сервер», остальные сняты.
// Справочник МагазиныЯндексМаркета и регистры сведений — условные объекты расширения.
Функция ВызватьAPI(Магазин, HTTPМетод, АдресРесурса, Данные = Неопределено) Экспорт
Результат = Новый Структура("Успех, КодСостояния, Ответ, ТекстОшибки, ПрерватьОбмен",
Ложь, 0, Неопределено, "", Истина);
Операция = HTTPМетод + " " + АдресРесурса;
УстановитьПривилегированныйРежим(Истина);
КлючAPI = ОбщегоНазначения.ПрочитатьДанныеИзБезопасногоХранилища(Магазин, "ApiKey");
УстановитьПривилегированныйРежим(Ложь);
Если Не ЗначениеЗаполнено(КлючAPI) Тогда
Результат.ТекстОшибки = "Не задан Api-Key магазина";
ЗаписатьРезультатВызова(Магазин, Операция, Результат);
Возврат Результат;
КонецЕсли;
Заголовки = Новый Соответствие;
Заголовки.Вставить("Api-Key", КлючAPI);
Заголовки.Вставить("Accept", "application/json");
Запрос = Новый HTTPЗапрос(АдресРесурса, Заголовки);
Если Данные <> Неопределено Тогда
Запрос.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Запрос.УстановитьТелоИзСтроки(ВJSON(Данные), КодировкаТекста.UTF8,
ИспользованиеByteOrderMark.НеИспользовать);
КонецЕсли;
Попытка
Соединение = Новый HTTPСоединение("api.partner.market.yandex.ru", 443, , , , 60,
Новый ЗащищенноеСоединениеOpenSSL());
Ответ = Соединение.ВызватьHTTPМетод(HTTPМетод, Запрос);
Исключение
Результат.ТекстОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
ЗаписатьРезультатВызова(Магазин, Операция, Результат);
Возврат Результат;
КонецПопытки;
Результат.КодСостояния = Ответ.КодСостояния;
ТелоОтвета = Строка(Ответ.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8));
Если ЗначениеЗаполнено(ТелоОтвета) Тогда
Попытка
Результат.Ответ = ИзJSON(ТелоОтвета);
Исключение
Результат.Ответ = Неопределено; // не JSON: начало тела попадет в ТекстОшибки
КонецПопытки;
КонецЕсли;
Результат.Успех = (Ответ.КодСостояния >= 200 И Ответ.КодСостояния < 300);
// 400 — ошибка в данных одной пачки, следующие пачки отправлять можно.
// 401, 403, 404, 420 и 5xx — до следующего запуска продолжать бессмысленно.
Результат.ПрерватьОбмен = Не Результат.Успех И Ответ.КодСостояния <> 400;
Если Не Результат.Успех Тогда
Результат.ТекстОшибки = ТекстОшибокAPI(Результат.Ответ, ТелоОтвета);
КонецЕсли;
ЗаписатьРезультатВызова(Магазин, Операция, Результат);
Возврат Результат;
КонецФункции
Функция ТекстОшибокAPI(Ответ, ТелоОтвета)
Ошибки = ?(ТипЗнч(Ответ) = Тип("Соответствие"), Ответ.Получить("errors"), Неопределено);
Если ТипЗнч(Ошибки) <> Тип("Массив") Тогда
Возврат Лев(ТелоОтвета, 300);
КонецЕсли;
Части = Новый Массив;
Для Каждого Ошибка Из Ошибки Цикл
Если ТипЗнч(Ошибка) = Тип("Соответствие") Тогда
Части.Добавить(СтрШаблон("%1: %2", Ошибка.Получить("code"), Ошибка.Получить("message")));
КонецЕсли;
КонецЦикла;
Возврат СтрСоединить(Части, "; ");
КонецФункцииЖурнал — журнал регистрации с отдельным событием и ссылкой на магазин: ошибки одного магазина отбираются в два клика. Почему JSON читается в соответствие, а не в структуру, — в статье JSON в 1С.
Процедура ЗаписатьВЖурнал(Магазин, Текст, Уровень) Экспорт
ЗаписьЖурналаРегистрации("ЯндексМаркет.Обмен", Уровень,
Метаданные.Справочники.МагазиныЯндексМаркета, Магазин, Текст);
КонецПроцедуры
Процедура ЗаписатьРезультатВызова(Магазин, Операция, Результат)
Если Результат.Успех Тогда
Уровень = УровеньЖурналаРегистрации.Информация;
ИначеЕсли Результат.КодСостояния = 420 Тогда
Уровень = УровеньЖурналаРегистрации.Предупреждение; // лимит: штатная ситуация
Иначе
Уровень = УровеньЖурналаРегистрации.Ошибка;
КонецЕсли;
ЗаписатьВЖурнал(Магазин, СтрШаблон("%1 | HTTP %2 | %3", Операция,
Формат(Результат.КодСостояния, "ЧН=0; ЧГ=0"), Результат.ТекстОшибки), Уровень);
КонецПроцедуры
Функция ВJSON(Данные)
Запись = Новый ЗаписьJSON;
Запись.УстановитьСтроку(Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет));
ЗаписатьJSON(Запись, Данные);
Возврат Запись.Закрыть();
КонецФункции
Функция ИзJSON(Текст)
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(Текст);
Значение = ПрочитатьJSON(Чтение, Истина); // Соответствие: доступ через Получить()
Чтение.Закрыть();
Возврат Значение;
КонецФункцииМетод остатков принимает до 2000 SKU за запрос, лимит — 100 000 товаров в минуту. Отправляйте все сопоставленные SKU, включая нулевые: если нуль не ушёл, Маркет продолжит продавать старый остаток. Поэтому запрос строится от регистра сопоставления с левым соединением к остаткам. Поле updatedAt необязательное — без него берётся текущее время.
Процедура ВыгрузитьОстатки(Магазин) Экспорт
РазмерПачки = 2000; // максимум SKU в одном запросе по документации метода
АдресРесурса = СтрШаблон("/v2/campaigns/%1/offers/stocks",
Формат(Магазин.ИдентификаторКампании, "ЧГ=0"));
// Регистр остатков условный: в УТ 11 и КА 2 возьмите свободный остаток по своей схеме резервов.
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Товары.SKU КАК SKU,
| СУММА(ЕСТЬNULL(Остатки.КоличествоОстаток, 0)) КАК Количество
|ИЗ
| РегистрСведений.ТоварыЯндексМаркета КАК Товары
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СвободныеОстаткиТоваров.Остатки(, Склад = &Склад) КАК Остатки
| ПО Товары.Номенклатура = Остатки.Номенклатура
| И Товары.Характеристика = Остатки.Характеристика
|ГДЕ
| Товары.Магазин = &Магазин
|
|СГРУППИРОВАТЬ ПО
| Товары.SKU");
Запрос.УстановитьПараметр("Магазин", Магазин);
Запрос.УстановитьПараметр("Склад", Магазин.Склад);
Выборка = Запрос.Выполнить().Выбрать();
Пачка = Новый Массив;
Пока Выборка.Следующий() Цикл
Элементы = Новый Массив; // в items ровно один элемент
Элементы.Добавить(Новый Структура("count", Макс(0, Цел(Выборка.Количество))));
Пачка.Добавить(Новый Структура("sku, items", Выборка.SKU, Элементы));
Если Пачка.Количество() = РазмерПачки Тогда
Результат = ВызватьAPI(Магазин, "PUT", АдресРесурса, Новый Структура("skus", Пачка));
Если Результат.ПрерватьОбмен Тогда
Возврат; // оставшиеся SKU уйдут при следующем запуске задания
КонецЕсли;
Пачка = Новый Массив;
КонецЕсли;
КонецЦикла;
Если Пачка.Количество() > 0 Тогда
ВызватьAPI(Магазин, "PUT", АдресРесурса, Новый Структура("skus", Пачка));
КонецЕсли;
КонецПроцедурыЗаранее решите по документации, какой остаток отправлять для FBS: учитывает ли Маркет товары в принятых, но ещё не отгруженных заказах. Ошибка здесь даёт либо перепродажу, либо заниженный остаток.
Метод GET /v2/campaigns/{campaignId}/orders помечен устаревшим: по документации его отключат 12 апреля 2027 года, а параметры page и pageSize перестают работать раньше. Новую разработку ведите на POST /v1/businesses/{businessId}/orders: фильтры в теле (статусы, кампании, модели, даты в пределах 30 дней), до 50 заказов на страницу, следующая страница — по nextPageToken, лимит — 10 000 запросов в час. Номер заказа здесь в поле orderId, а не id.
Заказы, готовые к сборке, — статус PROCESSING с подстатусом STARTED. Тестовые приходят с признаком fake: в рабочей базе их отфильтровывают.
Функция НовыеЗаказы(Магазин) Экспорт
Заказы = Новый Массив;
Фильтр = Новый Структура;
Фильтр.Вставить("campaignIds", МассивИз(Магазин.ИдентификаторКампании));
Фильтр.Вставить("statuses", МассивИз("PROCESSING"));
Фильтр.Вставить("substatuses", МассивИз("STARTED"));
АдресПервойСтраницы = СтрШаблон("/v1/businesses/%1/orders?limit=50",
Формат(Магазин.ИдентификаторКабинета, "ЧГ=0"));
ТокенСтраницы = "";
Для НомерСтраницы = 1 По 100 Цикл // предохранитель от бесконечного цикла
АдресРесурса = АдресПервойСтраницы;
Если ЗначениеЗаполнено(ТокенСтраницы) Тогда
АдресРесурса = АдресРесурса + "&page_token="
+ КодироватьСтроку(ТокенСтраницы, СпособКодированияСтроки.КодировкаURL);
КонецЕсли;
Результат = ВызватьAPI(Магазин, "POST", АдресРесурса, Фильтр);
Если Не Результат.Успех Или ТипЗнч(Результат.Ответ) <> Тип("Соответствие") Тогда
Прервать; // недополученные заказы заберем при следующем запуске
КонецЕсли;
Страница = Результат.Ответ.Получить("orders");
Если ТипЗнч(Страница) = Тип("Массив") Тогда
Для Каждого ЗаказМаркета Из Страница Цикл
Заказы.Добавить(ЗаказМаркета);
КонецЦикла;
КонецЕсли;
Пагинация = Результат.Ответ.Получить("paging");
СледующийТокен = ?(ТипЗнч(Пагинация) = Тип("Соответствие"),
Пагинация.Получить("nextPageToken"), Неопределено);
Если Не ЗначениеЗаполнено(СледующийТокен) Или СледующийТокен = ТокенСтраницы Тогда
Прервать;
КонецЕсли;
ТокенСтраницы = СледующийТокен;
КонецЦикла;
Возврат Заказы;
КонецФункции
Функция МассивИз(Значение)
Массив = Новый Массив;
Массив.Добавить(Значение);
Возврат Массив;
КонецФункцииЦикл ограничен сотней страниц и прерывается, если токен не изменился, — сбой пагинации не превратит задание в бесконечный цикл.
Порядок: проверить, не загружен ли заказ, сопоставить все позиции по offerId и только потом записать документ и запись регистра в одной транзакции. Частично созданный заказ хуже отсутствующего: склад соберёт не всё.
Функция СоздатьЗаказКлиента(Магазин, ЗаказМаркета) Экспорт
НомерМаркета = Формат(ЗаказМаркета.Получить("orderId"), "ЧГ=0");
Позиции = ЗаказМаркета.Получить("items");
Если Не ЗначениеЗаполнено(НомерМаркета) Или ТипЗнч(Позиции) <> Тип("Массив") Тогда
ЗаписатьВЖурнал(Магазин, "Заказ без номера или позиций пропущен", УровеньЖурналаРегистрации.Ошибка);
Возврат Неопределено;
КонецЕсли;
Отбор = Новый Структура("Магазин, НомерЗаказаМаркета", Магазин, НомерМаркета);
Если ЗначениеЗаполнено(РегистрыСведений.ЗаказыЯндексМаркета.Получить(Отбор).ЗаказКлиента) Тогда
Возврат Неопределено; // уже загружен одним из прошлых опросов
КонецЕсли;
// Документ и реквизиты условные: в УТ 11, КА 2 и ERP нужны еще партнер, соглашение, НДС.
Заказ1С = Документы.ЗаказКлиента.СоздатьДокумент();
Заказ1С.Дата = ТекущаяДатаСеанса();
Заказ1С.Организация = Магазин.Организация;
Заказ1С.Контрагент = Магазин.Контрагент;
Заказ1С.Склад = Магазин.Склад;
Заказ1С.Комментарий = "Яндекс Маркет, заказ " + НомерМаркета;
Несопоставленные = Новый Массив;
Для Каждого Позиция Из Позиции Цикл
SKU = Позиция.Получить("offerId");
Товар = РегистрыСведений.ТоварыЯндексМаркета.Получить(
Новый Структура("Магазин, SKU", Магазин, SKU));
Если Не ЗначениеЗаполнено(Товар.Номенклатура) Тогда
Несопоставленные.Добавить(SKU);
Продолжить;
КонецЕсли;
НоваяСтрока = Заказ1С.Товары.Добавить();
НоваяСтрока.Номенклатура = Товар.Номенклатура;
НоваяСтрока.Характеристика = Товар.Характеристика;
НоваяСтрока.Количество = Позиция.Получить("count");
НоваяСтрока.Цена = ЦенаЗаЕдиницу(Позиция);
НоваяСтрока.Сумма = НоваяСтрока.Цена * НоваяСтрока.Количество;
КонецЦикла;
Если Несопоставленные.Количество() > 0 Тогда
ЗаписатьВЖурнал(Магазин, СтрШаблон("Заказ %1 не создан, не сопоставлены SKU: %2",
НомерМаркета, СтрСоединить(Несопоставленные, ", ")), УровеньЖурналаРегистрации.Ошибка);
Возврат Неопределено;
КонецЕсли;
НачатьТранзакцию();
Попытка
Заказ1С.Записать(РежимЗаписиДокумента.Проведение);
Запись = РегистрыСведений.ЗаказыЯндексМаркета.СоздатьМенеджерЗаписи();
Запись.Магазин = Магазин;
Запись.НомерЗаказаМаркета = НомерМаркета;
Запись.ЗаказКлиента = Заказ1С.Ссылка;
Запись.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписатьВЖурнал(Магазин, "Заказ " + НомерМаркета + ": "
+ ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()), УровеньЖурналаРегистрации.Ошибка);
Возврат Неопределено;
КонецПопытки;
ЗаписатьВЖурнал(Магазин, "Загружен заказ " + НомерМаркета, УровеньЖурналаРегистрации.Информация);
Возврат Заказ1С.Ссылка;
КонецФункции
// Цена продавца за единицу: оплата покупателя плюс субсидия Маркета.
// Сверьте с документацией, за единицу ли передаются суммы в prices.
Функция ЦенаЗаЕдиницу(Позиция)
Цена = 0;
Цены = Позиция.Получить("prices");
Если ТипЗнч(Цены) <> Тип("Соответствие") Тогда
Возврат Цена;
КонецЕсли;
Для Каждого Вид Из СтрРазделить("payment,subsidy", ",") Цикл
Часть = Цены.Получить(Вид);
Если ТипЗнч(Часть) = Тип("Соответствие") И ТипЗнч(Часть.Получить("value")) = Тип("Число") Тогда
Цена = Цена + Часть.Получить("value");
КонецЕсли;
КонецЦикла;
Возврат Цена;
КонецФункцииВ типовых УТ 11, КА 2 и ERP у заказа клиента больше обязательных полей: соглашение, ставки НДС, вариант обеспечения. Заполнение по умолчанию вызывайте теми же процедурами, что и форма документа, и проверьте проведение на копии базы. Если загрузку можно запустить и вручную, добавьте управляемую блокировку по номеру заказа Маркета. Собранный FBS-заказ переводят в подстатус READY_TO_SHIP методом смены статуса, по DBS продавец ведёт заказ до доставки сам — эти вызовы идут через ту же обёртку.
Одно задание на все магазины: сначала заказы, потом остатки, — резервы по свежим заказам уже уменьшат отправляемый остаток. Как создать задание и расписание, описано в статье Регламентное задание 1С программно; разумный интервал — 5 минут.
// Метод регламентного задания ЯндексМаркетОбмен (условное имя), общий модуль ЯндексМаркетОбмен.
Процедура ОбменСЯндексМаркетом() Экспорт
ОбщегоНазначения.ПриНачалеВыполненияРегламентногоЗадания(
Метаданные.РегламентныеЗадания.ЯндексМаркетОбмен);
Запрос = Новый Запрос(
"ВЫБРАТЬ
| Магазины.Ссылка КАК Ссылка
|ИЗ
| Справочник.МагазиныЯндексМаркета КАК Магазины
|ГДЕ
| Магазины.ОбменВключен
| И НЕ Магазины.ПометкаУдаления");
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Магазин = Выборка.Ссылка;
Попытка
// Сначала заказы: их резервы уменьшат свободный остаток до выгрузки.
Для Каждого ЗаказМаркета Из НовыеЗаказы(Магазин) Цикл
СоздатьЗаказКлиента(Магазин, ЗаказМаркета);
КонецЦикла;
ВыгрузитьОстатки(Магазин);
Исключение
ЗаписатьВЖурнал(Магазин, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()),
УровеньЖурналаРегистрации.Ошибка);
КонецПопытки;
КонецЦикла;
КонецПроцедурыПауз в коде нет: при превышении лимита обёртка возвращает ПрерватьОбмен, и остаток работы уходит при следующем запуске. Это проще и надёжнее ожидания внутри серверного вызова.
| Код | Значение | Действие |
|---|---|---|
| 400 | неправильные данные в запросе | записать errors, пропустить пачку, продолжить |
| 401 | нет данных авторизации | проверить, что заголовок Api-Key передаётся |
| 403 | токен неверен или доступ запрещён | проверить права токена и принадлежность кампании кабинету |
| 404 | ресурс не найден | обычно неверный campaignId или businessId в адресе |
| 420 | превышено ограничение на доступ | остановить обмен до следующего запуска |
| 5xx | внутренняя ошибка Маркета | повторить при следующем запуске |
Превышение лимита Маркет возвращает кодом 420, а не привычным 429 — обработчик, написанный «по стандарту», его пропустит. Лимиты указаны в описании каждого метода; если к кабинету подключено несколько программ, закладывайте запас.
Одна модель, сотни товаров, стандартные процессы — начните с типового механизма или коннектора. Своя доработка оправдана при нескольких магазинах и складах, своей схеме резервов, комплектах или связке со складской системой.
Что спросить у исполнителя:
GET /orders?Строка(). Число превращается в «21 234 567» с неразрывными пробелами, адрес ломается, API отвечает 404. Нужно Формат(Число, "ЧГ=0").ИспользованиеByteOrderMark.НеИспользовать тело в UTF-8 может уйти с меткой порядка байтов, и сервер его не разберёт.STARTED приходит в нескольких опросах подряд; без регистра соответствия создаются копии.Для новой интеграции — нет. Документация рекомендует Api-Key из кабинета продавца, OAuth назван устаревшим способом.
В API Маркета есть уведомления о событиях; для 1С это опубликованный наружу HTTP-сервис, а какие события поддержаны, сверьте с документацией. Для старта проще опрос раз в 2–5 минут: он не требует публикации базы в интернет.
Каждые 5–15 минут и дополнительно после крупных поступлений. При 2000 SKU в пачке даже большой каталог укладывается в несколько запросов.
Схема одинакова для любой конфигурации на 8.3, отличаются регистр остатков и документ заказа. В решениях на базе УТ 11 сначала проверьте типовой механизм — возможно, дорабатывать нужно только недостающее.
Если нужно подключить несколько магазинов, учесть свою схему резервов или доработать типовой обмен, закажите интеграцию 1С с маркетплейсами. Опишите модели работы, конфигурацию и объём каталога — разместите задание, исполнители оценят сроки и стоимость.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.