Blog
Zdarzenia e-commerce bez platformy sklepowej: jak sprawić, by własna kasa raportowała do GA4
Opublikowano · Zaktualizowano · 5 min czytania
Platforma sklepowa daje Ci przynajmniej wtyczkę, z którą można się spierać. Kasa zbudowana samodzielnie — aplikacja React, ścieżka rezerwacji, rejestracja w SaaS ze Stripe w tle — nie daje Ci nic: żadne zdarzenie się nie uruchomi, dopóki Twój własny kod go nie wypchnie. Dobra wiadomość jest taka, że kontrakt jest niewielki: garść nazw, jeden kształt obiektu i kilka zasad, gdzie należy każde wypchnięcie. Oto całość, łącznie z częścią, którą większość poradników pomija — zakupami, które dzieją się, gdy nikogo nie ma na Twojej stronie.
Kontrakt: nazwa i obiekt ecommerce
Wszystko tutaj trafia do Analytics w ten sam sposób. Twoja strona wypycha do window.dataLayer zdarzenie pod jego nazwą z GA4, z obiektem ecommerce w kształcie GA4. Reguła Tag Managera dopasowuje nazwę, a tag zdarzenia GA4 odczytuje obiekt i przekazuje go dalej do Twojego kontenera serwerowego. Każdy kontener SignalHost ma już te reguły i tagi w swoim kontenerze internetowym, z prefiksem SH w nazwie — dla poniższych zdarzeń sklepowych oraz dla rejestracji, okresów próbnych i leadów. Zostaje samo wypchnięcie.
Dwie zasady wyłapują większość błędów. Najpierw wyczyść poprzedni obiekt ecommerce, inaczej view_item sprzed trzech stron pojedzie na gapę w następnym zdarzeniu. A w każdym zdarzeniu, które niesie pieniądze, wysyłaj value jako liczbę, a currency jako kod ISO: value w postaci ciągu znaków albo brak currency zostaną przyjęte bez słowa skargi i po cichu pominięte w przychodach.
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 }
]
}
});Które zdarzenie i gdzie jest jego miejsce
Własna ścieżka ma te same momenty co platforma sklepowa. Musisz je tylko znaleźć we własnym kodzie.
- view_item_list — strona lub komponent pokazujący kilka produktów albo planów. Raz, gdy lista się wyświetli, a nie przy każdym przewinięciu.
- view_item — strona pojedynczego produktu lub planu.
- add_to_cart i remove_from_cart — w handlerze przycisku, po tym, jak koszyk faktycznie się zmienił.
- view_cart — gdy koszyk zostaje pokazany: jego strona albo otwarcie wysuwanego panelu koszyka.
- begin_checkout — moment, w którym odwiedzający wchodzi do kasy: pierwsze renderowanie strony kasy albo kliknięcie, które otwiera okno kasy.
- add_shipping_info — gdy zostanie wybrana metoda wysyłki. Pomiń je, jeśli niczego nie wysyłasz.
- add_payment_info — gdy dane płatności zostaną przyjęte, a nie gdy pojawi się formularz.
- purchase — raz, gdy zamówienie zostanie potwierdzone. Większość tego artykułu dotyczy tego, jak zrobić dobrze właśnie to zdarzenie.
- sign_up i start_trial — w biznesie subskrypcyjnym konwersje, które następują, zanim jakiekolwiek pieniądze zmienią właściciela. SignalHost buduje tagi dla obu, obok zdarzeń sklepowych, a także dla leadów: generate_lead, contact, schedule, submit_application i subscribe.
Wypychaj z handlera, który spowodował zmianę, a nie z renderowania. Aplikacja jednostronicowa renderuje się ponownie, kiedy tylko chce, a React w trybie deweloperskim uruchamia efekty dwukrotnie: wypchnięcie wewnątrz efektu podwaja się w buildzie deweloperskim i powtarza przy każdym ponownym zamontowaniu komponentu.
Zakup na Twojej stronie potwierdzenia: odczytaj zamówienie ze swojego serwera
Gdy odwiedzający po zapłacie wraca na Twoją stronę, to właśnie tam należy purchase — pod dwoma warunkami. Pobierz zamówienie z własnego backendu, nie z adresu URL: parametry zapytania można edytować, przekierowania je gubią, a do tego pojawiają się też przy nieudanych płatnościach. Stripe na przykład odsyła na ten sam adres niezależnie od tego, czy płatność przeszła, czy nie, a który to przypadek, podaje we własnym parametrze. I wypchnij zakup tylko raz: odświeżenie, przycisk wstecz czy strona z paragonem zapisana w zakładkach — wszystko to prowadzi z powrotem na nią.
// 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 to siatka bezpieczeństwa, a nie plan. Analytics odrzuca powtórzony transaction_id w obrębie jednej sesji, ale nie odświeżenie strony z paragonem następnego ranka.
Płatność na cudzej stronie: Stripe Checkout, PayPal, przekierowanie do banku
Hostowana strona płatności to typowy wybór przy własnej implementacji i ma jedną ostrą krawędź: odwiedzający może nigdy nie wrócić. Płaci, zamyka kartę przeglądarki, a Twoja strona potwierdzenia nigdy się nie wyrenderuje. Przy płatnościach kartą to kilka procent zakupów. Przy przelewach bankowych, fakturach i metodach „kup teraz, zapłać później” może to być większość, bo pieniądze docierają kilka godzin albo dni później.
Zdecyduj więc, dla każdej metody płatności z osobna, gdzie leży prawda. Jeśli Twój backend dowiaduje się o płatności z webhooka, to webhook jest niezawodnym miejscem, by ją zgłosić — patrz następna sekcja — a strona potwierdzenia nie może dodatkowo wypychać purchase, bo inaczej każda płatność kartą zostanie policzona dwa razy. Jeśli jedynym miejscem, w którym się o niej dowiadujesz, jest strona powrotu, zgłoś ją tam i pogódź się z luką.
Zakupy, przy których nie ma przeglądarki: wysyłaj je ze swojego serwera
Przy subskrypcjach nie da się tego uniknąć. Okres próbny przechodzi w płatny po siedmiu dniach, odnowienie następuje co miesiąc, faktura zostaje opłacona przelewem w przyszłym tygodniu. Każde z nich to zakup i żadne nie dzieje się przed przeglądarką. Rozwiązaniem jest wysłanie zdarzenia z serwera do serwera — z webhooka, który dowiaduje się o płatności — do tego samego punktu końcowego tagowania, którego używają Twoje strony: Twojej nazwy hosta SignalHost albo Twojej własnej domeny, żeby przeszło przez kontener serwerowy jak każde inne trafienie.
W przypadku zakupów wysyłanych z serwera zapisz rzeczywiste wybory dotyczące zgody odwiedzającego wraz z zamówieniem. Brak pliku cookie `_ga` nie potwierdza odmowy. Ten przykład wysyła dane tylko do Analytics i wyłącznie po zapisaniu zgody oraz przy istniejącym identyfikatorze klienta; reklamy pozostają niedozwolone. Wysyłanie danych do platform reklamowych wymaga osobno zapisanych odpowiednich uprawnień.
// At checkout: save the visitor's actual banner choice with the order.
// Cookie presence alone is NOT consent. This example is Analytics-only.
const ga = req.cookies._ga;
order.gaClientId = ga ? ga.split('.').slice(-2).join('.') : null;
order.analyticsGranted = req.body.consent?.analytics === true;
// Later, in the payment webhook: only send with recorded permission and id.
if (order.analyticsGranted === true && order.gaClientId) {
const params = new URLSearchParams({
v: '2',
tid: 'G-XXXXXXXXXX',
cid: order.gaClientId,
gcs: 'G101', // Analytics allowed; advertising stays denied.
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' });
}Wybierz jedno źródło dla każdego zdarzenia i trzymaj się go. Dokładnie tak signalhost.io mierzy własną sprzedaż: przeglądarka wypycha sign_up, begin_checkout i add_payment_info; nasz serwer wysyła start_trial, gdy okres próbny faktycznie się rozpocznie, więc zamknięta karta przeglądarki nie może go zgubić; a webhook Stripe wysyła purchase, gdy opłacona zostanie pierwsza faktura. Żadne zdarzenie nie ma dwóch źródeł. Measurement Protocol od Google też by zadziałał, ale trafia prosto do Google i omija Twój kontener serwerowy, więc żaden z Twoich tagów po stronie serwera nigdy nie zobaczy tej sprzedaży.
Spóźnione zakupy to jeden z czterech powodów, dla których GA4 liczy mniej niż lista zamówień: pozostałe trzy i które z nich da się naprawić.
Skonfiguruj baner plików cookie
Wybierz tryb zgodny z zasadami organizacji. Nie wyłączaj celowego blokowania skryptów tylko po to, by zobaczyć zdarzenia.
**Basic:** blokuj fragment śledzący do uzyskania właściwej zgody. Przed zgodą nie oczekuje się zdarzeń śledzących w SignalHost.
**Advanced:** tagi Google wczytują się z domyślnym `denied`. Google może otrzymywać pomiary bez plików cookie przed wyborem lub po odmowie. Taki sygnał nie oznacza zgody.
Zanim rozpocznie się śledzenie, baner musi ustawić uprawnienia jako odrzucone. Po dokonaniu wyboru zaktualizuj tylko cele dozwolone przez odwiedzającego, zapamiętaj ten wybór i obsłuż wycofanie zgody.
[Poradnik konfiguracji banera plików cookie](/pl/blog/baner-cookie-consent-mode-serwer)
Sprawdzenie, czy to działa
- Otwórz kontener w SignalHost. Karta zdarzeń pokazuje, co dotarło, według nazwy, wraz z odsetkiem objętym zgodą. Wypchnięta nazwa, której tam nie widzisz, nigdy nie dotarła do Twojego serwera tagowania.
- Przejdź przez swoją kasę w trybie podglądu Tag Managera. Pokazuje on każde wypchnięcie, który tag się uruchomił i jaki obiekt ecommerce został wysłany.
- W Analytics DebugView pokazuje zdarzenia na bieżąco, w miarę jak docierają. Raporty standardowe mogą potrzebować doby.
- Odśwież stronę potwierdzenia dwa razy i policz zakupy. Nadal powinien być jeden.
Wszystkie usterki są tu ciche: nazwa zapisana z niewłaściwą wielkością liter, value wysłane jako ciąg znaków, items zagnieżdżone o poziom za głęboko. Żadna z nich nigdzie nie generuje błędu. Każda z nich daje raport z dziurą.

