Skip to content

fix(analytics): Attribute Awin affiliate sales even when the server conversion fails - #841

Merged
leomp12 merged 4 commits into
mainfrom
fix/awin-fallback-pixel-cks
Sep 23, 2026
Merged

leomp12 merged 4 commits into
mainfrom
fix/awin-fallback-pixel-cks

Conversation

@vitorrgg

Copy link
Copy Markdown
Member

Why

Awin homologation for Tia Sônia (advertiser 128977) is blocked on the fallback pixel. The S2S conversion is validated and working, but Awin won't launch with S2S only: the sread.img fallback fires without the click id today, so if the S2S call ever misses, attribution falls back to Awin's third-party cookies, which browsers increasingly block. Awin confirmed sread.img accepts cks the same way sread.php does and asked for a testmode=1 firing to validate.

What

  • cks on the fallback pixel (vbeta-app.ts): the awc captured on the click landing is now sent as cks, so the fallback attributes the sale deterministically without depending on cookies.
  • Opt-in test session ?awin_testmode=1 (set-tracking-ids.ts): kept on sessionStorage, flows to /_analytics with the other tracking ids. On such a session:
    • the pixel fires with testmode=1;
    • the server skips the S2S call (send-to-awin.ts) — the Awin conversion REST API has no test flag, any order posted there is a real conversion (this is what forced the store to cancel the previous test order);
    • the pixel always fires, since it becomes the only channel.

Nothing here is temporary. The test flag is scoped to one browser session and is meant to be reused on any future Awin validation without touching code.

Verification

  • ESLint clean on the changed files (the 6 no-console warnings in vbeta-app.ts are pre-existing).
  • tsc --noEmit on packages/storefront and packages/ssr: no errors on the changed files (remaining output is the usual import.meta.env / alias noise outside Astro).
  • No automated test added: none exists for the analytics forwarders and emitAwinFallbackPixel isn't exported. Can be covered in a follow-up by extracting the URL builder.

