Перейти к основному содержимому

Интеграция с интернет-магазином

ShopStory связывает эфиры и записи с товарами вашего магазина. Покупатель открывает карточку в плеере, выбирает вариант товара и выполняет действие покупки. Ваш сайт или приложение использует свои API и сессию, а плеер отображает данные и результат.

Что можно подключить​

ВозможностьКак она работает
Товары через фидКаталог синхронизируется из согласованной выгрузки
Товарные данные через APIОбработчик магазина передаёт данные для карточки и условия покупки
Фид вместе с APIФид описывает товары и варианты, API обновляет условия для покупателя
Цвет, размер и другие опцииКарточка объединяет реальные SKU; покупатель выбирает конкретный вариант
Актуальная цена и наличиеМагазин возвращает условия выбранного SKU в текущей сессии
Покупка внутри плеераМагазин добавляет выбранный SKU в свою корзину и подтверждает результат
Полная карточка, корзина и выбор полученияОткрываются интерфейсы магазина с возвратом к видео
Эфиры и записиИспользуют один товарный контракт; условия покупки проверяются при просмотре
Подключение обработчиков

Код магазина передаёт плееру товарные данные, условия покупки и результат действия. При подключении фиксируются версия SDK, способ регистрации обработчиков и целевые платформы. На этой странице описан контракт данных; примеры показывают код адаптера магазина.

1. Выберите источник товарных данных​

Товарный фид​

Передайте выгрузку каталога по HTTPS. ShopStory связывает стабильные ID товаров с эфирами и записями. Названия, изображения, ссылки и переданные цены обновляются при синхронизации. Для простого каталога с едиными ценами и наличием этого достаточно: Требования к фиду.

Цена и наличие в выгрузке — снимок на момент её обновления. Окончательную проверку при покупке выполняет магазин.

Товарный API​

В расширенном контракте магазин может предоставлять данные карточки через свой обработчик: описание, изображения, URL, структуру вариантов и актуальные условия. Обработчик сопоставляет товарные ID ShopStory с каталогом магазина и возвращает нормализованные данные.

Такой способ подходит для персональных цен, нескольких рынков или каталога, который уже доступен через API. Источником структуры вариантов может быть API: передача мастер-фида для этого режима не обязательна. API должен возвращать полный список реальных SKU и опций для запрошенного товара, включая временно отсутствующие варианты.

Здесь товарный API — API вашего магазина. Это не новый endpoint ShopStory Public API и не публичный доступ к внутреннему каталогу магазина.

Фид и API вместе​

Фид передаёт стабильное описание и структуру вариантов. API возвращает цену, наличие и доступное действие для конкретного покупателя. Это позволяет обновлять условия без новой выгрузки всего каталога.

Выберите один источник структуры товара: фид или API. Ответы с условиями покупателя обновляют его карточку и не меняют общий каталог. Правила отсутствующих товаров и неполного обновления фиксируются до подключения; ошибка источника не должна незаметно удалять существующие варианты.

Источники данных карточки · расширенный контрактПрокрутите схему по горизонтали
Описание и условия сопоставляются по ID товара и SKU. В режиме фида с едиными условиями отдельный API для карточки не нужен. Персональные ответы обновляют карточку текущего покупателя.

2. Опишите товары и варианты​

ДанныеНазначение
ID товараСвязывает карточку с товаром магазина
Название, изображения и URLСодержимое карточки и переход на полную страницу
ID каждого SKUОднозначно определяет продаваемый вариант
Связь SKU с товаромОбъединяет варианты в одной карточке
Опции SKUЦвет, размер и другие значения для выбора

Передавайте только существующие сочетания опций. Например, «синий, размер M» и «синий, размер L» — два отдельных SKU со своими ценой и наличием. Недоступность одного размера не означает недоступность всех синих вариантов.

Все ID передаются строками с сохранением ведущих нулей и регистра. В расширенном контракте productId соответствует согласованному feedProductId, а variantId — идентификатору SKU; для товара без вариантов variantId=null. Выбранные опции должны однозначно указывать на один SKU. Магазин проверяет именно этот SKU при добавлении.

Список вариантов поступает из выбранного товарного источника. Плеер не создаёт сочетания из отдельных списков цветов и размеров. Временно отсутствующий SKU остаётся в структуре; его состояние определяется актуальными условиями.

Пример нормализованных товарных данных для API-режима:

