Интеграция с интернет-магазином
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. Ответы с условиями покупателя обновляют его карточку и не меняют общий каталог. Правила отсутствующих товаров и неполного обновления фиксируются до подключения; ошибка источника не должна незаметно удалять существующие варианты.
2. Опишите товары и варианты
| Данные | Назначение |
|---|---|
| ID товара | Связывает карточку с товаром магазина |
| Название, изображения и URL | Содержимое карточки и переход на полную страницу |
| ID каждого SKU | Однозначно определяет продаваемый вариант |
| Связь SKU с товаром | Объединяет варианты в одной карточке |
| Опции SKU | Цвет, размер и другие значения для выбора |
Передавайте только существующие сочетания опций. Например, «синий, размер M» и «синий, размер L» — два отдельных SKU со своими ценой и наличием. Недоступность одного размера не означает недоступность всех синих вариантов.
Все ID передаются строками с сохранением ведущих нулей и регистра. В расширенном контракте productId соответствует согласованному feedProductId, а variantId — идентификатору SKU; для товара без вариантов variantId=null. Выбранные опции должны однозначно указывать на один SKU. Магазин проверяет именно этот SKU при добавлении.
Список вариантов поступает из выбранного товарного источника. Плеер не создаёт сочетания из отдельных списков цветов и размеров. Временно отсутствующий SKU остаётся в структуре; его состояние определяется актуальными условиями.
Пример нормализованных товарных данных для API-режима:
{
"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, если цена ещё не определена |
priceStatus | provisional — предварительная, 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 в списке обработчиков этой инструкции. Используйте указанный в ней способ регистрации; имена параметров и вызовы инициализации по примеру адаптера не определяются.
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 или навигации.