Public Access
feat(bf): gate the Google Ads tag behind a consent banner; fire the buy-click conversion proxy
/odoo-black-friday/ only, page-side. No Odoo change, no ad-account change.
- static/ads-consent.js (new): default-DENY consent; nothing loads and no request
fires until Accept. On Decline (or switching to Decline later) the _gcl_au/_gcl_aw
cookies are cleared. A footer "Cookie settings" link reopens the banner.
- conversion: on submit of either /shop/post_basket buy form (offer block + On the
Rise card), gtag('event','conversion',{send_to: AW-18065522009/vnDUCPDxt7AdENn6p6ZD})
then form.submit() from event_callback, with a 700 ms hard-timeout fallback so a
blocked tag can never swallow the buy. No consent / no tag -> native POST untouched.
- privacy.html: new "Advertising cookies" disclosure (what, why, how to decline).
- Plausible Buy marks and the checkout-side Purchase event untouched.
This commit is contained in:
1 parent
f119f041f0
commit
ae97f4f5eb
3 files changed
+310
-2
No files matched your search
@@ -0,0 +1,165 @@
|
||||
/* ads-consent.js — Google Ads tag, gated behind a consent banner. Page-side only.
|
||||
*
|
||||
* Page: /odoo-black-friday/. Companion to /static/black-friday.js (which is untouched).
|
||||
*
|
||||
* WHY A GATE: the Google Ads tag is NOT cookieless — it sets the advertising cookies
|
||||
* _gcl_au / _gcl_aw. So nothing loads and no request fires until the visitor accepts.
|
||||
* The default is DENY. Declining genuinely prevents the tag from loading.
|
||||
*
|
||||
* WHAT THE CONVERSION IS: a CLICK PROXY, not a sale. It fires when a buy form on this page
|
||||
* is submitted. The real purchase is measured on the checkout host (20.odoo4projects.com)
|
||||
* by odoo-dev. See wiki/concepts/conversion-tracking-contract. Never label it a sale.
|
||||
*
|
||||
* SAFETY: a conversion tag must never swallow a buy. On submit we fire the event and submit
|
||||
* the form from the event callback, with a hard timeout that submits anyway. With JavaScript
|
||||
* off the native POST forms submit untouched (no banner, no conversion, no error).
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var AW_ID = "AW-18065522009";
|
||||
var SEND_TO = "AW-18065522009/vnDUCPDxt7AdENn6p6ZD";
|
||||
var STORE_KEY = "o4p_ads_consent"; // "granted" | "denied"
|
||||
var SUBMIT_FALLBACK_MS = 700; // hard cap: submit even if the tag never answers
|
||||
|
||||
/* ---- the visitor's stored choice ---- */
|
||||
function readChoice() {
|
||||
try {
|
||||
return window.localStorage.getItem(STORE_KEY);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function saveChoice(value) {
|
||||
try {
|
||||
window.localStorage.setItem(STORE_KEY, value);
|
||||
} catch (e) {
|
||||
/* private mode / storage disabled: treat as no choice, banner stays up */
|
||||
}
|
||||
}
|
||||
function isGranted() {
|
||||
return readChoice() === "granted";
|
||||
}
|
||||
|
||||
/* ---- cookie removal (only used to revoke a prior grant) ---- */
|
||||
function clearCookie(name) {
|
||||
var host = window.location.hostname;
|
||||
var bare = host.replace(/^www\./, "");
|
||||
var paths = ["/", "/odoo-black-friday/"];
|
||||
var domains = ["", "; domain=" + host, "; domain=." + bare];
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
for (var j = 0; j < domains.length; j++) {
|
||||
document.cookie =
|
||||
name + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" + paths[i] + domains[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
function clearAdsCookies() {
|
||||
clearCookie("_gcl_au");
|
||||
clearCookie("_gcl_aw");
|
||||
}
|
||||
|
||||
/* ---- the tag: loaded ONLY on consent ---- */
|
||||
function loadGtag() {
|
||||
if (window.__o4pAdsLoaded) return;
|
||||
window.__o4pAdsLoaded = true;
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.gtag =
|
||||
window.gtag ||
|
||||
function () {
|
||||
window.dataLayer.push(arguments);
|
||||
};
|
||||
window.gtag("js", new Date());
|
||||
window.gtag("config", AW_ID);
|
||||
var s = document.createElement("script");
|
||||
s.async = true;
|
||||
s.src = "https://www.googletagmanager.com/gtag/js?id=" + AW_ID;
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
/* ---- the conversion proxy, fired on a buy-form submit ---- */
|
||||
function onSubmit(e) {
|
||||
var form = e.target;
|
||||
if (!form || form.tagName !== "FORM") return;
|
||||
var action = form.getAttribute("action") || "";
|
||||
if (action.indexOf("/shop/post_basket") === -1) return;
|
||||
|
||||
// No consent, or no tag: do nothing, let the native POST go through untouched.
|
||||
if (!isGranted() || typeof window.gtag !== "function") return;
|
||||
|
||||
e.preventDefault();
|
||||
var submitted = false;
|
||||
function submitNow() {
|
||||
if (submitted) return;
|
||||
submitted = true;
|
||||
form.submit(); // bypasses the submit event: no loop, no re-fire
|
||||
}
|
||||
try {
|
||||
window.gtag("event", "conversion", {
|
||||
send_to: SEND_TO,
|
||||
event_callback: submitNow,
|
||||
});
|
||||
} catch (err) {
|
||||
submitNow();
|
||||
}
|
||||
// Hard fallback: a blocked / slow / failed tag must never stop the purchase.
|
||||
window.setTimeout(submitNow, SUBMIT_FALLBACK_MS);
|
||||
}
|
||||
|
||||
/* ---- the banner ---- */
|
||||
function bannerEl() {
|
||||
return document.getElementById("adsConsent");
|
||||
}
|
||||
function showBanner() {
|
||||
var el = bannerEl();
|
||||
if (el) el.hidden = false;
|
||||
}
|
||||
function hideBanner() {
|
||||
var el = bannerEl();
|
||||
if (el) el.hidden = true;
|
||||
}
|
||||
function accept() {
|
||||
saveChoice("granted");
|
||||
loadGtag();
|
||||
hideBanner();
|
||||
}
|
||||
function decline() {
|
||||
saveChoice("denied");
|
||||
clearAdsCookies();
|
||||
hideBanner();
|
||||
}
|
||||
|
||||
function init() {
|
||||
document.addEventListener("submit", onSubmit, false);
|
||||
|
||||
var banner = bannerEl();
|
||||
if (!banner) return;
|
||||
|
||||
var choice = readChoice();
|
||||
if (choice === "granted") {
|
||||
loadGtag(); // returning visitor who already accepted: load straight away
|
||||
} else if (choice !== "denied") {
|
||||
showBanner(); // first visit (or no stored choice): default deny, ask
|
||||
}
|
||||
|
||||
var acceptBtn = document.getElementById("adsConsentAccept");
|
||||
if (acceptBtn) acceptBtn.addEventListener("click", accept);
|
||||
var declineBtn = document.getElementById("adsConsentDecline");
|
||||
if (declineBtn) declineBtn.addEventListener("click", decline);
|
||||
|
||||
// A real way to change the choice later.
|
||||
var opener = document.getElementById("adsConsentOpen");
|
||||
if (opener) {
|
||||
opener.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
showBanner();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
Reference in new issue
Block a user