___TERMS_OF_SERVICE___ By creating or modifying this file you agree to Google Tag Manager's Community Template Gallery Developer Terms of Service available at https://developers.google.com/tag-manager/gallery-tos (or such other URL as Google may provide), as modified from time to time. ___INFO___ { "type": "MACRO", "id": "cvt_temp_public_id", "version": 1, "securityGroups": [], "displayName": "Payment Referral Cleaner", "categories": [ "ANALYTICS", "ATTRIBUTION", "UTILITY" ], "brand": { "id": "brand_dummy", "displayName": "Metric Owl", "thumbnail": "data:image/png;base64,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" }, "description": "Stops Klarna, Clearpay, PayPal, Stripe, 3-D Secure and other payment pages being credited with sales in GA4. Returns true when the visitor is coming back from a payment page, for GA4's ignore_referrer setting, and nothing otherwise.", "containerContexts": [ "WEB" ] } ___TEMPLATE_PARAMETERS___ [ { "type": "CHECKBOX", "name": "matchBankPages", "checkboxText": "Also treat bank verification pages as payment pages", "simpleValueType": true, "defaultValue": false, "help": "Matches hostnames whose first part starts with acs or 3ds, such as acs.bank.example. Check your referral report first: banks use many domains." }, { "type": "TEXT", "name": "extraDomains", "displayName": "Extra payment domains", "simpleValueType": true, "help": "One per line, for example pay.example.com. A full address works too. Subdomains are included.", "lineCount": 4 }, { "type": "GROUP", "name": "advanced", "displayName": "Advanced", "groupStyle": "ZIPPY_CLOSED", "subParams": [ { "type": "TEXT", "name": "referrerOverride", "displayName": "Referrer to check", "simpleValueType": true, "help": "Leave blank to use the page's referrer." } ] } ] ___SANDBOXED_JS_FOR_WEB_TEMPLATE___ const getReferrerUrl = require('getReferrerUrl'); const parseUrl = require('parseUrl'); const makeString = require('makeString'); // The sandbox doesn't guarantee String.trim, so this removes spaces, tabs and carriage returns. const trim = function (value) { const str = makeString(value); let start = 0; let end = str.length; while (start < end && ' \t\r'.indexOf(str.charAt(start)) > -1) start++; while (end > start && ' \t\r'.indexOf(str.charAt(end - 1)) > -1) end--; return str.slice(start, end); }; // A full URL or a bare domain becomes a lowercase hostname without www. const hostOf = function (value) { let v = trim(value || '').toLowerCase(); if (!v) return ''; if (v.indexOf('://') > -1) { const parsed = parseUrl(v); v = parsed ? parsed.hostname : ''; } v = v.split('/')[0].split(':')[0]; if (v.indexOf('www.') === 0) v = v.slice(4); return v; }; // Payment and buy-now-pay-later domains that send customers back to the shop. const DOMAINS = [ 'klarna.com', 'klarnaservices.com', 'clearpay.co.uk', 'afterpay.com', 'paypal.com', 'zip.co', 'laybuy.com', 'stripe.com', 'checkout.com', 'adyen.com', 'worldpay.com', 'opayo.co.uk', 'sagepay.com', 'braintreegateway.com', 'pay.google.com', 'mollie.com' ]; const extra = makeString(data.extraDomains || '').split('\n'); for (let i = 0; i < extra.length; i++) { const d = hostOf(extra[i]); if (d) DOMAINS.push(d); } const host = hostOf(data.referrerOverride || getReferrerUrl()); if (!host) return undefined; let isPayment = false; for (let i = 0; i < DOMAINS.length; i++) { const d = DOMAINS[i]; if (host === d || host.slice(-(d.length + 1)) === '.' + d) { isPayment = true; break; } } // Bank verification (3-D Secure) pages, such as acs.bank.example or 3ds2.bank.example. if (!isPayment && data.matchBankPages) { const first = host.split('.')[0]; if (first.indexOf('acs') === 0 || first.indexOf('3ds') === 0) isPayment = true; } // true tells GA4 not to treat this page's referrer as a traffic source. Anything else: no value, // so the ignore_referrer parameter isn't sent at all. return isPayment ? true : undefined; ___WEB_PERMISSIONS___ [ { "instance": { "key": { "publicId": "get_referrer", "versionId": "1" }, "param": [ { "key": "urlParts", "value": { "type": 1, "string": "any" } }, { "key": "queriesAllowed", "value": { "type": 1, "string": "any" } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true } ] ___TESTS___ scenarios: - name: Ordinary referrers return nothing code: |- mock('getReferrerUrl', 'https://www.google.com/search?q=x'); assertThat(runCode({})).isUndefined(); - name: Klarna, PayPal and Clearpay return true code: |- mock('getReferrerUrl', 'https://pay.klarna.com/eu/hpp/payments/abc'); assertThat(runCode({})).isTrue(); mock('getReferrerUrl', 'https://www.paypal.com/checkoutnow?token=1'); assertThat(runCode({})).isTrue(); mock('getReferrerUrl', 'https://portal.clearpay.co.uk/uk/checkout'); assertThat(runCode({})).isTrue(); - name: No referrer returns nothing code: |- mock('getReferrerUrl', ''); assertThat(runCode({})).isUndefined(); - name: Bank pages only when the setting is on code: |- mock('getReferrerUrl', 'https://acs2.bank.example/3ds/challenge'); assertThat(runCode({matchBankPages: false})).isUndefined(); assertThat(runCode({matchBankPages: true})).isTrue(); - name: Extra domains, as domains or full addresses code: |- mock('getReferrerUrl', 'https://checkout.payments.example/return'); assertThat(runCode({extraDomains: 'www.payments.example'})).isTrue(); assertThat(runCode({extraDomains: ' https://payments.example/pay \r\nother.example'})).isTrue(); - name: Look-alikes don't match code: |- mock('getReferrerUrl', 'https://notpaypal.com/'); assertThat(runCode({})).isUndefined(); mock('getReferrerUrl', 'https://paypal.com.example.net/'); assertThat(runCode({})).isUndefined(); - name: Referrer override is used code: |- mock('getReferrerUrl', 'https://www.google.com/'); assertThat(runCode({referrerOverride: 'https://checkout.stripe.com/c/pay'})).isTrue(); ___NOTES___ Built and maintained by Marc Alexander, Metric Owl Ltd (https://www.metricowl.co.uk). Licensed under the Apache License 2.0.