product-data.json
{
"productId": "0001001",
"name": "Футболка",
"images": ["https://example.ru/images/0001001.webp"],
"url": "https://example.ru/products/0001001",
"variants": [
{
"variantId": "0001001-blue-M",
"options": { "Цвет": "Синий", "Размер": "M" }
},
{
"variantId": "0001001-blue-L",
"options": { "Цвет": "Синий", "Размер": "L" }
}
]
}

Это данные обработчика магазина, а не запрос к ShopStory Public API. Для товара без вариантов variants — пустой массив. В режиме фида эти же связи извлекаются из согласованного маппинга выгрузки.

3. Передайте актуальные условия покупки​

Обработчик getProductConditions(items) получает пакет пар productId + variantId. Магазин запрашивает свои API в сессии покупателя и возвращает условия по каждой паре. Порядок ответа может отличаться от запроса: сопоставление выполняется по обоим ID.

ПолеЗначение для карточки
productId, variantIdТовар и SKU из запроса
priceСумма и валюта; null, если цена ещё не определена
priceStatusprovisional — предварительная, confirmed — подтверждённая, unknown — не определена
availableНаличие конкретного SKU для текущего покупателя
showProductПоказывать ли общую карточку; одинаково для её SKU
fulfilmentLabelПодпись выбранного способа получения
inCartЕсть ли выбранный SKU в текущей корзине
actionРазрешённое следующее действие
conditionsRefНепрозрачная ссылка на условия, проверяемая магазином при добавлении

Магазин определяет действие: addDirectly — добавить SKU, chooseFulfilment — выбрать получение, openMerchantFlow — открыть интерфейс магазина, disabled — покупка недоступна. Цена, наличие и действие передаются отдельно: наличие само по себе не разрешает покупку.

Предварительная цена получает явную подпись. При price=null карточка объясняет, когда цена станет известна. Цена одного SKU не подменяет цену другого.

Все перечисленные поля обязательны в ответе; для допускающих отсутствие значения полей передавайте null. Поле price содержит amount — десятичную сумму строкой, и currency — код валюты, например {"amount":"1299.00","currency":"RUB"}. При price=null статус равен unknown. available, showProduct и inCart — boolean-значения подтверждённого ответа; неизвестный результат передаётся пропуском пары ID. fulfilmentLabel — строка или null, если способ получения ещё не выбран. conditionsRef — строка для разрешённого прямого добавления; когда добавление ещё недоступно, значение может быть null.

showProduct должен совпадать для всех SKU одной карточки. При противоречивом ответе магазин повторно проверяет условия этого товара; покупка недоступна до согласованного ответа. Для товара без вариантов в запрос и ответ передаётся variantId=null.

Пропущенный ответ означает неизвестное состояние SKU. Описание сохраняется, покупка этого варианта недоступна до успешной проверки. Ошибка API не превращается в «Нет в наличии». Код магазина привязывает чтение к текущей ревизии сессии, магазина, получения и выбранного SKU. При изменении этого контекста ревизия меняется, условия обновляются, а запоздавший ответ прежнего чтения отбрасывается. Сессионные данные для такого сравнения остаются в коде магазина.

Обработчик наличия возвращает только boolean-карту и не меняет цену или варианты. Для актуальной цены, опций и разрешённого действия используйте getProductConditions; этот контракт подключается отдельно от boolean-проверки наличия.

4. Подключите корзину​

Обработчик startCommerceAction(request, events) получает productId, variantId, action, conditionsRef и operationId. Магазин выполняет разрешённое действие своим API-клиентом.

При добавлении commerce API проверяет выбранный SKU и актуальные условия при записи. conditionsRef связывает запрос с условиями для этого SKU и контекста; сервер магазина проверяет срок действия и отклоняет изменённые или устаревшие условия. Подтверждение added содержит те же ID товара, варианта и операции. Только подтверждённый результат переводит карточку в «В корзине». HTTP 200 и наличие SKU в фиде этого не доказывают.

Если требуется форма магазина, отдельно передаются её открытие, закрытие и изменение условий. Закрытие формы и выбор способа получения не считаются добавлением. После получения новых условий покупатель выполняет действие покупки.

Подтверждённое добавление · расширенный контрактПрокрутите схему по горизонтали
Показан успешный путь прямого добавления. Корзину изменяет магазин. При отказе или потере ответа подтверждение не отправляется; правила проверки неизвестного результата описаны ниже.

Сбой связи и безопасный повтор​

