___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": "TAG", "id": "pixelfly_cod_protection_web", "version": 1, "securityGroups": [], "displayName": "PixelFly COD Protection (Web)", "brand": { "id": "brand_pixelfly", "displayName": "PixelFly", "thumbnail": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAFQUExURfluS/luS/luS/luS/luS/luS/luS/ltSvhtSvlzUfl0U/l0UvluTPl1VPzJvP3f1/3e1v3e1/3Rxvl8XfhtSfl3V/3j3f////7v6/mBY/hsSfmBYvl3VvmAYvhrSPl7W/l6WvmDZf3c1P708f7z8P7z8f3m4PqKbvl4WPl5Wfuum/7q5f7o4v7o4/3j3Pqbg/mEZ/qGafmFaPqSeP3c0/7n4f7m4Py8rPhsSPy6qv729PqLb/hqRvhrR/l/YP7u6vzMv/739fqMcPmAYfzLv/uvnP7r5/7p5P3k3vqagfqRd/7r5vzGufmFZ/mEZvl8XPl7XP3a0f7x7v7y7/3k3funkvuzofl2Vf3i2/7t6fuii/7+/vl2VvuijPlyT/l1U/lyUPutmv3g2fy/sPy/r/7v7PqOc/l+X/y+rv7w7f7x7fuyn/78+/7t6Pl4V//+/rfPao4AAAAGdFJOU/T29/H8+fQOKpMAAAABYktHRBcL1piPAAAACXBIWXMAACxKAAAsSgF3enRNAAAAB3RJTUUH6gEYFA0caUbtKgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyNi0wMS0yNFQyMDoxMzoyOCswMDowMBOfiR4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjYtMDEtMjRUMjA6MTM6MjgrMDA6MDBiwjGiAAAAKHRFWHRkYXRlOnRpbWVzdGFtcAAyMDI2LTAxLTI0VDIwOjEzOjI4KzAwOjAwNdcQfQAAAXFJREFUSMft1dlTgkAcwHG7YCNJSwpdDaU0U9LoxlKs1LTTIi07DDutrPz/30KMmEYwqF6c+j4y8xmW2d8uFkuXmbp7LL2Iqfr+wTcACuQMAxTpxzBswAqMAoAP2uxDww7CMBgZJUnS6YImgdsYAAQBPTIYo4AaqgcA5vX66PEJf2AyOBVS8uEIqgNgmJmORGfY2bn5hcUlpcgyAnQAF5NWs7Iahwk+SaqtWduC9Q0OgalPwOwb0gBCCDS/IbOZZXJbNEtv25ms0s7u3j7LhoAGALgjf0Af+oWjQtCbV/IVBUEIHUMNIO/DSUlaxSlPEUrxs3PpSYzT2+kLGaTUnU6UOxs0Rs8MAJeiKFaKhgFKXF3bbHbGOIA3H/Px64CnpIGD1K1RcHdf5aWqD4/OZk812BY8J18aJWu8u5nrFbQF9XpzIeE4fE/zPLSCDIW09GMgpnPRaKkUbVQoc18CFME9ahXwJVCue6B763fAL+tvgDdnRI6o69eB+AAAAABJRU5ErkJggg==" }, "description": "Hold COD purchases in PixelFly from the web container. Sends order + user data to your first-party /cod/hold endpoint. Prefer the sGTM COD tag when you have a server container — do not fire both.", "categories": [ "ANALYTICS", "CONVERSIONS", "MARKETING" ], "containerContexts": [ "WEB" ] } ___TEMPLATE_PARAMETERS___ [ { "type": "GROUP", "name": "connectionGroup", "displayName": "Connection", "groupStyle": "ZIPPY_OPEN", "subParams": [ { "type": "TEXT", "name": "serverHost", "displayName": "Server hostname / base URL", "simpleValueType": true, "valueValidators": [ { "type": "NON_EMPTY" } ], "help": "First-party host from PixelFly COD Power-up. Example: https://server.bazarei.com (or paste full …/cod/hold URL)." }, { "type": "TEXT", "name": "apiKey", "displayName": "API Key", "simpleValueType": true, "valueValidators": [ { "type": "NON_EMPTY" } ], "help": "Container API key (pf_live_…)." } ] }, { "type": "GROUP", "name": "dataGroup", "displayName": "Order data", "groupStyle": "ZIPPY_OPEN", "subParams": [ { "type": "RADIO", "name": "dataMode", "displayName": "How to read order fields", "radioItems": [ { "value": "auto", "displayValue": "Auto from dataLayer (Recommended)" }, { "value": "manual", "displayValue": "Map fields manually" } ], "simpleValueType": true, "defaultValue": "auto" }, { "type": "TEXT", "name": "orderId", "displayName": "Order ID", "simpleValueType": true, "enablingConditions": [ { "paramName": "dataMode", "paramValue": "manual", "type": "EQUALS" } ] }, { "type": "TEXT", "name": "value", "displayName": "Value", "simpleValueType": true, "enablingConditions": [ { "paramName": "dataMode", "paramValue": "manual", "type": "EQUALS" } ] }, { "type": "TEXT", "name": "currency", "displayName": "Currency", "simpleValueType": true, "enablingConditions": [ { "paramName": "dataMode", "paramValue": "manual", "type": "EQUALS" } ] }, { "type": "TEXT", "name": "email", "displayName": "Email", "simpleValueType": true, "enablingConditions": [ { "paramName": "dataMode", "paramValue": "manual", "type": "EQUALS" } ] }, { "type": "TEXT", "name": "phone", "displayName": "Phone", "simpleValueType": true, "enablingConditions": [ { "paramName": "dataMode", "paramValue": "manual", "type": "EQUALS" } ] }, { "type": "TEXT", "name": "items", "displayName": "Items", "simpleValueType": true, "enablingConditions": [ { "paramName": "dataMode", "paramValue": "manual", "type": "EQUALS" } ] }, { "type": "TEXT", "name": "paymentMethod", "displayName": "Payment method", "simpleValueType": true, "defaultValue": "cod", "help": "Default cod. Use a DLV if you need dynamic values; pair with a COD-only trigger." }, { "type": "SIMPLE_TABLE", "name": "additionalFields", "displayName": "Additional fields to store", "newRowButtonText": "Add field", "simpleTableColumns": [ { "defaultValue": "", "displayName": "Field name", "name": "name", "type": "TEXT" }, { "defaultValue": "", "displayName": "Value", "name": "value", "type": "TEXT" } ], "help": "Extra keys merged into the hold payload (name, city, …)." } ] }, { "type": "GROUP", "name": "optionsGroup", "displayName": "Options", "groupStyle": "ZIPPY_CLOSED", "subParams": [ { "type": "CHECKBOX", "name": "skipIfNotCod", "checkboxText": "Skip if payment_method is not COD", "simpleValueType": true, "defaultValue": true }, { "type": "CHECKBOX", "name": "logToConsole", "checkboxText": "Log in Preview (debug)", "simpleValueType": true, "defaultValue": false } ] } ] ___SANDBOXED_JS_FOR_WEB_TEMPLATE___ const sendPixel = require('sendPixel'); const encodeUriComponent = require('encodeUriComponent'); const getCookieValues = require('getCookieValues'); const getUrl = require('getUrl'); const copyFromDataLayer = require('copyFromDataLayer'); const getType = require('getType'); const JSON = require('JSON'); const logToConsole = require('logToConsole'); const getTimestampMillis = require('getTimestampMillis'); const makeNumber = require('makeNumber'); const makeString = require('makeString'); const TAG = 'PixelFly COD Protection (Web)'; function isEmpty(v) { return v === undefined || v === null || v === ''; } function firstNonEmpty() { for (let i = 0; i < arguments.length; i++) { if (!isEmpty(arguments[i])) return arguments[i]; } return undefined; } function normalizeHost(host) { let h = makeString(host || '').trim(); if (!h) return ''; if (h.indexOf('http://') !== 0 && h.indexOf('https://') !== 0) { h = 'https://' + h; } while (h.length > 0 && h.charAt(h.length - 1) === '/') { h = h.substring(0, h.length - 1); } if (h.indexOf('/cod/hold') !== -1) return h; return h + '/cod/hold'; } function isCodMethod(method) { if (isEmpty(method)) return true; const m = makeString(method).toLowerCase(); return m === 'cod' || m.indexOf('cash_on_delivery') !== -1 || m.indexOf('cash on delivery') !== -1; } function cookieFirst(name) { const vals = getCookieValues(name); if (vals && vals.length) return vals[0]; return undefined; } function gclidFromGoogleCookie() { const aw = cookieFirst('_gcl_aw'); const dc = cookieFirst('_gcl_dc'); const raw = aw || dc; if (!raw) return undefined; const parts = makeString(raw).split('.'); if (parts.length >= 3) return parts.slice(2).join('.'); return undefined; } function gclidFromUrl() { const url = makeString(getUrl() || ''); const match = url.match(/[?&]gclid=([^&]+)/); if (match && match[1]) { try { return decodeURIComponent(match[1]); } catch (e) { return match[1]; } } return undefined; } const holdUrl = normalizeHost(data.serverHost); const apiKey = makeString(data.apiKey || '').trim(); if (!holdUrl || !apiKey) { if (data.logToConsole) logToConsole(TAG + ': missing serverHost or apiKey'); data.gtmOnFailure(); return; } let orderId; let value; let currency; let email; let phone; let items; if (data.dataMode === 'manual') { orderId = firstNonEmpty(data.orderId, copyFromDataLayer('ecommerce.transaction_id'), copyFromDataLayer('transaction_id')); value = firstNonEmpty(data.value, copyFromDataLayer('ecommerce.value')); currency = firstNonEmpty(data.currency, copyFromDataLayer('ecommerce.currency')); email = firstNonEmpty(data.email, copyFromDataLayer('email'), copyFromDataLayer('user_data.email')); phone = firstNonEmpty(data.phone, copyFromDataLayer('phone'), copyFromDataLayer('user_data.phone')); items = firstNonEmpty(data.items, copyFromDataLayer('ecommerce.items')); } else { orderId = firstNonEmpty( copyFromDataLayer('ecommerce.transaction_id'), copyFromDataLayer('transaction_id'), copyFromDataLayer('order_id') ); value = firstNonEmpty(copyFromDataLayer('ecommerce.value'), copyFromDataLayer('value')); currency = firstNonEmpty(copyFromDataLayer('ecommerce.currency'), copyFromDataLayer('currency')); email = firstNonEmpty(copyFromDataLayer('email'), copyFromDataLayer('user_data.email'), copyFromDataLayer('user_data.em')); phone = firstNonEmpty(copyFromDataLayer('phone'), copyFromDataLayer('user_data.phone'), copyFromDataLayer('user_data.ph')); items = firstNonEmpty(copyFromDataLayer('ecommerce.items'), copyFromDataLayer('items')); } const paymentMethod = firstNonEmpty( data.paymentMethod, copyFromDataLayer('payment_method'), copyFromDataLayer('ecommerce.payment_method'), 'cod' ); if (data.skipIfNotCod && !isCodMethod(paymentMethod)) { if (data.logToConsole) logToConsole(TAG + ': skipped non-COD'); data.gtmOnSuccess(); return; } if (isEmpty(orderId)) { if (data.logToConsole) logToConsole(TAG + ': missing order_id'); data.gtmOnFailure(); return; } const fbp = cookieFirst('_fbp'); const fbc = cookieFirst('_fbc'); const gclid = firstNonEmpty( copyFromDataLayer('gclid'), copyFromDataLayer('user_data.gclid'), gclidFromUrl(), gclidFromGoogleCookie() ); const fbclid = firstNonEmpty(copyFromDataLayer('fbclid'), copyFromDataLayer('user_data.fbclid')); const eventTime = makeNumber(getTimestampMillis() / 1000); const payload = { order_id: makeString(orderId), event_id: makeString(orderId), event_name: 'Purchase', payment_method: makeString(paymentMethod || 'cod'), source: 'web_container', event_time: eventTime, event_source_url: getUrl() }; if (!isEmpty(value)) payload.value = makeNumber(value); if (!isEmpty(currency)) payload.currency = makeString(currency); if (!isEmpty(email)) payload.email = makeString(email); if (!isEmpty(phone)) payload.phone = makeString(phone); if (!isEmpty(fbp)) payload.fbp = makeString(fbp); if (!isEmpty(fbc)) payload.fbc = makeString(fbc); if (!isEmpty(gclid)) payload.gclid = makeString(gclid); if (!isEmpty(fbclid)) payload.fbclid = makeString(fbclid); if (!isEmpty(items)) payload.contents = items; const extraRows = data.additionalFields; if (getType(extraRows) === 'array') { for (let i = 0; i < extraRows.length; i++) { const row = extraRows[i]; if (!row) continue; const key = makeString(row.name || '').trim(); if (!key || isEmpty(row.value)) continue; payload[key] = row.value; } } const body = JSON.stringify(payload); const trackingUrl = holdUrl + '?k=' + encodeUriComponent(apiKey) + '&d=' + encodeUriComponent(body); if (data.logToConsole) { logToConsole(TAG + ' GET ' + holdUrl); logToConsole(TAG + ' payload ' + body); } sendPixel(trackingUrl, data.gtmOnSuccess, data.gtmOnFailure); ___WEB_PERMISSIONS___ [ { "instance": { "key": { "publicId": "logging", "versionId": "1" }, "param": [ { "key": "environments", "value": { "type": 1, "string": "debug" } } ] }, "isRequired": true }, { "instance": { "key": { "publicId": "get_cookies", "versionId": "1" }, "param": [ { "key": "cookieAccess", "value": { "type": 1, "string": "specific" } }, { "key": "cookieNames", "value": { "type": 2, "listItem": [ { "type": 1, "string": "_fbp" }, { "type": 1, "string": "_fbc" } ] } } ] }, "isRequired": true }, { "instance": { "key": { "publicId": "get_url", "versionId": "1" }, "param": [ { "key": "urlParts", "value": { "type": 1, "string": "any" } }, { "key": "queriesAllowed", "value": { "type": 1, "string": "any" } } ] }, "isRequired": true }, { "instance": { "key": { "publicId": "read_data_layer", "versionId": "1" }, "param": [ { "key": "keyPatterns", "value": { "type": 2, "listItem": [ { "type": 1, "string": "ecommerce.*" }, { "type": 1, "string": "transaction_id" }, { "type": 1, "string": "order_id" }, { "type": 1, "string": "value" }, { "type": 1, "string": "currency" }, { "type": 1, "string": "email" }, { "type": 1, "string": "phone" }, { "type": 1, "string": "payment_method" }, { "type": 1, "string": "items" }, { "type": 1, "string": "user_data.*" } ] } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true }, { "instance": { "key": { "publicId": "send_pixel", "versionId": "1" }, "param": [ { "key": "allowedUrls", "value": { "type": 1, "string": "any" } } ] }, "isRequired": true } ] ___TESTS___ scenarios: [] ___NOTES___ PixelFly COD Protection (Web) — GTM web container tag Version 1.0.0 Sends COD purchase to {host}/cod/hold via sendPixel GET (?k=&d=), same transport pattern as PixelFly /e. IMPORTANT: - If you already use sGTM "PixelFly COD Protection" tag → do NOT also fire this web tag (duplicate holds). - Recommended with sGTM: web only forwards purchase → sGTM; hold happens in sGTM. - Use this web tag for proxy-only setups, or when you intentionally hold from the browser. Trigger: purchase + payment_method equals cod (or rely on skipIfNotCod). Pause browser Meta/Google/TikTok Purchase tags for COD.