博客
没有店铺平台的电商事件:让自建结账流程向 GA4 上报
发布于 · 阅读约 5 分钟
店铺平台至少会给您一个可以较劲的插件。自己搭建的结账流程——一个 React 应用、一个预订流程、一个背后接着 Stripe 的 SaaS 注册——什么都不给您:在您自己的代码推送之前,没有任何事件会触发。好消息是,这份约定很小:几个名称、一种对象格式,以及关于每次推送该放在哪里的几条规则。这里就是它的全部,包括大多数指南略过的那部分——没有人在您页面上时发生的购买。
约定:一个名称和一个 ecommerce 对象
这里的一切都以同样的方式到达 Analytics。您的页面以 GA4 的名称向 window.dataLayer 推送一个事件,携带一个符合 GA4 格式的 ecommerce 对象。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 在开发环境中会把 effect 运行两次:放在 effect 里的推送在开发构建中会翻倍,并在组件每次重新挂载时重复。
确认页面上的 purchase:从您的服务器读取订单
如果访客付款后会回到您的某个页面,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、银行跳转
托管支付页面是自建结账的常见选择,而它有一个需要当心的地方:访客可能再也不会回来。他们付了款、关掉标签页,您的确认页面就永远不会渲染。用银行卡支付时,这占购买的百分之几。用银行转账、发票和先买后付方式时,可能占大多数,因为钱要在几个小时甚至几天之后才到账。
所以,请针对每种支付方式,决定以哪里的信息为准。如果您的后端通过 Webhook 得知这笔付款,Webhook 就是上报它的可靠位置——见下一节——而确认页面就不能再推送 purchase,否则每一笔银行卡付款都会被计两次。如果您唯一能得知付款的地方是返回页面,就在那里上报,并接受这个缺口。
没有浏览器在场的购买:从您的服务器发送
订阅让这一点无法回避。试用在七天后转为付费,续订每月发生,发票下周通过转账支付。每一个都是购买,而没有一个发生在浏览器面前。解决办法是服务器到服务器地发送事件:从得知这笔付款的 Webhook,发送到您的页面所用的同一个跟踪端点——您的 SignalHost 主机名或您自己的域名——这样它就像其他所有命中一样经过您的服务器容器。
它需要结账流程提供一样东西:访客的 Analytics 客户端 ID,这样购买才会归到那位通过一次次访问促成它的访客名下。它在 _ga Cookie 里,而您的结账请求发往您自己的域名时,本来就会带着这个 Cookie。把它和订单一起保存。没有 _ga Cookie,意味着访客没有授予分析同意:要么用一个一次性 ID 把命中作为拒绝同意发送,要么干脆不发送——绝不要替他们编造一个身份。
// 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 Webhook 在第一张发票支付时发送 purchase;没有任何事件由两边同时发送。Google 的 Measurement Protocol 也能用,但它直接发往 Google,绕过您的服务器容器,所以您的服务器端代码没有一个能看到这笔销售。
同意不需要您额外做任何事
无论访客作何选择,都照常推送事件。代码遵守 Consent Mode:有同意时正常发送;没有同意时发送无 Cookie ping,Analytics 用它们来建模,而不是直接报告。自己扣下这些推送,只会让这些转化躲开建模。真正重要的是顺序——Tag Manager 代码片段要放在同意横幅的脚本之后,这样在第一个事件发送之前,同意的默认值就已经存在。
检查它是否生效
- 在 SignalHost 中打开容器。事件卡片按名称列出到达的内容,以及其中带有同意的比例。您推送了却没有在那里看到的名称,从未到达您的跟踪服务器。
- 在 Tag Manager 的预览模式下走一遍结账流程。它会显示每一次推送、哪个代码触发了,以及发送出去的 ecommerce 对象。
- 在 Analytics 中,DebugView 会在事件到达时即时显示。标准报告可能需要一天。
- 把确认页面重新加载两次,然后数一数购买。应该仍然只有一次。
这里的故障全都是无声的:大小写错误的名称、作为字符串发送的 value、多嵌套了一层的 items。它们没有一个会在任何地方报错。而每一个都会产生一份有漏洞的报告。