В расширенном контракте плеер создаёт уникальный operationId при первом действии добавления и передаёт его обработчику. Код магазина сохраняет полученный ID до окончательного ответа. Сервер связывает ID с исходной корзиной, сессией и неизменным запросом. Повтор с тем же ID и другим SKU или условиями отклоняется.

Потерянный ответ означает неизвестный исход операции. Автоматического повтора по умолчанию нет. Он допустим только при подтверждённой защите API магазина от дублей: неизменный запрос с тем же operationId возвращает исходный результат без второй записи.

Магазин связывает операцию с исходной корзиной и сессией и проверяет права при каждом обращении. operationId и conditionsRef не заменяют авторизацию.

До окончательного ответа код магазина блокирует новое добавление того же SKU в исходную корзину. Открытие корзины, смена варианта и возврат к видео не снимают блокировку. Наличие товара в корзине не доказывает исход конкретной операции. API магазина должен позволять получить окончательный результат без нового добавления; способ проверки, интервал и предел ожидания, срок хранения результата и восстановление после потери сессии фиксируются до запуска.

Покупатель видит «Проверяем добавление» и может открыть корзину магазина. По истечении согласованного времени ожидания показывается «Не удалось проверить добавление». Просмотр видео и переход к корзине остаются доступны; тайм-аут не разрешает повторную запись. Блокировка снимается после подтверждённого добавления или окончательного отказа, который гарантирует отсутствие записи. Для новой корзины проверяется её собственное состояние.

5. Подготовьте обработчики магазина​

Для текущего Web SDK используйте опубликованные callback корзины и обработчик наличия.

Для расширенного контракта подключите чтение условий и выполнение действия. Ниже показана логика адаптера; store — существующий API-клиент вашего магазина. Регистрация обработчиков выполняется по инструкции для подключаемой версии SDK.

При подключении проверьте getProductConditions и startCommerceAction в списке обработчиков этой инструкции. Используйте указанный в ней способ регистрации; имена параметров и вызовы инициализации по примеру адаптера не определяются.

commerce-adapter.js
function isProductPair(value) {
return value !== null && typeof value === 'object' &&
typeof value.productId === 'string' && value.productId.length > 0 &&
(value.variantId === null ||
(typeof value.variantId === 'string' && value.variantId.length > 0));
}

const commerceAdapter = {
async getProductConditions(items) {
if (!Array.isArray(items) || !items.every(isProductPair)) {
throw new Error('Некорректный список товаров');
}
const conditions = await store.readProductConditions(items);
if (!Array.isArray(conditions)) throw new Error('Некорректный ответ магазина');
const requested = new Set(items.map(item => JSON.stringify([item.productId, item.variantId])));
const visibility = new Map();
return conditions.map(({ productId, variantId, price, priceStatus,
available, showProduct, fulfilmentLabel, inCart, action, conditionsRef }) => {
const key = JSON.stringify([productId, variantId]);
const validPrice = price !== null && typeof price === 'object' &&
typeof price.amount === 'string' && /^\d+(?:\.\d+)?$/.test(price.amount) &&
typeof price.currency === 'string' && /^[A-Z]{3}$/.test(price.currency);
if (typeof productId !== 'string' || !productId ||
(variantId !== null && (typeof variantId !== 'string' || !variantId)) ||
!requested.has(key) ||
!['provisional', 'confirmed', 'unknown'].includes(priceStatus) ||
(price === null ? priceStatus !== 'unknown' : !validPrice || priceStatus === 'unknown') ||
[available, showProduct, inCart].some(value => typeof value !== 'boolean') ||
(fulfilmentLabel !== null && typeof fulfilmentLabel !== 'string') ||
(conditionsRef !== null && typeof conditionsRef !== 'string') ||
!['addDirectly', 'chooseFulfilment', 'openMerchantFlow', 'disabled'].includes(action) ||
(action === 'addDirectly' && !conditionsRef)) {
throw new Error('Некорректные условия товара');
}
if (visibility.has(productId) && visibility.get(productId) !== showProduct) {
throw new Error('Противоречивые условия карточки');
}
requested.delete(key);
visibility.set(productId, showProduct);
return {
productId, variantId,
price: price === null ? null : { amount: price.amount, currency: price.currency },
priceStatus, available, showProduct, fulfilmentLabel, inCart, action, conditionsRef,
};
});
},

async startCommerceAction(request, events) {
// Для других action код магазина подключает формы и навигацию по разделам 4 и 6.
if (request?.action !== 'addDirectly') {
throw new Error('Этот пример обрабатывает прямое добавление');
}
if (!isProductPair(request) ||
typeof request.operationId !== 'string' || !request.operationId ||
typeof request.conditionsRef !== 'string' || !request.conditionsRef ||
!events || typeof events.added !== 'function') {
throw new Error('Некорректное действие покупки');
}

// Код магазина удерживает блокировку SKU и проверяет исход при сбое связи.
// Исключение из этого примера само по себе не разрешает повторное добавление.
const result = await store.addSelectedSku(request);
if (!result || typeof result !== 'object' || result.added !== true ||
result.productId !== request.productId ||
result.variantId !== request.variantId ||
result.operationId !== request.operationId) {
throw new Error('Добавление не подтверждено');
}
events.added({
productId: result.productId,
variantId: result.variantId,
operationId: result.operationId,
});
},
};

