Voltar ao blog

Blog

Eventos de comércio eletrónico sem plataforma de loja: pôr um checkout próprio a reportar ao GA4

Publicado · Atualizado · 6 min de leitura

Uma plataforma de loja dá-lhe, pelo menos, um plugin com que discutir. Um checkout construído por si — uma aplicação React, um fluxo de reservas, um registo SaaS com o Stripe por trás — não lhe dá nada: nenhum evento dispara até o seu próprio código o enviar. A boa notícia é que o contrato é pequeno: um punhado de nomes, uma forma de objeto e algumas regras sobre onde pertence cada envio. Está tudo aqui, incluindo a parte que a maioria dos guias deixa de fora — as compras que acontecem quando ninguém está na sua página.

O contrato: um nome e um objeto ecommerce

Tudo aqui chega ao Analytics da mesma forma. A sua página envia um evento para window.dataLayer com o nome que o GA4 lhe dá, levando um objeto ecommerce na forma do GA4. Um acionador do Tag Manager reconhece o nome, e uma etiqueta de evento do GA4 lê o objeto e reencaminha-o para o seu contentor de servidor. Cada contentor da SignalHost já tem esses acionadores e etiquetas no seu contentor web, com o prefixo SH no nome — para os eventos de loja abaixo e para registos, períodos experimentais e leads. O que falta é o envio.

Duas regras apanham a maioria dos erros. Limpe primeiro o objeto ecommerce anterior, ou um view_item de há três páginas segue à boleia dentro do evento seguinte. E em cada evento que envolva dinheiro, envie value como número e currency como código ISO: um value como string ou uma currency em falta é aceite sem queixa e discretamente deixado de fora da receita.

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 }
    ]
  }
});

Que evento, e onde pertence

Um fluxo personalizado tem os mesmos momentos que uma plataforma de loja. Só tem de os encontrar no seu próprio código.

  • view_item_list — uma página ou um componente que mostra vários produtos ou planos. Uma vez, quando a lista é mostrada, não a cada deslocamento.
  • view_item — a página de um único produto ou plano.
  • add_to_cart e remove_from_cart — no handler do botão, depois de o carrinho ter mudado de facto.
  • view_cart — quando o carrinho é mostrado: a sua página, ou a abertura do painel lateral.
  • begin_checkout — o momento em que o visitante entra no checkout: a primeira renderização da página de checkout, ou o clique que abre uma janela de checkout.
  • add_shipping_info — quando é escolhido um método de envio. Deixe-o de fora se não expede nada.
  • add_payment_info — quando os dados de pagamento são aceites, não quando o formulário aparece.
  • purchase — uma vez, quando a encomenda é confirmada. A maior parte deste artigo trata de acertar neste.
  • sign_up e start_trial — num negócio de subscrições, as conversões que acontecem antes de qualquer dinheiro mudar de mãos. A SignalHost constrói etiquetas para ambos, a par dos eventos de loja, e também para leads: generate_lead, contact, schedule, submit_application e subscribe.

Envie a partir do handler que causou a alteração, não a partir de uma renderização. Uma aplicação de página única volta a renderizar à vontade, e o React executa os efeitos duas vezes em desenvolvimento: um envio dentro de um efeito duplica na build de desenvolvimento e repete-se sempre que um componente é montado de novo.

A compra na sua página de confirmação: leia a encomenda do seu servidor

Quando o visitante regressa a uma página sua depois de pagar, é nessa página que o purchase pertence — com duas condições. Obtenha a encomenda do seu próprio backend, não do URL: os parâmetros de consulta podem ser editados, perdem-se em redirecionamentos e também chegam em pagamentos falhados. O Stripe, por exemplo, devolve o visitante ao mesmo endereço quer o pagamento tenha passado quer não, e indica qual dos dois num parâmetro próprio. E envie-o uma única vez: um recarregamento, o botão de retroceder ou uma página de recibo guardada nos favoritos voltam todos a ela.

// 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');
}

O transaction_id é uma rede de segurança, não um plano. O Analytics descarta um transaction_id repetido dentro de uma sessão, mas não um recarregamento da página de recibo na manhã seguinte.

Pagamento numa página alheia: Stripe Checkout, PayPal, um redirecionamento para o banco

