Skip to content

Head hook and GA4 checkout step events - #5

Open
alecritson wants to merge 3 commits into
mainfrom
kjs-209-checkout-step-analytics
Open

alecritson wants to merge 3 commits into
mainfrom
kjs-209-checkout-step-analytics

Conversation

@alecritson

@alecritson alecritson commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

The checkout renders its own document (lunar-checkout::app), so a store had no way to add Tag Manager to it, and the only checkout event was purchase (confirmation). A store's Checkout or Buy Now request ends in Inertia::location, so the store can't reliably send begin_checkout either.

Changes

  • Head hook: lunar-checkout::partials.head, an empty include at the end of <head>. A store overrides it from resources/views/vendor/lunar-checkout/partials/head.blade.php.
  • GA4 step events pushed to window.dataLayer, with currency, value and items:
    • begin_checkout on first load of the checkout, once per session per tab (tracked in sessionStorage). The store must not send its own; KJP removed its one in kjpspares.co.uk c44afc6d.
    • add_shipping_info with shipping_tier, when a delivery method or collection is chosen. If the server chose it, it is sent at pay instead.
    • add_payment_info with payment_type (the payment method handle, e.g. card), when the customer pays.
    • Each is sent once per tier or method, so a retry isn't counted twice.
  • Consent stays with the host: events go out only when window.dataLayer exists. The host creates it in the head hook once the shopper has consented.
  • Tests: npm test (node:test, no new dependencies) covers the event helper and runs in the existing vite build job. There are two feature tests for the head hook.
  • README section and a rebuilt resources/dist.

Test plan

  • vendor/bin/pest --testsuite checkout (317 passed)
  • npm test
  • phpstan
  • KJP (KJS-209): in GTM preview, a checkout run shows begin_checkout, add_shipping_info, add_payment_info, purchase. With cookies rejected, nothing goes to googletagmanager.com.

Refs KJS-209

🤖 Generated with Claude Code

The checkout renders its own document, so a store had no way to add Tag
Manager to it, and the steps between begin_checkout and purchase sent
nothing.

- lunar-checkout::partials.head: an empty include at the end of the
  head, overridable from resources/views/vendor/lunar-checkout/.
- add_shipping_info (shipping_tier) when a delivery method or collection
  is chosen, or at pay if the server chose it; add_payment_info
  (payment_type) when the customer pays. Each once per tier/method.
- Events go to window.dataLayer only when the host created one, so no
  Tag Manager or no consent means nothing is sent.
- node:test coverage for the event helper (npm test, run in CI).

Refs KJS-209

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A store's "Checkout" or "Buy Now" request ends in Inertia::location, a
full page visit, so its onSuccess never runs and it can't tell the
checkout started. Send begin_checkout from the checkout on first load
instead, once per session per tab (sessionStorage), so every way in
is counted once and only when the checkout really started.

Refs KJS-209

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alecritson

Copy link
Copy Markdown
Contributor Author

Added in 1d9bf9c: the checkout now sends begin_checkout itself, on first load, once per session per tab (tracked in sessionStorage). A store's Checkout or Buy Now request ends in Inertia::location, so the store can't send it reliably (kjpspares.co.uk#155 review). Covered by npm test.

@TheAaronLawrence TheAaronLawrence left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I've left 2 comments: begin_checkout now also fired here; may duplicate storefront's event; payment_type uses the display label, which may be localized or vary.

Additional findings

  • payment_type uses the display label, which may be localized or vary (resources/js/composables/useCheckout.js:349) · Low · Quick check

    trackPayment uses method.label ?? method.handle as payment_type. Labels are display text that can change with locale or configuration, which fragments GA4 reports and makes the dedupe key unstable. Prefer the stable handle, or send the handle as the value and keep the label out of it.

state.method = state.paymentMethods[0]?.handle ?? null

// GA4 begin_checkout for a host running Tag Manager, once per session.
trackCheckoutStart(data.uuid, { items: state.items, currency: state.currency })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

begin_checkout now also fired here; may duplicate storefront's event
Medium · Quick check

The PR description and KJS-209 say begin_checkout is already sent by the storefront (KJS-97, basket and Buy Now). This PR adds trackCheckoutStart, which fires begin_checkout again on checkout load. The README even says the store should not send its own. If the storefront still sends it, GA4 will count two begin_checkout events per checkout and distort funnel numbers. The change is also missing from the PR description's change list.

Suggested fix: either remove it from the package, or confirm that KJS-97's event is removed in KJP and document this in the PR. The sessionStorage dedupe only protects against reloads within this app. It doesn't protect against the storefront's event.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No double count: KJP already stopped sending its own begin_checkout in neondigital/kjpspares.co.uk@c44afc6d (useAddToCart.test.ts checks it stays out). The checkout owns the event now. I've added it to the PR description's change list.

The display label can change with locale or config, which splits GA4
reports and the once-per-method dedupe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alecritson

Copy link
Copy Markdown
Contributor Author

payment_type: fixed in 24e66a0. It now sends the payment method handle (card, on-account) instead of the label. README updated and resources/dist rebuilt. npm test passes.

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