Метод чтения проверяет обязательные поля, соответствие запрошенным ID, отсутствие дублей и согласованность showProduct. Некорректный ответ отклоняется; код магазина повторно проверяет условия, сохраняя покупку недоступной. Метод возвращает только нормализованные поля раздела 3; дополнительные поля API магазина отбрасываются, в том числе внутри price. Метод добавления передаёт неизменные ID и условия серверу магазина и возвращает подтверждённый результат. В событие для плеера включаются только идентификаторы подтверждённой операции; полный ответ API магазина не передаётся. Пример показывает прямое добавление и проверку идентичности ответа. Его вызывают после проверки action=addDirectly и установки блокировки в коде магазина. Формы, отказы и неизвестный результат обрабатываются по правилам раздела 4.

Для API как источника описания товара дополнительно подготовьте чтение полного товара и его SKU по ID из раздела 2. Для режима «фид + API» повторно передавать описание при каждом обновлении условий не требуется.

6. Настройте переходы и возврат​

Полная карточка, корзина и формы открываются через навигацию магазина. Сигналы screenOpened, screenClosed и conditionsChanged отделены от подтверждения покупки. После возврата плеер сохраняет выбранный товар и SKU и обновляет условия: покупатель мог изменить корзину вне плеера.

Запись сохраняет позицию просмотра; эфир возвращается к текущему моменту. При полном перекрытии плеера или уходе приложения в фон видео и звук приостанавливаются. Возврат, звук и сохранение выбора проверяются на целевых платформах.

На web обработчик работает в коде сайта. В приложении его выполняет код приложения с существующими API-клиентом и авторизацией; экраны магазина открываются штатной навигацией. Формат подключения и целевые платформы фиксируются для используемой версии Mobile SDK.

Безопасность и ответственность​

ShopStoryИнтернет-магазин
Плеер, карточки и выбор SKUКаталог, SKU и торговые правила
Запрос и отображение условийЦена и наличие для текущей сессии
Передача действия и показ результатаПроверка условий, изменение корзины и защита от дублей
Состояние просмотра и обработка возвратаАвторизация, формы, навигация и оплата

Результат подтверждает система, которая владеет корзиной и остатками. Cookies, токены, адреса покупателя и другие персональные данные в ответ для плеера не включаются. Серверные ключи остаются на сервере магазина.

Для browser-интеграции используйте API, доступное с вашей страницы, либо серверный посредник магазина с его проверками доступа. Прямой доступ серверов ShopStory к торговому API для обработчиков не требуется. Commerce-обработчик не является стандартным endpoint Public API.

Клиентские URL, учётные данные, внутренние схемы API и договорные ограничения входят в закрытое техническое приложение. Публичный контракт описывает только данные карточки, действия и сигналы.

Проверка перед запуском​

  • Товарные ID совпадают между источником данных, плеером и корзиной. Выбранный цвет и размер приводят к добавлению нужного SKU.
  • Цена, наличие и действие соответствуют текущей сессии. Пропущенный ответ отличается от подтверждённой недоступности.
  • Выбор получения, отмена формы и отказ API не показывают добавление.
  • Поздний ответ прежнего контекста не меняет карточку. После возврата условия обновляются.
  • Потеря ответа и допустимый повтор не увеличивают количество повторно; неизвестный результат сохраняет блокировку.
  • Эфир, запись, переходы, звук и фон проверены с реальными API и устройствами.

Повторяйте затронутые проверки после изменений API магазина, обработчиков, SDK или навигации.