Uma página de pagamento alojada é a escolha habitual num checkout feito à medida, e tem uma aresta cortante: o visitante pode nunca regressar. Paga, fecha o separador, e a sua página de confirmação nunca chega a ser renderizada. Com cartões, isso são alguns por cento das compras. Com transferências bancárias, faturas e métodos de pagamento diferido pode ser a maioria, porque o dinheiro chega horas ou dias depois.

Por isso, decida, para cada método de pagamento, onde está a verdade. Se o seu backend fica a saber do pagamento através de um webhook, o webhook é o sítio fiável para o reportar — veja a secção seguinte — e a página de confirmação não pode enviar também o purchase, ou cada pagamento com cartão é contado duas vezes. Se o único sítio onde fica a saber é a página de regresso, reporte-o aí e aceite a lacuna.

Compras sem nenhum navegador presente: envie-as a partir do seu servidor

As subscrições tornam isto inevitável. Um período experimental converte ao fim de sete dias, uma renovação acontece todos os meses, uma fatura é paga por transferência na semana seguinte. Cada uma é uma compra, e nenhuma acontece à frente de um navegador. A resposta é enviar o evento de servidor para servidor, a partir do webhook que fica a saber do pagamento, para o mesmo endpoint de tagging que as suas páginas usam — o seu nome de anfitrião SignalHost ou o seu próprio domínio — para que passe pelo seu contentor de servidor como qualquer outro hit.

Para compras enviadas pelo servidor, salve as escolhas reais de consentimento do visitante junto com o pedido. A ausência de um cookie `_ga` não comprova uma recusa. Este exemplo envia dados apenas ao Analytics e somente com uma permissão registrada e um ID de cliente existente; a publicidade continua negada. O envio às plataformas de publicidade precisa das respectivas permissões salvas separadamente.

// 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' });
}

Escolha uma origem por evento e mantenha-se fiel a ela. É exatamente assim que o signalhost.io mede as suas próprias vendas: o navegador envia sign_up, begin_checkout e add_payment_info; o nosso servidor envia start_trial assim que um período experimental começa de facto, para que um separador fechado não o possa perder; e o webhook do Stripe envia purchase quando a primeira fatura é paga. Nenhum evento tem duas origens. O Measurement Protocol do Google também funcionaria, mas vai diretamente para o Google e contorna o seu contentor de servidor, pelo que nenhuma das suas etiquetas do lado do servidor chega a ver a venda.

As compras tardias são um de quatro motivos pelos quais o GA4 conta menos do que a sua lista de encomendas: os outros três, e quais pode corrigir.

Configurar o banner de cookies

Escolha o modo adequado à política da organização. Não desative um bloqueio deliberado apenas para fazer aparecer eventos.

**Basic:** bloqueie o snippet de seguimento até ao consentimento adequado. Não são esperados eventos de seguimento no SignalHost antes disso.

**Advanced:** carregue as etiquetas Google com valores iniciais `denied`. A Google pode receber medições sem cookies antes da escolha ou após a recusa. Um sinal não é consentimento.

Antes de começar o rastreamento, o banner deve definir as permissões como negadas. Após a escolha, atualize apenas as finalidades permitidas pelo visitante, memorize essa escolha e trate de uma eventual retirada do consentimento.

[Guia de configuração do banner de cookies](/pt/blog/banner-cookies-consent-mode-servidor)

Verificar que funciona

  • Abra o contentor na SignalHost. O cartão de eventos lista o que chegou, por nome, com a parte que trazia consentimento. Um nome que enviou e não vê ali nunca chegou ao seu servidor de tagging.
  • Percorra o seu checkout no modo de pré-visualização do Tag Manager. Mostra cada envio, que etiqueta disparou e o objeto ecommerce que foi enviado.
  • No Analytics, o DebugView mostra os eventos à medida que chegam. Os relatórios padrão podem demorar um dia.
  • Recarregue a página de confirmação duas vezes e conte as compras. Deve continuar a ser uma.

Aqui, as falhas são todas silenciosas: um nome com as maiúsculas erradas, value enviado como string, items aninhados um nível a mais. Nenhuma produz um erro em lado nenhum. Todas produzem um relatório com um buraco.

Deixa de perder dados para bloqueadores

Comece gratuitamente. O primeiro contentor fica pronto em poucos minutos.