<!-- Canonical: https://docs.shopstory.live/docs/guide/confirmed-cart-analytics -->

# Пример: корзина и аналитика

Задача: отправить одно событие GA4 после подтверждённого добавления товара. Клик «Купить», открытие PDP, отказ и timeout с неизвестным исходом не подтверждают корзину.

Этот пример — код analytics adapter магазина. Он не добавляет метод в ShopStory SDK, не вызывает реальный Cart API и не отправляет события, пока вы не подключите свой `sendEvent`. Для проверки формата достаточно Node.js 22+; доступ к SDK и клиентские данные не нужны.

## Запустить на синтетических данных

Скачайте [Web starter](<https://docs.shopstory.live/downloads/shopstory-web-starter.zip>) и откройте его каталог. Файл `confirmed-cart.mjs` содержит полный reporter.

check-analytics.mjs

```javascript
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` и выполните:

```bash
node check-analytics.mjs
```

Ожидается ровно одно событие:

```json
[
  {
    "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](<https://developers.google.com/analytics/devguides/collection/ga4/reference/events#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-периода и данных покупателей.

Связанные разделы: [смысл метрик](<https://docs.shopstory.live/docs/reference/events-metrics>), [Web tracking](<https://docs.shopstory.live/docs/guide/analytics-web>), [Mobile tracking](<https://docs.shopstory.live/docs/guide/analytics-mobile>), [диагностика](<https://docs.shopstory.live/docs/operations/troubleshooting#analytics>).
