Добавление товара в корзину
ShopStory Web SDK может передать нажатие «Купить» функции ритейлера. Этот сценарий добавления без перехода на карточку товара называется Short Flow. Корзина, пользовательская сессия, окончательная проверка остатка и результат операции остаются в системе ритейлера.
«Купить» → callback ритейлера → проверка товара и остатка → изменение корзины
→ true только после подтверждённого успеха
Если callback не настроен, кнопка работает как ссылка на карточку товара.
В Mobile SDK приложение получает типизированное действие со строковым feedProductId. Оно проверяет каталог, текущий остаток и результат cart-команды в своей commerce-системе. Обработка результата для каждой платформы описана в документации к поставленной версии SDK: Mobile SDK.
Web SDK callback
Этот callback передаётся в основной экземпляр ShopStorySDK.show(). Standalone PiP bundle его не наследует. Если пользователь открывает player из product mini-player, direct cart требует отдельного контракта, описанного в разделе Кастомные commerce-интеграции.
type CartActions = {
addProductToCartById?: (
feedProductId: string,
callback: (success: boolean) => void,
) => void;
addProductToCart?: (
vendorCode: string,
callback: (success: boolean) => void,
) => void;
};
type ShopStoryOptions = {
actions?: CartActions;
cartUrl?: string;
};
Если заданы обе функции и у товара есть feedProductId, SDK сначала использует addProductToCartById. Для интеграций по vendorCode используется addProductToCart.
Callback получает только один товарный идентификатор. Количество, выбранный магазин, регион и способ получения в его аргументы не входят. Для прямой корзины feedProductId должен однозначно определять продаваемый offer или SKU; commerce endpoint получает остальной контекст из текущей пользовательской сессии и состояния сайта. Если выбор варианта на PDP меняет SKU, передавайте отдельный feedProductId для выбранного offer либо выполняйте явный mapping на стороне ритейлера.
Идентификатор передаётся как строка и не имеет универсальной внутренней структуры:
- не преобразовывайте его в JavaScript
Number; - не удаляйте ведущие нули;
- не предполагайте, что это внутренний ID конкретной CMS;
- используйте сопоставление, согласованное при подключении товарного фида.
Результат Web SDK callback
| Событие | Поведение SDK |
|---|---|
callback(true) | Кнопка переходит в состояние «В корзине» |
callback(false) | Показывается повторяемая ошибка «Не удалось добавить» |
| Синхронное исключение | Показывается повторяемая ошибка |
| Callback не вызван за 10 секунд | Показывается повторяемая ошибка |
| Callback вызван повторно | Повторные вызовы игнорируются |
callback(false) не означает «нет в наличии». Причиной может быть тайм-аут, ошибка сети, истёкшая сессия, отказ API или недоступный товар. Недоступность товара — отдельное состояние карточки, и задаётся оно наличием, а не результатом корзины.
Boolean callback поддерживает только состояния успеха и повторяемой ошибки. Предложение другого магазина и выбор способа получения требуют согласованного commerce-контракта; не выводите причину только из callback(false).
Вызывайте callback(true) только после того, как система ритейлера подтвердила изменение текущей корзины. HTTP 2xx сам по себе не доказывает бизнес-успех.
Если задан cartUrl, повторный клик по состоянию «В корзине» открывает этот URL в новой вкладке. Относительный путь разрешается относительно текущего origin сайта.
Пример адаптера корзины
Адаптируйте пример к API своей платформы: замените названия CSRF-заголовка и полей ответа.
ShopStorySDK.show({
containerElement: document.getElementById('shopstory'),
config: { clientId: 'assigned-application-id' },
cartUrl: '/cart/',
actions: {
addProductToCartById: function (feedProductId, callback) {
let completed = false;
const finish = function (success) {
if (completed) return;
completed = true;
callback(success === true);
};
fetch('/api/cart/items', {
method: 'POST',
credentials: 'same-origin',
signal: AbortSignal.timeout(8000),
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': readPlatformCsrfToken(),
},
body: JSON.stringify({ feedProductId, quantity: 1 }),
})
.then(async function (response) {
if (!response.ok) return finish(false);
const result = await response.json();
finish(
result.added === true &&
String(result.item?.feedProductId) === feedProductId
);
})
.catch(function () {
finish(false);
});
},
},
});
Не копируйте имя X-CSRF-Token, если ваша CMS использует другой механизм. Для 1С-Битрикс, Shopify и других платформ применяйте их штатную защиту сессии и операций изменения данных.
AbortSignal.timeout() должен входить в принятую browser matrix. Для более старого браузера используйте AbortController и таймер из frontend-инфраструктуры проекта.
Пример рассчитан на same-origin endpoint: браузер отправляет действующую cookie только своему origin, а сервер проверяет штатный CSRF token. Если commerce API находится на другом origin или требует server credentials, вызывайте его через backend-for-frontend ритейлера. Не передавайте такие credentials в JavaScript или конфигурацию ShopStory.
В примере merchant API получает 8 секунд, а SDK ждёт callback 10 секунд. После тайм-аута результат может быть неопределённым: сервер мог принять запрос до обрыва соединения. Сначала проверьте корзину, затем решайте, повторять ли запрос.
Требования к endpoint корзины
Endpoint ритейлера должен:
- Принимать только ожидаемый HTTP-метод и content type.
- Проверять пользовательскую сессию, CSRF-токен и допустимый origin.
- Валидировать product key и количество на сервере.
- Проверять текущую доступность товара в том контексте, который влияет на покупку: страна, магазин, способ получения или склад.
- Возвращать успех только после подтверждённого изменения корзины.
- Не включать внутреннюю ошибку, stack trace или учётные данные в публичный ответ.
- Обрабатывать повторный запрос идемпотентно или сверять корзину после неопределённого результата до повторного изменения.
CORS не защищает запрос с cookie от CSRF. Одной cookie сессии недостаточно.
Остатки
Состояние кнопки покупки определяется до клика. Базовый источник — feedProductAvailable, снимок из фида. Если для проекта настроен обработчик наличия, состояние кнопки определяет его ответ по выбранному магазину. При недоступном товаре действие покупки заблокировано и callback корзины не вызывается.
Callback корзины и обработчик наличия решают разные задачи и дополняют друг друга: первый подтверждает результат уже выполненного добавления, второй показывает наличие заранее. Ни один из них не резервирует товар — окончательную проверку выполняет cart-команда ритейлера.
Приоритет источников, контекст магазина, тайм-ауты и поведение при недоступном stock API описаны в разделе Наличие товара и остатки. Условия согласования нестандартных commerce-контрактов — в разделе Кастомные commerce-интеграции.
Проверка перед запуском
- Сопоставление идентификатора проверено на обычном товаре и SKU-варианте.
- Идентификатор остаётся строкой на всём пути.
- Endpoint отклоняет запрос без действующей сессии или CSRF-защиты.
- Текущий остаток проверяется до подтверждения покупки.
callback(true)вызывается только при фактическом успехе.- Mobile action обрабатывается нативным приложением по контракту поставленного пакета, без подмены Web callback.
- Ошибка, exception и тайм-аут оставляют возможность повторить действие.
- После неопределённого тайм-аута состояние корзины сверяется до повтора.
- Callback вызывается не более одного раза.
- Клик по
cartUrlоткрывает правильную корзину. - Событие успеха не отправляется при бизнес-ошибке внутри HTTP
2xx.
Диагностика
| Симптом | Что проверить |
|---|---|
| Открывается карточка товара | Передан ли actions в ShopStorySDK.show() |
| «Не удалось добавить» | HTTP-ответ, бизнес-поле успеха, сессию, CSRF и тайм-аут |
| Добавлен другой SKU | Сопоставление feedProductId/vendorCode; отсутствие числового преобразования |
| Корзина открывается пустой | Работал ли endpoint с той же пользовательской сессией |
| Двойное добавление | Защиту кнопки и идемпотентность merchant endpoint |