Блог
События электронной торговли без платформы магазина: как заставить собственное оформление заказа отправлять данные в GA4
Опубликовано · 5 мин чтения
Платформа магазина даёт вам хотя бы плагин, с которым можно воевать. Оформление заказа, написанное вами самими, — приложение на React, процесс бронирования, регистрация в SaaS со Stripe за ней — не даёт ничего: ни одно событие не сработает, пока его не отправит ваш собственный код. Хорошая новость в том, что контракт невелик: горстка имён, одна форма объекта и несколько правил о том, где место каждой отправки. Здесь он целиком, включая часть, которую опускает большинство руководств, — покупки, которые происходят, когда на вашей странице никого нет.
Контракт: имя и объект ecommerce
Всё, о чём здесь идёт речь, попадает в Analytics одним и тем же путём. Ваша страница отправляет в window.dataLayer событие под его именем в GA4, с объектом ecommerce в форме GA4. Триггер Tag Manager срабатывает на это имя, а тег события GA4 читает объект и передаёт его дальше, в ваш серверный контейнер. Если вы сообщили SignalHost, что ваш сайт — магазин, или нажали «Настроить» в разделе «События конверсии» на странице контейнера, эти триггеры и теги уже есть в вашем веб-контейнере, с префиксом SH в названии. Остаётся только отправка.
Два правила отсекают большинство ошибок. Сначала очищайте предыдущий объект ecommerce, иначе view_item трёхстраничной давности поедет внутри следующего события. И в каждом событии с денежной суммой передавайте value числом, а currency — кодом ISO: value строкой или отсутствующая currency принимаются без единой жалобы и молча не попадают в выручку.
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: 'begin_checkout',
ecommerce: {
currency: 'EUR',
value: 49.00,
items: [
{ item_id: 'plan-pro', item_name: 'Pro', price: 49.00, quantity: 1 }
]
}
});Какое событие и где его место
В собственном процессе те же моменты, что и на платформе магазина. Просто искать их придётся в своём коде.
- view_item_list — страница или компонент, где показано несколько товаров или тарифов. Один раз, когда список показан, а не при каждой прокрутке.
- view_item — страница одного товара или тарифа.
- add_to_cart и remove_from_cart — в обработчике кнопки, после того как ваша корзина действительно изменилась.
- begin_checkout — момент, когда посетитель входит в оформление заказа: первая отрисовка страницы оформления или клик, открывающий диалог оформления.
- add_payment_info — когда платёжные данные приняты, а не когда появилась форма.
- purchase — один раз, когда заказ подтверждён. Большая часть этой статьи посвящена тому, чтобы не ошибиться именно с ним.
- sign_up и start_trial — для бизнеса на подписках это конверсии до того, как деньги перейдут из рук в руки. SignalHost создаёт теги для обоих наряду с событиями магазина.
Отправляйте из обработчика, который вызвал изменение, а не при отрисовке. Одностраничное приложение перерисовывается сколько угодно, а React в режиме разработки запускает эффекты дважды: отправка внутри эффекта удваивается в сборке для разработки и повторяется при каждом повторном монтировании компонента.
Покупка на странице подтверждения: берите заказ со своего сервера
Если после оплаты посетитель возвращается на вашу страницу, именно там место purchase — при двух условиях. Берите заказ из собственного бэкенда, а не из URL: параметры запроса можно отредактировать, редиректы их отбрасывают, а приходят они и при неудавшихся платежах. Stripe, например, возвращает на один и тот же адрес независимо от того, прошёл платёж или нет, а что именно произошло, сообщает в собственном параметре. И отправляйте событие один раз: перезагрузка, кнопка «Назад» или страница чека в закладках — всё это возвращает на неё.
// On your confirmation page. The order comes from YOUR server, by an id the
// page already knows — never from price or status parameters in the URL.
const order = await fetch(`/api/orders/${orderId}`).then((r) => r.json());
const key = `purchase-sent-${order.id}`;
if (order.status === 'paid' && !localStorage.getItem(key)) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: 'purchase',
ecommerce: {
transaction_id: order.id,
value: order.total,
tax: order.tax,
currency: order.currency,
items: order.items.map((i) => ({
item_id: i.sku, item_name: i.name, price: i.price, quantity: i.quantity
}))
}
});
localStorage.setItem(key, '1');
}transaction_id — страховочная сетка, а не план. Analytics отбрасывает повторный transaction_id в пределах одной сессии, но не перезагрузку страницы чека на следующее утро.
Оплата на чужой странице: Stripe Checkout, PayPal, перенаправление в банк
Размещённая платёжная страница — обычный выбор для собственной разработки, и у неё есть один острый угол: посетитель может так и не вернуться. Он платит, закрывает вкладку, и ваша страница подтверждения так и не отрисовывается. С картами это несколько процентов покупок. С банковскими переводами, счетами и способами «купи сейчас — плати потом» это может быть большинство из них, потому что деньги приходят через часы или дни.
Поэтому для каждого способа оплаты решите, где находится истина. Если ваш бэкенд узнаёт о платеже из вебхука, вебхук и есть надёжное место, чтобы о нём сообщить (см. следующий раздел), — и тогда страница подтверждения не должна отправлять purchase ещё раз, иначе каждый платёж картой будет посчитан дважды. Если узнать о платеже можно только на странице возврата, сообщайте о нём там и смиритесь с пробелом.
Покупки без браузера: отправляйте их со своего сервера
С подписками этого не избежать. Пробный период переходит в платную подписку через семь дней, продление происходит каждый месяц, счёт оплачивают переводом на следующей неделе. Каждый из этих случаев — покупка, и ни один не происходит перед браузером. Решение — отправлять событие с сервера на сервер, из вебхука, который узнаёт о платеже, на ту же конечную точку тегинга, которую используют ваши страницы, — ваше имя хоста SignalHost или ваш собственный домен, — чтобы оно проходило через серверный контейнер, как любое другое обращение.
От оформления заказа для этого нужно одно: client id посетителя в Analytics, чтобы покупка была засчитана тому человеку, чьи визиты к ней привели. Он хранится в cookie _ga, и ваш запрос оформления заказа уже передаёт этот cookie, когда идёт на ваш собственный домен. Сохраните его вместе с заказом. Нет cookie _ga — значит, посетитель не дал согласия на аналитику: отправьте обращение с отказом в согласии и одноразовым идентификатором или не отправляйте вовсе, но никогда не придумывайте ему личность.
// In your checkout request handler: keep the visitor's Analytics id with the order.
const ga = req.cookies._ga; // "GA1.1.1234567890.1700000000"
order.gaClientId = ga ? ga.split('.').slice(-2).join('.') : null;
// Later, in your payment webhook — a trial converting, an invoice paid:
const params = new URLSearchParams({
v: '2',
tid: 'G-XXXXXXXXXX', // your GA4 measurement ID
cid: order.gaClientId ?? `${Date.now()}.${Math.floor(Math.random() * 1e9)}`,
gcs: order.gaClientId ? 'G101' : 'G100', // no _ga cookie = no consent
en: 'purchase',
'ep.transaction_id': invoice.id,
'epn.value': '49.00',
cu: 'EUR',
pr1: 'idplan-pro~nmPro~pr49.00~qt1',
});
await fetch(`https://metrics.example.com/g/collect?${params}`, { method: 'POST' });Выберите один источник для каждого события и придерживайтесь его. Именно так signalhost.io измеряет собственные продажи: браузер отправляет sign_up, begin_checkout и start_trial; вебхук Stripe отправляет purchase, когда оплачен первый счёт; ни одно событие не приходит из обоих источников. Measurement Protocol от Google тоже подошёл бы, но он идёт прямо в Google в обход вашего серверного контейнера, так что ни один ваш серверный тег эту продажу так и не увидит.
Согласие не требует от вас ничего дополнительного
Отправляйте события, что бы ни выбрал посетитель. Теги подчиняются Consent Mode: с согласием они отправляют данные как обычно; без него — пинги без cookie, которые Analytics моделирует, а не показывает в отчётах. Придерживая отправки сами, вы лишь скрываете конверсии от этого моделирования. Что действительно важно — порядок: сниппет Tag Manager идёт после скрипта вашего баннера согласия, чтобы значения согласия по умолчанию уже существовали, когда отправляется первое событие.
Как проверить, что всё работает
- Откройте контейнер в SignalHost. Карточка событий показывает, что пришло, по именам, вместе с долей обращений, в которых было согласие. Имя, которое вы отправили, но не видите там, так и не дошло до вашего сервера тегинга.
- Пройдите оформление заказа в режиме предпросмотра Tag Manager. Он показывает каждую отправку, какой тег сработал и какой объект ecommerce был отправлен.
- В Analytics события по мере поступления показывает DebugView. Стандартные отчёты могут занять сутки.
- Перезагрузите страницу подтверждения дважды и посчитайте покупки. Покупка по-прежнему должна быть одна.
Все сбои здесь тихие: имя не в том регистре, value, отправленное строкой, items, вложенные на уровень глубже, чем нужно. Ни один из них нигде не выдаёт ошибку. Каждый из них выдаёт отчёт с дырой.
