← Voltar ao blog

Tracking de ecommerce GA4 e GTM no Magento 2 que fecha a conta

Rodrigo Biazzi

O tracking de ecommerce do GA4 no Magento 2 parece simples: envia alguns eventos para o data layer e deixa o Google Tag Manager mandar. Os problemas aparecem depois, quando a receita no GA4 não bate com os pedidos do admin, os eventos de add to cart faltam em metade dos produtos ou o purchase é contado duas vezes. A maioria disso vem do jeito como o Magento renderiza as páginas: full page cache, add to cart por AJAX, um checkout baseado em Knockout e uma success page que pode ou não ser vista.

Este artigo cobre uma configuração de data layer para o Magento 2.4 que se sustenta: quais eventos enviar, de onde vem cada um e os pontos específicos do Magento que quebram o tracking.

Integração nativa ou data layer customizado

Releases recentes do Open Source incluem uma integração com Google Analytics baseada em gtag em Stores, Configuration, Sales, Google API, e o Adobe Commerce adiciona uma integração com Google Tag Manager com data layer próprio. Confira o que a sua versão e edição exatas oferecem antes de construir qualquer coisa. As opções nativas servem para um tracking básico de purchase, mas a maioria dos times acaba precisando de um data layer que controlem por completo, porque o marketing quer campos extras, o tratamento de consent precisa se encaixar no banner deles e o tema muitas vezes não é o Luma.

A abordagem abaixo é um módulo customizado pequeno que envia eventos de ecommerce do GA4 para window.dataLayer. O GTM os captura com triggers de evento customizado e envia para o GA4 e qualquer outra ferramenta.

Os eventos que importam

Evento GA4Onde no MagentoOrigem dos dados
view_item_listPáginas de categoria e buscaNo servidor, no HTML em cache
view_itemPágina de produtoNo servidor, no HTML em cache
add_to_cartPágina de produto, listagensJavaScript, depois do sucesso do AJAX
remove_from_cartMinicart, página do carrinhoJavaScript ou reload da página do carrinho
begin_checkoutCarregamento da página de checkoutDados do carrinho vindos do customer data
add_shipping_infoEnvio da etapa de shippingJavaScript do checkout
add_payment_infoClique em place orderJavaScript do checkout
purchaseSuccess pageNo servidor, a partir do pedido

Comece com view_item, add_to_cart, begin_checkout e purchase. Eles dão um funil utilizável. Adicione os outros quando esses estiverem estáveis.

Regras do full page cache

As páginas de categoria e de produto são servidas pelo Varnish ou pelo cache embutido. Tudo que você renderiza nelas é igual para todo visitante naquele contexto de cache. Isso é aceitável para dados de produto, como ID do item, nome e preço do grupo guest. É errado para qualquer coisa pessoal, como e-mail ou ID do cliente, ou conteúdo do carrinho. Dados pessoais precisam vir das customer data sections carregadas por JavaScript, que é como o minicart funciona. Veja customer data sections para entender como elas funcionam.

Nunca marque um block de tracking como cacheable="false" para colocar dados por usuário nele. Isso desativa o full page cache da página inteira. Os motivos estão explicados em full page cache com Varnish.

O evento purchase

A success page não é cacheada, então o evento purchase pode ser montado no servidor a partir do último pedido na sessão do checkout. Um view model:

<?php
declare(strict_types=1);

namespace Vendor\Ga4\ViewModel;

use Magento\Checkout\Model\Session as CheckoutSession;
use Magento\Framework\View\Element\Block\ArgumentInterface;

class Purchase implements ArgumentInterface
{
    public function __construct(
        private readonly CheckoutSession $checkoutSession
    ) {
    }

    public function getEventJson(): string
    {
        $order = $this->checkoutSession->getLastRealOrder();
        if (!$order->getEntityId()) {
            return '';
        }

        $items = [];
        foreach ($order->getAllVisibleItems() as $item) {
            $items[] = [
                'item_id' => $item->getSku(),
                'item_name' => $item->getName(),
                'price' => round((float) $item->getPrice(), 2),
                'quantity' => (int) $item->getQtyOrdered(),
                'discount' => round((float) $item->getDiscountAmount() / max(1, (int) $item->getQtyOrdered()), 2),
            ];
        }

        $event = [
            'event' => 'purchase',
            'ecommerce' => [
                'transaction_id' => $order->getIncrementId(),
                'value' => round((float) $order->getGrandTotal(), 2),
                'tax' => round((float) $order->getTaxAmount(), 2),
                'shipping' => round((float) $order->getShippingAmount(), 2),
                'currency' => $order->getOrderCurrencyCode(),
                'coupon' => (string) $order->getCouponCode(),
                'items' => $items,
            ],
        ];

        return (string) json_encode($event, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP);
    }
}

Registre no handle checkout_onepage_success e renderize com o secure renderer, para que o script inline receba um nonce de CSP onde for exigido:

<?php
/** @var \Magento\Framework\View\Element\Template $block */
/** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */
$json = $block->getData('view_model')->getEventJson();
if ($json === '') {
    return;
}
$script = 'window.dataLayer = window.dataLayer || [];'
    . 'window.dataLayer.push({ecommerce: null});'
    . 'window.dataLayer.push(' . $json . ');';
?>
<?= /* @noEscape */ $secureRenderer->renderTag('script', [], $script, false) ?>

Enviar ecommerce: null primeiro limpa o objeto ecommerce anterior no modelo de dados do GTM, o que evita que campos de um evento anterior vazem para este.

Por que os números de purchase não batem

