Пример: корзина и аналитика
Задача: отправить одно событие GA4 после подтверждённого добавления товара. Клик «Купить», открытие PDP, отказ и timeout с неизвестным исходом не подтверждают корзину.
Этот пример — код analytics adapter магазина. Он не добавляет метод в ShopStory SDK, не вызывает реальный Cart API и не отправляет события, пока вы не подключите свой sendEvent. Для проверки формата достаточно Node.js 22+; доступ к SDK и клиентские данные не нужны.
Запустить на синтетических данных
Скачайте Web starter и откройте его каталог. Файл confirmed-cart.mjs содержит полный reporter.
import { createConfirmedCartReporter } from './confirmed-cart.mjs';
const events = [];
const report = createConfirmedCartReporter((name, payload) => {
events.push({ name, payload });
});
const receipt = {
status: 'confirmed',
operationId: 'cart-operation-1',
feedProductId: '0001001',
unitPriceMinor: 129900,
quantity: 2,
currency: 'RUB',
};
await report({ status: 'unknown' }, { hasConsent: true });
await report({ status: 'rejected' }, { hasConsent: true });
await report(receipt, { hasConsent: false });
await report(receipt, { hasConsent: true });
await report(receipt, { hasConsent: true });
console.log(JSON.stringify(events, null, 2));
Сохраните код рядом с confirmed-cart.mjs и выполните:
node check-analytics.mjs
Ожидается ровно одно событие:
[
{
"name": "add_to_cart",
"payload": {
"currency": "RUB",
"value": 2598,
"items": [{ "item_id": "0001001", "price": 1299, "quantity": 2 }]
}
}
]
Это проверяет момент отправки, формат и локальную дедупликацию. Получение события системой аналитики проверяется отдельно.
Откуда берётся receipt
receipt — нормализованный ответ commerce-кода магазина, а не формат ShopStory. Его значения берутся из подтверждённого результата Cart API, а не из product action или снимка каталога player.
| Поле примера | Значение |
|---|---|
status | confirmed — магазин подтвердил добавление; rejected — отказ; unknown — исход ещё не выяснен |
operationId | Стабильная идентичность одной cart operation; повторная доставка использует тот же ID |
feedProductId | Строковый товарный или SKU ID, согласованный для E-commerce |
unitPriceMinor | Подтверждённая цена единицы в копейках после скидок, приведённая к базису GA4 |
quantity | Число добавленных единиц; пример рассчитан на целое положительное количество |
currency | В этом примере только RUB |
Расчёт выполняется в целых копейках, затем переводится в рубли для GA4: 129900 × 2 / 100 = 2598. Некорректные ID, количество, валюта и переполнение числа отклоняются. Для другой валюты, весового товара или отдельного распределения скидок адаптируйте схему по подтверждённым условиям магазина.
В value не включайте доставку и налог. Приведение цены магазина к этому базису выполняется до создания receipt; reporter не вычисляет налог и не меняет условия Cart API.
Подключить к интеграции
Создайте reporter один раз в integration owner. Передайте существующий analytics dispatcher магазина, например (name, payload) => gtag('event', name, payload). Используйте принятый consent flow; пример по умолчанию ничего не отправляет.
| Платформа | Где вызывать reporter или эквивалентный analytics-код |
|---|---|
| Web short flow | В ветке подтверждённого результата Cart API; назначьте одного отправителя в целевую систему, чтобы SDK и клиентский callback не дублировали событие |
| Native iOS | В commerce-коде приложения после успешной cart operation. Delegate SDK не имеет completion |
| Flutter на iOS | После подтверждения cart operation; ShopStoryProductActionSucceeded возвращается отдельно для lifecycle adapter |
| Переход на PDP | Cart event не отправляется; переход при необходимости учитывается своим событием |
Функция sendEvent принимает имя и очищенный payload. Успешное завершение reporter означает вызов dispatcher, а не доставку GA4. Схема value, currency и items соответствует GA4 add_to_cart.
Повторы и неизвестный результат
Reporter подавляет повтор одного operationId, включая параллельные вызовы, пока живёт этот экземпляр. Он не хранит состояние между перезагрузками и не гарантирует exactly-once доставку в стороннюю аналитику.
Идемпотентность самой cart operation обеспечивает backend магазина. Reporter не повторяет запросы корзины. При timeout сначала выясните состояние операции; после подтверждения можно передать тот же receipt. Если dispatcher вызвал исключение, сначала проверьте исход отправки, прежде чем повторять аналитику.
Для заказа используйте отдельное событие purchase с согласованным transaction_id. Cart operation ID и Buy intent не заменяют ID заказа.
Приёмка разработчиком и аналитиком
| Сценарий | Ожидаемый результат |
|---|---|
| Один подтверждённый receipt | Один вызов dispatcher с правильным товаром, количеством и суммой |
| Тот же receipt повторно или параллельно | Второй вызов dispatcher отсутствует |
unknown, rejected, открытие PDP | add_to_cart отсутствует |
| Consent запрещён или не передан | Dispatcher не вызывается |
| Другая подтверждённая operation | Новое событие; прежний ID не переиспользуется для нового действия |
| Ошибка данных | Ошибка интеграции; неверный payload не отправляется |
После локальной проверки выполните тест с согласованным проектом: подтверждение Cart API → вызов dispatcher → исходящий запрос → запись в отчёте. Тестовые события должны быть отделены от production-периода и данных покупателей.
Связанные разделы: смысл метрик, Web tracking, Mobile tracking, диагностика.