___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": "cvt_temp_public_id", "version": 1, "securityGroups": [], "displayName": "ChurnReturn", "categories": ["PERSONALIZATION", "REMARKETING", "CONVERSIONS"], "brand": { "id": "brand_dummy", "displayName": "ChurnReturn", "thumbnail": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTIiIGZpbGw9IiMwYTU4Y2EiLz48cGF0aCBkPSJNNDQgMjRhMTQgMTQgMCAxIDAgMCAxNiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPjxwYXRoIGQ9Ik0zOCAxOGw2IDYtNiA2IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iNiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PC9zdmc+" }, "description": "Blendet Besuchern im richtigen Moment passende Produkte aus dem eigenen Feed ein (Exit-Intent, Verweildauer, Scrollen). Lädt das ChurnReturn-Snippet, meldet Einwilligungen und löst eigene Trigger aus. Kein Schlüssel nötig, die Website wird an der Domain erkannt.", "containerContexts": [ "WEB" ] } ___TEMPLATE_PARAMETERS___ [ { "type": "SELECT", "name": "action", "displayName": "Aktion", "macrosInSelect": false, "selectItems": [ { "value": "load", "displayValue": "Snippet laden (auf allen Seiten)" }, { "value": "consent", "displayValue": "Einwilligung melden (nach Zustimmung im Consent-Tool)" }, { "value": "trigger", "displayValue": "Eigenen Trigger auslösen (z.B. bei einem Shop-Ereignis)" } ], "simpleValueType": true, "defaultValue": "load", "help": "Für die Grundinstallation reicht ein Tag mit Snippet laden und dem Trigger All Pages. Die anderen beiden Aktionen sind optional." }, { "type": "CHECKBOX", "name": "consentGranted", "checkboxText": "Einwilligung liegt beim Laden bereits vor", "simpleValueType": true, "enablingConditions": [ { "paramName": "action", "paramValue": "load", "type": "EQUALS" } ], "help": "Setzen, wenn dieser Tag erst nach der Zustimmung feuert (z.B. über die Einwilligungseinstellungen des Tags). Das Snippet startet dann direkt im Modus „nach Einwilligung“." }, { "type": "TEXT", "name": "triggerName", "displayName": "Name des Triggers", "simpleValueType": true, "enablingConditions": [ { "paramName": "action", "paramValue": "trigger", "type": "EQUALS" } ], "valueValidators": [ { "type": "NON_EMPTY" }, { "type": "REGEX", "args": ["^[a-z0-9_]{1,24}$"] } ], "help": "Kleinbuchstaben, Ziffern und Unterstrich, z.B. add_to_cart. Der Trigger muss in der ChurnReturn-Verwaltung als eigener Trigger mit diesem Namen angelegt sein.", "valueHint": "add_to_cart" }, { "type": "GROUP", "name": "advanced", "displayName": "Erweitert", "groupStyle": "ZIPPY_CLOSED", "subParams": [ { "type": "CHECKBOX", "name": "debug", "checkboxText": "Meldungen in der Browser-Konsole ausgeben", "simpleValueType": true, "help": "Zeigt in der Konsole, welche Konfiguration geladen wurde und warum ein Trigger feuert oder nicht. Nur zum Testen." } ] } ] ___SANDBOXED_JS_FOR_WEB_TEMPLATE___ const injectScript = require('injectScript'); const callInWindow = require('callInWindow'); const setInWindow = require('setInWindow'); const copyFromWindow = require('copyFromWindow'); const log = require('logToConsole'); const SCRIPT_URL = 'https://churnreturn.com/cr.js'; const action = data.action || 'load'; if (action === 'load') { // Flags, die das Snippet beim Start liest; setInWindow ueberschreibt vorhandene Werte nicht ohne drittes Argument if (data.consentGranted) setInWindow('crConsent', true, true); if (data.debug) setInWindow('crDebug', true, true); injectScript(SCRIPT_URL, data.gtmOnSuccess, data.gtmOnFailure, 'churnreturn'); } else if (action === 'consent') { // Vor dem Snippet: Flag setzen, das Snippet liest es beim Laden. Nach dem Snippet: direkt melden. setInWindow('crConsent', true, true); if (copyFromWindow('ChurnReturn')) callInWindow('ChurnReturn.consent', true); data.gtmOnSuccess(); } else if (action === 'trigger') { if (copyFromWindow('ChurnReturn')) { callInWindow('ChurnReturn.trigger', data.triggerName); data.gtmOnSuccess(); } else { if (data.debug) log('ChurnReturn: Snippet noch nicht geladen, Trigger "' + data.triggerName + '" verworfen'); data.gtmOnFailure(); } } else { data.gtmOnFailure(); } ___WEB_PERMISSIONS___ [ { "instance": { "key": { "publicId": "inject_script", "versionId": "1" }, "param": [ { "key": "urls", "value": { "type": 2, "listItem": [ { "type": 1, "string": "https://churnreturn.com/cr.js" } ] } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true }, { "instance": { "key": { "publicId": "access_globals", "versionId": "1" }, "param": [ { "key": "keys", "value": { "type": 2, "listItem": [ { "type": 3, "mapKey": [ { "type": 1, "string": "key" }, { "type": 1, "string": "read" }, { "type": 1, "string": "write" }, { "type": 1, "string": "execute" } ], "mapValue": [ { "type": 1, "string": "crConsent" }, { "type": 8, "boolean": true }, { "type": 8, "boolean": true }, { "type": 8, "boolean": false } ] }, { "type": 3, "mapKey": [ { "type": 1, "string": "key" }, { "type": 1, "string": "read" }, { "type": 1, "string": "write" }, { "type": 1, "string": "execute" } ], "mapValue": [ { "type": 1, "string": "crDebug" }, { "type": 8, "boolean": true }, { "type": 8, "boolean": true }, { "type": 8, "boolean": false } ] }, { "type": 3, "mapKey": [ { "type": 1, "string": "key" }, { "type": 1, "string": "read" }, { "type": 1, "string": "write" }, { "type": 1, "string": "execute" } ], "mapValue": [ { "type": 1, "string": "ChurnReturn" }, { "type": 8, "boolean": true }, { "type": 8, "boolean": false }, { "type": 8, "boolean": false } ] }, { "type": 3, "mapKey": [ { "type": 1, "string": "key" }, { "type": 1, "string": "read" }, { "type": 1, "string": "write" }, { "type": 1, "string": "execute" } ], "mapValue": [ { "type": 1, "string": "ChurnReturn.consent" }, { "type": 8, "boolean": true }, { "type": 8, "boolean": false }, { "type": 8, "boolean": true } ] }, { "type": 3, "mapKey": [ { "type": 1, "string": "key" }, { "type": 1, "string": "read" }, { "type": 1, "string": "write" }, { "type": 1, "string": "execute" } ], "mapValue": [ { "type": 1, "string": "ChurnReturn.trigger" }, { "type": 8, "boolean": true }, { "type": 8, "boolean": false }, { "type": 8, "boolean": true } ] } ] } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true }, { "instance": { "key": { "publicId": "logging", "versionId": "1" }, "param": [ { "key": "environments", "value": { "type": 1, "string": "debug" } } ] }, "clientAnnotations": { "isEditedByUser": true }, "isRequired": true } ] ___TESTS___ scenarios: - name: Snippet laden ruft injectScript mit der ChurnReturn-URL auf code: |- const mockData = { action: 'load' }; let injected = null; mock('injectScript', (url, onSuccess) => { injected = url; onSuccess(); }); runCode(mockData); assertThat(injected).isEqualTo('https://churnreturn.com/cr.js'); assertApi('gtmOnSuccess').wasCalled(); - name: Snippet laden mit vorliegender Einwilligung setzt crConsent code: |- const mockData = { action: 'load', consentGranted: true }; mock('injectScript', (url, onSuccess) => onSuccess()); runCode(mockData); assertApi('setInWindow').wasCalledWith('crConsent', true, true); - name: Einwilligung melden setzt das Flag und ruft consent, wenn das Snippet da ist code: |- const mockData = { action: 'consent' }; mock('copyFromWindow', (key) => ({ loaded: true })); runCode(mockData); assertApi('setInWindow').wasCalledWith('crConsent', true, true); assertApi('callInWindow').wasCalledWith('ChurnReturn.consent', true); assertApi('gtmOnSuccess').wasCalled(); - name: Eigener Trigger ohne geladenes Snippet schlaegt fehl code: |- const mockData = { action: 'trigger', triggerName: 'add_to_cart' }; mock('copyFromWindow', (key) => undefined); runCode(mockData); assertApi('callInWindow').wasNotCalled(); assertApi('gtmOnFailure').wasCalled(); - name: Eigener Trigger mit geladenem Snippet ruft trigger code: |- const mockData = { action: 'trigger', triggerName: 'add_to_cart' }; mock('copyFromWindow', (key) => ({ loaded: true })); runCode(mockData); assertApi('callInWindow').wasCalledWith('ChurnReturn.trigger', 'add_to_cart'); assertApi('gtmOnSuccess').wasCalled(); ___NOTES___ Created on 2026-09-18. Quelle und Anleitung: https://churnreturn.com/hilfe/einbindung