Espere que o GA4 mostre menos purchases que o admin. Ad blockers, consent recusado e clientes que fecham a aba antes de a success page carregar causam lacunas. Meios de pagamento que redirecionam para uma página externa e voltam depois são o pior caso. Se o cliente não volta, não há success page e não há evento.

O sentido contrário, purchases demais, acontece quando a success page é renderizada mais de uma vez para o mesmo pedido, por exemplo por reloads ou módulos customizados de success page. Mantenha o transaction_id estável para o GA4 poder filtrar duplicatas, e se você vir dobrados, guarde uma flag na sessão depois da primeira renderização e pule o evento quando ela estiver definida.

Quando os números precisam ser quase exatos, envie o purchase pelo servidor com o Measurement Protocol do GA4, disparado quando o pedido é feito ou faturado. Você precisa do client ID do GA, que dá para capturar do cookie _ga no checkout e guardar na quote. É mais trabalho, e deve substituir o evento de purchase do navegador, não rodar junto com ele.

O evento add_to_cart

No Luma, o add to cart pela página de produto e pelas listagens é uma requisição AJAX tratada pelo Magento_Catalog/js/catalog-add-to-cart. Depois de uma resposta bem-sucedida, ele dispara um evento jQuery no document chamado ajax:addToCart, com o SKU e os IDs do produto. Escute esse evento:

define(['jquery'], function ($) {
    'use strict';

    return function (config) {
        $(document).on('ajax:addToCart', function (event, data) {
            var qty = 1;

            if (data.form) {
                qty = parseInt(data.form.find('input[name="qty"]').val(), 10) || 1;
            }

            window.dataLayer = window.dataLayer || [];
            window.dataLayer.push({ecommerce: null});
            window.dataLayer.push({
                event: 'add_to_cart',
                ecommerce: {
                    currency: config.currency,
                    items: [{item_id: data.sku, quantity: qty}]
                }
            });
        });
    };
});

Os dados do evento não trazem preço. Se você precisa do value no add to cart, renderize na página um mapa pequeno de ID do produto para preço, o que é seguro no HTML em cache, e consulte pelo ID do produto em data.productIds. Produtos configuráveis reportam aqui o SKU do pai, então decida se o GA4 deve ver o pai ou o filho selecionado e seja consistente com view_item e purchase.

Os temas Hyvä não usam esse evento jQuery. Eles disparam eventos próprios do navegador para atualizações do carrinho, então o listener precisa ser escrito para esse tema. Confira a documentação do tema para o nome do evento.

Eventos do checkout

O checkout do Luma é uma aplicação Knockout. O begin_checkout pode ser enviado no carregamento da página usando a section cart do customer data. O add_shipping_info se encaixa como um mixin na action setShippingInformation da etapa de shipping, e o add_payment_info como um mixin na action de place order. Mixins são explicados em mixins do RequireJS. Mantenha esses mixins finos e envolva em try e catch, porque um erro de JavaScript no código de tracking nunca pode bloquear um pedido.

Carregando o GTM e o CSP

Adicione o snippet do container do GTM ao head pelo seu tema ou módulo. A Content Security Policy do Magento precisa permitir os hosts do Google, ou o navegador vai bloquear scripts e requisições. Adicione no etc/csp_whitelist.xml do seu módulo:

<?xml version="1.0"?>
<csp_whitelist xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Csp:etc/csp_whitelist.xsd">
    <policies>
        <policy id="script-src">
            <values>
                <value id="gtm" type="host">https://www.googletagmanager.com</value>
            </values>
        </policy>
        <policy id="connect-src">
            <values>
                <value id="ga-collect" type="host">https://*.google-analytics.com</value>
                <value id="ga-analytics" type="host">https://*.analytics.google.com</value>
            </values>
        </policy>
    </policies>
</csp_whitelist>

Releases recentes aplicam uma política mais rígida na página de pagamento do checkout, então teste o tracking especificamente ali. Toda tag adicionada no GTM que carrega de outro host precisa da própria entrada.

Consent

Em regiões com exigência de consent, defina os defaults do Google consent mode antes do snippet do GTM e atualize quando o visitante aceitar. O banner de consent, seja um módulo ou uma ferramenta de terceiros, precisa enviar a atualização para o data layer. Teste o fluxo completo com consent recusado e também aceito.

Testes

  1. Use o modo preview do GTM e faça uma compra completa com um meio de pagamento de teste.
  2. Confira no DebugView do GA4 cada evento e os parâmetros dos itens.
  3. Teste como visitante e como cliente logado, e com um produto configurável.
  4. Compare uma semana de purchases do GA4 com os pedidos do admin para conhecer a sua lacuna normal.

FAQ

Por que o GA4 mostra menos purchases que o Magento?

Ad blockers, consent recusado, redirecionamentos de pagamento externos e clientes que fecham o navegador antes da success page impedem o evento do navegador. O Measurement Protocol pelo servidor fecha a maior parte da lacuna.

Posso colocar valores de data layer específicos do usuário em páginas em cache?

Não. O HTML em cache é compartilhado entre visitantes. Carregue valores pessoais com JavaScript a partir das customer data sections em vez de renderizá-los no servidor.

Como rastreio o AJAX add to cart no Luma?

Escute o evento jQuery ajax:addToCart no document. O Luma o dispara depois de uma requisição de add to cart bem-sucedida, com o SKU e os IDs do produto.

Por que minhas tags do GTM são bloqueadas no Magento 2.4?

A Content Security Policy bloqueia hosts que não são permitidos. Adicione os hosts do Google ao csp_whitelist.xml em um módulo e teste as páginas de checkout separadamente.

Atendo como freelancer em Magento e Adobe Commerce, incluindo configurações de analytics e tracking. Se precisar de ajuda com isso, fale comigo.

Artigos relacionados