Rollout

  1. Release → bump @cloudcommerce/* on tiasonia/functions/ssr → deploy.
  2. Click the Awin test affiliate link with &awin_testmode=1, place an order, confirm on DevTools that sread.img carries cks and testmode=1.
  3. Awin validates the fallback and signs off the full install.

🤖 Generated with Claude Code

…onversion fails

Send the captured click id (`awc`) as `cks` on the `sread.img` fallback
pixel, so a sale that misses the S2S call is still attributed
deterministically instead of relying on Awin third-party cookies. Awin
confirmed `sread.img` accepts `cks` the same way `sread.php` does.

Add an opt-in test session, marked with `?awin_testmode=1` and kept on
sessionStorage: the pixel then fires with `testmode=1` and the server
skips the S2S conversion, which has no test flag on the REST API and
would otherwise register a real order. On such a session the pixel
always fires, as it becomes the only tracking channel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vitorrgg

Copy link
Copy Markdown
Member Author

Revisão adversarial

Escopo

A PR deveria: (1) mandar o awc como cks no pixel de fallback sread.img; (2) criar sessão de teste opt-in via ?awin_testmode=1 que faz o pixel disparar com testmode=1 e o servidor pular o S2S. Não deveria alterar o fluxo de produção fora do parâmetro cks, nem o dedup entre pixel e S2S para sessões normais. O diff bate com isso.

Premissas verificadas

  • Os commits anteriores da área (77ae44baf canal, cea8b1bb1 amounts, 1760abca8 awc em cookie) seguem o mesmo padrão: cliente normaliza em set-tracking-ids.ts, servidor valida tipo do body em send-to-awin.ts. A PR segue o padrão.
  • A confirmação "sread.img aceita cks igual ao sread.php" existe no e-mail da Awin de 08/09/2026 (Marcello, Global Integration Analyst). A doc pública do pixel não lista cks, então a confirmação por e-mail é a única fonte.
  • payload.awin_testmode === '1' no servidor é comparação estrita; o valor vem do body mas não chega a log, URL ou request externo.

Achados

Required

  1. Flag de teste não sobrevive nova aba, mas o awc sim (set-tracking-ids.ts:72-77). O awc foi movido para cookie de 30 dias (fix(storefront): Persist awc as a cookie so it survives a new tab #789) justamente porque a Awin homologa o cenário "clique + last click direto em nova aba" (PRD, cenário 2 e a falha registrada no pedido 3043120). O awin_testmode fica só em sessionStorage. Cenário de falha: testador clica no link com awin_testmode=1, abre a loja em nova aba ou o gateway redireciona em nova janela, finaliza o pedido. O cookie entrega o awc, o flag some, e o servidor posta uma conversão real na Conversion API. É exatamente o que a PR quer impedir e o que obrigou o cancelamento do pedido de teste anterior. Sugestão: persistir o flag no mesmo mecanismo do awc (cookie curto, 1 dia) e ler dele quando o sessionStorage estiver vazio. Teste de regressão: getTrackingIds com sessionStorage vazio e cookie presente deve retornar awin_testmode: '1'.

Optional

  1. Log de skip em todo batch (send-to-awin.ts:71-76). O guard vem antes do filtro de purchase, então cada page_view e view_item da sessão de teste gera um logger.info. Mover o if (isTestmode) para depois de purchaseEvents.length.

  2. Não há saída do modo teste. Só fechar a aba limpa o flag. Testador que continua navegando e faz um pedido real na mesma aba perde o S2S. ?awin_testmode=0 removendo a chave resolve.

  3. Zero teste automatizado, como a PR admite. packages/storefront/tests já tem vitest. Extrair buildAwinPixelSrc(trackingIds, orderRef, amount, coupon) e cobrir: cks igual ao awc codificado; testmode=1 só com flag; testmode=0 sem. No servidor, sendToAwin com isTestmode: true e evento purchase não deve chamar o axios.

Nit

  1. Qualquer visitante pode suprimir o S2S do próprio pedido com o parâmetro na URL. Não há ganho para quem faz isso, então aceitável, mas vale uma linha no comentário do código.

Eixos sem achado

  • Regressão: sessão normal com S2S ativo mantém a regra de order_number e shipping; só ganha cks. Sem S2S, comportamento legado intacto.
  • Auth: awc já era tratado; nada novo vaza.
  • Fila/idempotência, integração, aggregation: sem mudança de somatório, retry ou Mongo. Dedup por ref na Awin permanece.

Veredito

APROVAR COM RESSALVAS. O código de produção está correto. O item 1 compromete o objetivo declarado da PR e deve entrar antes do passo 2 do rollout, senão a homologação pode gerar outro pedido real.

Não verificado: ESLint e tsc na branch; se o link de afiliado de teste da Awin ou o gateway da Tia Sônia abre nova aba na prática; comportamento real da Awin com cks no sread.img além da confirmação por e-mail.

leomp12 and others added 3 commits September 23, 2026 10:06
The test mode check now runs after the purchase filter, so a test session
logs once per test order instead of on every page view batch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HYm5krXU4pnuPzynSUMQUg
Drops the duplicated "REST API has no test flag" explanation, kept on
the SSR send-to-awin.ts where the S2S call is skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HYm5krXU4pnuPzynSUMQUg

@leomp12 leomp12 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O caminho da flag está redondo: ?awin_testmode=1 → sessionStorage → trackingIds → /_analytics → skip do S2S, sem whitelist no meio e sem estado dividido entre pixel e servidor (os dois leem o mesmo objeto, gravado na mesma chamada). O cks com o awc bate com o que a Awin usa nas integrações deles, e o guard !trackingIds.awc já garante que ele nunca sai vazio. Pular o S2S em vez de tentar um "modo teste" na API é a escolha certa, dado que ela não tem um.

Empurrei no branch:

  • merge do main (estava 6 commits atrás; só o #842 tocava vbeta-app.ts, em trecho separado);
  • eea68ae — o check de isTestmode em send-to-awin.ts agora vem depois do filtro de purchase, então a sessão de teste loga o skip uma vez por pedido de teste, não a cada lote de page_view/view_item. Comportamento idêntico fora o log;
  • c3f405a — o comentário de set-tracking-ids.ts deixou de repetir a justificativa "API REST sem test flag" e aponta para o send-to-awin.ts, onde a decisão acontece.

🟠 A flag de teste não acompanha o awc numa aba nova

set-tracking-ids.ts:55 grava o awc também em cookie de 30 dias, exatamente porque sessionStorage doesn't survive a manually-opened tab, e :82/:107 o recuperam desse cookie. A analytics_awin_testmode fica só no sessionStorage.

Se quem estiver testando abrir o checkout ou a confirmação numa aba nova (ou retomar a compra em outra janela), essa aba tem o awc via cookie e não tem a flag. O servidor então faz o S2S e o pixel sai com testmode=0: o pedido de teste vira conversão real comissionada — o mesmo cenário que obrigou a loja a cancelar o pedido de teste anterior, que é justamente o que a PR quer evitar.

No fluxo normal da SPA na mesma aba não acontece, então não seguro o merge por isso. Mas como a validação com a Awin é o próximo passo, vale fechar agora: persistir a flag do mesmo jeito que o awc (cookie, com vida igual ou menor — de sessão já resolve), ou, no mínimo, deixar explícito no roteiro do rollout que o teste tem que ser feito do clique ao pedido na mesma aba.

🟢 Minors

  • CodeFactor acusou 2 issues novas; o ESLint local passa limpo nos 4 arquivos, então provavelmente é métrica de complexidade do getTrackingIds. Não bloqueia.
  • O follow-up de teste que você propôs (extrair o builder da URL do pixel) não tem issue ainda; o packages/ssr segue sem nenhum teste, como já apareceu na #811.

Aprovando — o ponto da aba nova fica a seu critério entre corrigir aqui ou só documentar no roteiro de teste.

@leomp12
leomp12 merged commit cb5efb2 into main Sep 23, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants