PageSourceSearch

https://wizfort.com/assets/cslshoes_erp/js/csl-analytics.js?v=20260528a

js wizfort.com collected 2026-09-27 13:40:26 UTC 6,914 bytes, 185 lines download raw bytes

1/* CSL web analytics — GA4 event layer (Session A Phase 2a, 2026-05-25).
2 *
3 * Loaded sitewide via hooks.py `web_include_js`. The GA4 base tag + Consent
4 * Mode v2 defaults are emitted server-side into <head> by
5 * get_web_analytics_head() (utils/website.py); THIS file fires only EVENTS.
6 *
7 * window.cslTrack(event, params): safe wrapper — no-ops if gtag is absent.
8 * (Consent is enforced by gtag/Consent Mode itself, set in the <head> defaults;
9 *  EEA/UK visitors are default-denied until a banner grants — gtag handles that.)
10 *
11 * Conversion events handled here:
12 *  - purchase (RETAIL): landing pages set window.cslPurchaseData = {...}; we fire
13 *    once, dedup by transaction_id (sessionStorage), then strip ?purchased=1 from
14 *    the URL so a refresh / later revisit cannot re-fire.
15 *  - wholesale_order (custom, NEVER purchase): on /me?gap_f_order=ok, fire a
16 *    DISTINCT event (wholesale draft order, no card charged) so wholesale never
17 *    pollutes retail conversion / revenue metrics.
18 *
19 * Phase 2b will add funnel events (view_item, add_to_cart, view_cart,
20 * begin_checkout, add_payment_info) through the same window.cslTrack wrapper.
21 */
22(function () {
23  'use strict';
24
25  var FIRED_KEY = 'csl_ga_fired'; // sessionStorage map of already-fired ids
26
27  function gtagReady() {
28    return typeof window.gtag === 'function';
29  }
30
31  function alreadyFired(id) {
32    if (!id) return false;
33    try {
34      var raw = sessionStorage.getItem(FIRED_KEY);
35      var set = raw ? JSON.parse(raw) : {};
36      return !!set[id];
37    } catch (e) { return false; }
38  }
39
40  function markFired(id) {
41    if (!id) return;
42    try {
43      var raw = sessionStorage.getItem(FIRED_KEY);
44      var set = raw ? JSON.parse(raw) : {};
45      set[id] = 1;
46      sessionStorage.setItem(FIRED_KEY, JSON.stringify(set));
47    } catch (e) { /* private mode / quota — degrade silently */ }
48  }
49
50  // Public safe event wrapper (used here + by Phase 2b funnel events).
51  window.cslTrack = function (event, params) {
52    if (!gtagReady()) return;
53    try { window.gtag('event', event, params || {}); } catch (e) { /* noop */ }
54  };
55
56  // GA4 standard event name -> Meta Pixel standard event name.
57  var GA4_TO_META = {
58    view_item: 'ViewContent',
59    add_to_cart: 'AddToCart',
60    begin_checkout: 'InitiateCheckout',
61    purchase: 'Purchase'
62  };
63
64  // Map our item rows ({item_id,item_name,price,quantity}) -> Meta content_ids.
65  // PII-safe: only product codes leave the page, never customer data.
66  function metaContentIds(items) {
67    if (!Array.isArray(items)) return [];
68    return items.map(function (i) { return i && (i.item_id || i.id); })
69                .filter(Boolean);
70  }
71
72  // Fire a standard ecommerce funnel event to BOTH GA4 and Meta Pixel in one
73  // call. data = { value, currency, items:[{item_id,item_name,price,quantity}] }.
74  // PII-safe by contract: pass product ids / value / currency only — NEVER an
75  // email, name, address, or any card data. Used by the (Phase 2b) funnel hooks
76  // on PDP / cart / checkout. No-ops silently if the tag libs aren't present.
77  window.cslTrackEcommerce = function (ga4Event, data) {
78    data = data || {};
79    var items = Array.isArray(data.items) ? data.items : [];
80    window.cslTrack(ga4Event, {
81      value: Number(data.value) || 0,
82      currency: data.currency || 'USD',
83      items: items
84    });
85    var metaEvent = GA4_TO_META[ga4Event];
86    if (metaEvent && typeof window.fbq === 'function') {
87      try {
88        window.fbq('track', metaEvent, {
89          value: Number(data.value) || 0,
90          currency: data.currency || 'USD',
91          content_type: 'product',
92          content_ids: metaContentIds(items),
93          num_items: items.length
94        });
95      } catch (e) { /* noop */ }
96    }
97  };
98
99  function stripQueryFlag(flag) {
100    try {
101      var url = new URL(window.location.href);
102      if (url.searchParams.has(flag)) {
103        url.searchParams.delete(flag);
104        window.history.replaceState({}, document.title,
105          url.pathname + (url.search || '') + url.hash);
106      }
107    } catch (e) { /* noop */ }
108  }
109
110  // --- Retail purchase: data set inline by the confirmation templates ---------
111  function firePurchase() {
112    var d = window.cslPurchaseData;
113    if (!d || !d.transaction_id) return;
114    if (alreadyFired(d.transaction_id)) { stripQueryFlag('purchased'); return; }
115    window.cslTrack('purchase', {
116      transaction_id: d.transaction_id,
117      value: Number(d.value) || 0,
118      tax: Number(d.tax) || 0,
119      shipping: Number(d.shipping) || 0,
120      currency: d.currency || 'USD',
121      items: Array.isArray(d.items) ? d.items : []
122    });
123    // Google Ads conversion — shares the same gtag; the AW config is emitted in
124    // <head> (get_web_analytics_head). Fires only when both the conversion id
125    // and the purchase label are configured. transaction_id dedups in Ads too.
126    var cfg = window.cslAnalyticsCfg || {};
127    if (cfg.google_ads_id && cfg.google_ads_purchase_label && gtagReady()) {
128      try {
129        window.gtag('event', 'conversion', {
130          send_to: cfg.google_ads_id + '/' + cfg.google_ads_purchase_label,
131          value: Number(d.value) || 0,
132          currency: d.currency || 'USD',
133          transaction_id: d.transaction_id
134        });
135      } catch (e) { /* noop */ }
136    }
137    // Meta Pixel Purchase — PII-safe (product ids + value + currency only).
138    if (typeof window.fbq === 'function') {
139      try {
140        window.fbq('track', 'Purchase', {
141          value: Number(d.value) || 0,
142          currency: d.currency || 'USD',
143          content_type: 'product',
144          content_ids: metaContentIds(Array.isArray(d.items) ? d.items : []),
145          num_items: Array.isArray(d.items) ? d.items.length : 0
146        });
147      } catch (e) { /* noop */ }
148    }
149    markFired(d.transaction_id);
150    stripQueryFlag('purchased'); // belt-and-suspenders: revisit w/o flag won't refire
151  }
152
153  // --- Wholesale order: distinct event, never `purchase` ----------------------
154  function fireWholesaleOrder() {
155    var path = window.location.pathname.replace(/\/+$/, '');
156    if (path !== '/me') return;
157    var params = new URLSearchParams(window.location.search);
158    if (params.get('gap_f_order') !== 'ok') return;
159    var si = params.get('si');
160    var so = params.get('so');
161    if (!si && !so) return;
162    var key = 'wholesale:' + (si || '') + ':' + (so || '');
163    if (alreadyFired(key)) return;
164    var wtotal = parseFloat(params.get('wtotal'));
165    window.cslTrack('wholesale_order', {
166      customer_type: 'wholesale',
167      value: isNaN(wtotal) ? 0 : wtotal,
168      currency: 'USD',
169      sales_invoice: si || '',
170      sales_order: so || ''
171    });
172    markFired(key);
173  }
174
175  function run() {
176    firePurchase();
177    fireWholesaleOrder();
178  }
179
180  if (document.readyState === 'loading') {
181    document.addEventListener('DOMContentLoaded', run);
182  } else {
183    run();
184  }
185})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.