Repository navigation
Head hook and GA4 checkout step events #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
alecritson
wants to merge
3
commits into
main
Choose a base branch
from
kjs-209-checkout-step-analytics
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -101,3 +101,4 @@ jobs: | |
| node-version: "22" | ||
| - run: npm install | ||
| - run: npm run build | ||
| - run: npm test | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
2 changes: 1 addition & 1 deletion
2
resources/dist/build/assets/app-BTCEeZdH.js → resources/dist/build/assets/app-BKHspGPZ.js
Large diffs are not rendered by default.
Oops, something went wrong.
2 changes: 1 addition & 1 deletion
2
...ces/dist/build/assets/express-BuS9V1WX.js → ...ces/dist/build/assets/express-3pZq12Nj.js
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,27 +1,27 @@ | ||
| { | ||
| "_StripeExpress-B8vLxJvM.js": { | ||
| "file": "assets/StripeExpress-B8vLxJvM.js", | ||
| "_StripeExpress-D2omc5gD.js": { | ||
| "file": "assets/StripeExpress-D2omc5gD.js", | ||
| "name": "StripeExpress" | ||
| }, | ||
| "resources/js/app.js": { | ||
| "file": "assets/app-BTCEeZdH.js", | ||
| "file": "assets/app-BKHspGPZ.js", | ||
| "name": "app", | ||
| "src": "resources/js/app.js", | ||
| "isEntry": true, | ||
| "imports": [ | ||
| "_StripeExpress-B8vLxJvM.js" | ||
| "_StripeExpress-D2omc5gD.js" | ||
| ], | ||
| "css": [ | ||
| "assets/app-CpM2wqoC.css" | ||
| ] | ||
| }, | ||
| "resources/js/express.js": { | ||
| "file": "assets/express-BuS9V1WX.js", | ||
| "file": "assets/express-3pZq12Nj.js", | ||
| "name": "express", | ||
| "src": "resources/js/express.js", | ||
| "isEntry": true, | ||
| "imports": [ | ||
| "_StripeExpress-B8vLxJvM.js" | ||
| "_StripeExpress-D2omc5gD.js" | ||
| ] | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| // GA4 checkout events (begin_checkout, add_shipping_info, add_payment_info) | ||
| // for a store running Google Tag Manager. | ||
| // | ||
| // The checkout never loads a tag itself. A host that wants measurement puts | ||
| // its container (and its consent handling) in the head hook, | ||
| // resources/views/vendor/lunar-checkout/partials/head.blade.php, and creates | ||
| // `window.dataLayer` only when the shopper has consented. No data layer, no | ||
| // events: a store without Tag Manager, or a shopper who said no, sends nothing. | ||
|
|
||
| /** Decimal places of a currency's minor unit: 2 for GBP, 0 for JPY. */ | ||
| function currencyDecimals(currency) { | ||
| try { | ||
| return new Intl.NumberFormat('en', { style: 'currency', currency }).resolvedOptions().maximumFractionDigits ?? 2 | ||
| } catch { | ||
| return 2 | ||
| } | ||
| } | ||
|
|
||
| /** Minor units (1299) to GA4's major units (12.99). */ | ||
| export function fromMinor(minor, currency) { | ||
| return Math.round(minor || 0) / 10 ** currencyDecimals(currency) | ||
| } | ||
|
|
||
| /** Checkout lines as GA4 items, priced per unit. */ | ||
| export function analyticsItems(items, currency) { | ||
| return items.map((item) => ({ | ||
| item_id: item.sku ?? String(item.id), | ||
| item_name: item.title ?? '', | ||
| price: fromMinor(item.price, currency), | ||
| quantity: item.qty, | ||
| })) | ||
| } | ||
|
|
||
| function itemsValue(items) { | ||
| const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0) | ||
|
|
||
| // Floating point noise only (0.1 * 3), not real precision. | ||
| return Math.round(total * 1e6) / 1e6 | ||
| } | ||
|
|
||
| /** | ||
| * Push a GA4 e-commerce event, if the host has a data layer. Returns whether | ||
| * it was sent. | ||
| */ | ||
| export function trackCheckoutEvent(event, { items, currency, ...extra }) { | ||
| if (typeof window === 'undefined' || !Array.isArray(window.dataLayer)) return false | ||
|
|
||
| const lines = analyticsItems(items, currency) | ||
|
|
||
| // Clear the previous event's ecommerce object, as Google recommends, so | ||
| // fields don't leak from one event into the next. | ||
| window.dataLayer.push({ ecommerce: null }) | ||
| window.dataLayer.push({ | ||
| event, | ||
| ecommerce: { currency, value: itemsValue(lines), ...extra, items: lines }, | ||
| }) | ||
|
|
||
| return true | ||
| } | ||
|
|
||
| /** Checkout sessions already announced in this tab, so a reload isn't a second start. */ | ||
| const BEGUN_KEY = 'lunar_checkout_begun' | ||
|
|
||
| /** | ||
| * begin_checkout, once per checkout session. Sent from the checkout itself | ||
| * rather than the store's "Checkout" button: the button's request ends in a | ||
| * full page visit, so the store can't tell it succeeded, and every way in | ||
| * (basket, Buy Now, a resumed link) lands here. | ||
| */ | ||
| export function trackCheckoutStart(uuid, payload) { | ||
| if (!uuid) return false | ||
|
|
||
| let begun = [] | ||
|
|
||
| try { | ||
| begun = JSON.parse(window.sessionStorage.getItem(BEGUN_KEY) ?? '[]') | ||
| } catch { | ||
| // Storage blocked or corrupt: send it rather than lose the step. | ||
| } | ||
|
|
||
| if (begun.includes(uuid)) return false | ||
|
|
||
| if (!trackCheckoutEvent('begin_checkout', payload)) return false | ||
|
|
||
| try { | ||
| window.sessionStorage.setItem(BEGUN_KEY, JSON.stringify([...begun, uuid].slice(-20))) | ||
| } catch { | ||
| // As above. | ||
| } | ||
|
|
||
| return true | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| {{-- | ||
| The host's slot in the checkout's <head>, rendered last. Empty here; a | ||
| store fills it by publishing its own copy to | ||
| resources/views/vendor/lunar-checkout/partials/head.blade.php, for a tag | ||
| manager, a consent script or verification meta tags. $page (the Inertia | ||
| page) is in scope. | ||
|
|
||
| The checkout pushes GA4 step events (add_shipping_info, add_payment_info) | ||
| to window.dataLayer when one exists, so a store that wants them creates | ||
| it here, and only once the shopper has consented. | ||
| --}} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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_checkoutis already sent by the storefront (KJS-97, basket and Buy Now). This PR addstrackCheckoutStart, which firesbegin_checkoutagain on checkout load. The README even says the store should not send its own. If the storefront still sends it, GA4 will count twobegin_checkoutevents 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
sessionStoragededupe only protects against reloads within this app. It doesn't protect against the storefront's event.There was a problem hiding this comment.
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_checkoutin neondigital/kjpspares.co.uk@c44afc6d (useAddToCart.test.tschecks it stays out). The checkout owns the event now. I've added it to the PR description's change list.