___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