From 74ea5424f71368fab0a3efeb686664b78a7dd771 Mon Sep 17 00:00:00 2001 From: Shubham Sharma Date: Tue, 4 Nov 2025 12:09:38 +0000 Subject: [PATCH 1/4] feat: Add flexible item ID formatting options This commit introduces a new feature to the Universal Conversions Variable, allowing users to select the format for product IDs sent to various platforms. Previously, the template only used the value from the 'Product ID/SKU' field. This update adds a "Product ID Format" dropdown menu with four options: - Default (uses the original 'Product ID/SKU' field) - Variant ID (uses the new 'Item Variant ID Key' field) - SKU (uses the new 'Item SKU Key' field) - Custom Shopify (builds 'shopify_MARKET_prod_var') To support this, the following new template fields were added: - `idFormatType` (the dropdown) - `keyVariantId` - `keySku` - `marketCode` A new helper function, `getFormattedId(item)`, has been created to contain this new logic. All internal functions (getItems, getContents, getContentIds, etc.) have been updated to use this helper function instead of directly accessing the original `keyId` value. --- template.tpl | 1141 +++----------------------------------------------- 1 file changed, 57 insertions(+), 1084 deletions(-) diff --git a/template.tpl b/template.tpl index 103d314..2fe3de9 100644 --- a/template.tpl +++ b/template.tpl @@ -1,1089 +1,62 @@ -___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": "MACRO", - "id": "cvt_temp_public_id", - "version": 1, - "securityGroups": [], - "displayName": "Universal Conversions Variable", - "categories": ["UTILITY", "CONVERSIONS", "REMARKETING"], - "description": "Generates the desired parameter from an array.\nBy stape.io.", - "containerContexts": [ - "WEB" - ] -} - - -___TEMPLATE_PARAMETERS___ - -[ - { - "type": "SELECT", - "name": "platform", - "displayName": "Platform", - "macrosInSelect": false, - "selectItems": [ - { - "value": "meta", - "displayValue": "Meta Pixel/CAPI" - }, - { - "value": "ga4", - "displayValue": "Google Analytics 4" - }, - { - "value": "tiktok", - "displayValue": "TikTok CAPI" - }, - { - "value": "twitter", - "displayValue": "Twitter CAPI" - }, - { - "value": "microsoft", - "displayValue": "Microsoft Ads" - }, - { - "value": "klaviyo", - "displayValue": "Klaviyo" - }, - { - "value": "snap", - "displayValue": "Snapchat" - }, - { - "value": "gAdsOff", - "displayValue": "Google Ads Offline" - }, - { - "value": "pinterest", - "displayValue": "Pinterest" - }, - { - "value": "rakuten", - "displayValue": "Rakuten" - }, - { - "value": "criteo", - "displayValue": "Criteo" - }, - { - "value": "reddit", - "displayValue": "Reddit" - } - ], - "simpleValueType": true, - "help": "Choose which platform to generate parameter" - }, - { - "type": "RADIO", - "name": "meta_task", - "displayName": "What to return", - "radioItems": [ - { - "value": "contents", - "displayValue": "contents [ {} ]" - }, - { - "value": "ids", - "displayValue": "content_ids [ ]" - }, - { - "value": "name", - "displayValue": "content_name \u0027 \u0027", - "help": "will only return value if there is one object in product array, since content_name parameter is applicable only to single product (type) events" - }, - { - "value": "value", - "displayValue": "value", - "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" - }, - { - "value": "numitems", - "displayValue": "num_items", - "subParams": [] - } - ], - "simpleValueType": true, - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "meta", - "type": "EQUALS" - } - ] - }, - { - "type": "RADIO", - "name": "ga4_task", - "displayName": "What to return", - "radioItems": [ - { - "value": "value", - "displayValue": "value", - "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" - }, - { - "value": "items", - "displayValue": "items", - "help": "" - }, - { - "value": "ids", - "displayValue": "ecomm_prodid", - "help": "for Ads Remarketing" - } - ], - "simpleValueType": true, - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "ga4", - "type": "EQUALS" - } - ] - }, - { - "type": "RADIO", - "name": "rakuten_task", - "displayName": "What to return", - "radioItems": [ - { - "value": "items", - "displayValue": "line_items", - "help": "", - "subParams": [] - } - ], - "simpleValueType": true, - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "rakuten", - "type": "EQUALS" - } - ] - }, - { - "type": "RADIO", - "name": "gAdsOff_task", - "displayName": "What to return", - "radioItems": [ - { - "value": "value", - "displayValue": "value", - "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" - }, - { - "value": "items", - "displayValue": "items" - } - ], - "simpleValueType": true, - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "gAdsOff", - "type": "EQUALS" - } - ] - }, - { - "type": "RADIO", - "name": "klaviyo_task", - "displayName": "What to return", - "radioItems": [ - { - "value": "value", - "displayValue": "value", - "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" - }, - { - "value": "item", - "displayValue": "item", - "help": "for ViewedProduct event" - }, - { - "value": "items", - "displayValue": "items" - } - ], - "simpleValueType": true, - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "klaviyo", - "type": "EQUALS" - } - ] - }, - { - "type": "RADIO", - "name": "microsoft_task", - "displayName": "What to return", - "radioItems": [ - { - "value": "value", - "displayValue": "revenue_value", - "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" - } - ], - "simpleValueType": true, - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "microsoft", - "type": "EQUALS" - } - ] - }, - { - "type": "RADIO", - "name": "tiktok_task", - "displayName": "What to return", - "radioItems": [ - { - "value": "contents", - "displayValue": "contents [ {} ]", - "subParams": [ - { - "type": "SELECT", - "name": "contentType", - "displayName": "content_type parameter", - "macrosInSelect": false, - "selectItems": [ - { - "value": "product", - "displayValue": "product" - }, - { - "value": "product_group", - "displayValue": "product_group" - } + "exportFormatVersion": 2, + "exportTime": "2025-11-04 12:06:21", + "containerVersion": { + "path": "accounts/6007116146/containers/67573214/versions/0", + "accountId": "6007116146", + "containerId": "67573214", + "containerVersionId": "0", + "container": { + "path": "accounts/6007116146/containers/67573214", + "accountId": "6007116146", + "containerId": "67573214", + "name": "velstar.co.uk", + "publicId": "GTM-5CRSQR9", + "usageContext": [ + "WEB" ], - "simpleValueType": true, - "help": "choose either product or product_group as is required by TikTok events API", - "defaultValue": "product", - "enablingConditions": [] - } - ] - }, - { - "value": "name", - "displayValue": "content_name \u0027 \u0027", - "help": "will only return value if there is one object in product array, since content_name parameter is applicable only to single product (type) events" - }, - { - "value": "value", - "displayValue": "value", - "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" - }, - { - "value": "numitems", - "displayValue": "num_items", - "subParams": [] - } - ], - "simpleValueType": true, - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "tiktok", - "type": "EQUALS" - } - ] - }, - { - "type": "RADIO", - "name": "twitter_task", - "displayName": "What to return", - "radioItems": [ - { - "value": "contents", - "displayValue": "contents [{ }]" - }, - { - "value": "value", - "displayValue": "value", - "subParams": [], - "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" - } - ], - "simpleValueType": true, - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "twitter", - "type": "EQUALS" - } - ] - }, - { - "type": "RADIO", - "name": "criteo_task", - "displayName": "What to return", - "radioItems": [ - { - "value": "items", - "displayValue": "items [{ }]" - } - ], - "simpleValueType": true, - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "criteo", - "type": "EQUALS" - } - ] - }, - { - "type": "RADIO", - "name": "pinterest_task", - "displayName": "What to return", - "radioItems": [ - { - "value": "items", - "displayValue": "line_items [{ }]" - }, - { - "value": "value", - "displayValue": "value", - "subParams": [], - "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" - }, - { - "value": "ids", - "displayValue": "content_ids [ ]" - }, - { - "value": "contents", - "displayValue": "contents [{ }]" - } - ], - "simpleValueType": true, - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "pinterest", - "type": "EQUALS" - } - ] - }, - { - "type": "RADIO", - "name": "snap_task", - "displayName": "What to return", - "radioItems": [ - { - "value": "value", - "displayValue": "price", - "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" - }, - { - "value": "ids", - "displayValue": "item_ids" - }, - { - "value": "numitems", - "displayValue": "number_items", - "subParams": [] - } - ], - "simpleValueType": true, - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "snap", - "type": "EQUALS" - } - ] - }, - { - "type": "RADIO", - "name": "reddit_task", - "displayName": "What to return", - "radioItems": [ - { - "value": "value", - "displayValue": "value", - "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" - }, - { - "value": "numitems", - "displayValue": "itemCount", - "subParams": [] - }, - { - "value": "items", - "displayValue": "products" - } - ], - "simpleValueType": true, - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "reddit", - "type": "EQUALS" - } - ], - "help": "As described in the \u003ca href\u003d\"https://business.reddithelp.com/s/article/manual-conversion-events-with-the-reddit-pixel\"\u003eofficial documentation\u003c/a\u003e." - }, - { - "type": "GROUP", - "name": "arrayGroup", - "displayName": "Input Array", - "groupStyle": "ZIPPY_OPEN", - "subParams": [ - { - "type": "TEXT", - "name": "orderItems", - "displayName": "Array of Objects", - "simpleValueType": true, - "help": "[{}] any structured array of item objects", - "valueValidators": [ - { - "type": "NON_EMPTY" - } - ] - } - ] - }, - { - "type": "GROUP", - "name": "keyGroup", - "displayName": "Input Array Keys", - "groupStyle": "ZIPPY_OPEN", - "subParams": [ - { - "type": "TEXT", - "name": "keyId", - "displayName": "Product ID/SKU", - "simpleValueType": true, - "valueValidators": [ - { - "type": "NON_EMPTY" - } - ] - }, - { - "type": "TEXT", - "name": "keyNm", - "displayName": "Product Name", - "simpleValueType": true, - "valueValidators": [ - { - "type": "NON_EMPTY" - } - ] - }, - { - "type": "TEXT", - "name": "keyPr", - "displayName": "Product Price", - "simpleValueType": true, - "valueValidators": [ - { - "type": "NON_EMPTY" - } - ] - }, - { - "type": "TEXT", - "name": "keyQt", - "displayName": "Product Quantity", - "simpleValueType": true, - "valueValidators": [ - { - "type": "NON_EMPTY" - } - ] - }, - { - "type": "TEXT", - "name": "keyCat", - "displayName": "Product Category", - "simpleValueType": true, - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "ga4", - "type": "EQUALS" - }, - { - "paramName": "platform", - "paramValue": "klaviyo", - "type": "EQUALS" - }, - { - "paramName": "platform", - "paramValue": "twitter", - "type": "EQUALS" - }, - { - "paramName": "platform", - "paramValue": "reddit", - "type": "EQUALS" - } - ] - }, - { - "type": "TEXT", - "name": "keyImg", - "displayName": "Product Image URL", - "simpleValueType": true, - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "klaviyo", - "type": "EQUALS" - } - ] - }, - { - "type": "CHECKBOX", - "name": "buildCatTree", - "checkboxText": "Build Category Tree?", - "simpleValueType": true, - "help": "If your items have multiple categories, create a tree in \u0027cat1 \u003e cat2 \u003e cat3\u0027 string format", - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "rakuten", - "type": "EQUALS" - } + "fingerprint": "1742467264542", + "tagManagerUrl": "https://tagmanager.google.com/#/container/accounts/6007116146/containers/67573214/workspaces?apiLink=container", + "features": { + "supportUserPermissions": true, + "supportEnvironments": true, + "supportWorkspaces": true, + "supportGtagConfigs": false, + "supportBuiltInVariables": true, + "supportClients": false, + "supportFolders": true, + "supportTags": true, + "supportTemplates": true, + "supportTriggers": true, + "supportVariables": true, + "supportVersions": true, + "supportZones": true, + "supportTransformations": false + }, + "tagIds": [ + "GTM-5CRSQR9" + ] + }, + "fingerprint": "1762257981409", + "tagManagerUrl": "https://tagmanager.google.com/#/versions/accounts/6007116146/containers/67573214/versions/0?apiLink=version", + "customTemplate": [ + { + "accountId": "6007116146", + "containerId": "67573214", + "templateId": "73", + "name": "Universal Conversions Variable", + "fingerprint": "1762257596435", + "templateData": "___TERMS_OF_SERVICE___\n\nBy creating or modifying this file you agree to Google Tag Manager's Community\nTemplate Gallery Developer Terms of Service available at\nhttps://developers.google.com/tag-manager/gallery-tos (or such other URL as\nGoogle may provide), as modified from time to time.\n\n\n___INFO___\n\n{\n \"type\": \"MACRO\",\n \"id\": \"cvt_WF8HR\",\n \"version\": 1,\n \"displayName\": \"Universal Conversions Variable\",\n \"categories\": [\n \"UTILITY\",\n \"CONVERSIONS\",\n \"REMARKETING\"\n ],\n \"description\": \"Generates the desired parameter from an array.\\nBy stape.io.\",\n \"containerContexts\": [\n \"WEB\"\n ],\n \"securityGroups\": []\n}\n\n\n___TEMPLATE_PARAMETERS___\n\n[\n {\n \"type\": \"SELECT\",\n \"name\": \"platform\",\n \"displayName\": \"Platform\",\n \"macrosInSelect\": false,\n \"selectItems\": [\n {\n \"value\": \"meta\",\n \"displayValue\": \"Meta Pixel/CAPI\"\n },\n {\n \"value\": \"ga4\",\n \"displayValue\": \"Google Analytics 4\"\n },\n {\n \"value\": \"tiktok\",\n \"displayValue\": \"TikTok CAPI\"\n },\n {\n \"value\": \"twitter\",\n \"displayValue\": \"Twitter CAPI\"\n },\n {\n \"value\": \"microsoft\",\n \"displayValue\": \"Microsoft Ads\"\n },\n {\n \"value\": \"klaviyo\",\n \"displayValue\": \"Klaviyo\"\n },\n {\n \"value\": \"snap\",\n \"displayValue\": \"Snapchat\"\n },\n {\n \"value\": \"gAdsOff\",\n \"displayValue\": \"Google Ads Offline\"\n },\n {\n \"value\": \"pinterest\",\n \"displayValue\": \"Pinterest\"\n },\n {\n \"value\": \"rakuten\",\n \"displayValue\": \"Rakuten\"\n },\n {\n \"value\": \"criteo\",\n \"displayValue\": \"Criteo\"\n },\n {\n \"value\": \"reddit\",\n \"displayValue\": \"Reddit\"\n }\n ],\n \"simpleValueType\": true,\n \"help\": \"Choose which platform to generate parameter\"\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"meta_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"contents\",\n \"displayValue\": \"contents [ {} ]\"\n },\n {\n \"value\": \"ids\",\n \"displayValue\": \"content_ids [ ]\"\n },\n {\n \"value\": \"name\",\n \"displayValue\": \"content_name \\u0027 \\u0027\",\n \"help\": \"will only return value if there is one object in product array, since content_name parameter is applicable only to single product (type) events\"\n },\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"numitems\",\n \"displayValue\": \"num_items\",\n \"subParams\": []\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"meta\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"ga4_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"items\",\n \"displayValue\": \"items\",\n \"help\": \"\"\n },\n {\n \"value\": \"ids\",\n \"displayValue\": \"ecomm_prodid\",\n \"help\": \"for Ads Remarketing\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"ga4\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"rakuten_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"items\",\n \"displayValue\": \"line_items\",\n \"help\": \"\",\n \"subParams\": []\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"rakuten\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"gAdsOff_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"items\",\n \"displayValue\": \"items\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"gAdsOff\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"klaviyo_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"item\",\n \"displayValue\": \"item\",\n \"help\": \"for ViewedProduct event\"\n },\n {\n \"value\": \"items\",\n \"displayValue\": \"items\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"klaviyo\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"microsoft_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"value\",\n \"displayValue\": \"revenue_value\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"microsoft\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"tiktok_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"contents\",\n \"displayValue\": \"contents [ {} ]\",\n \"subParams\": [\n {\n \"type\": \"SELECT\",\n \"name\": \"contentType\",\n \"displayName\": \"content_type parameter\",\n \"macrosInSelect\": false,\n \"selectItems\": [\n {\n \"value\": \"product\",\n \"displayValue\": \"product\"\n },\n {\n \"value\": \"product_group\",\n \"displayValue\": \"product_group\"\n }\n ],\n \"simpleValueType\": true,\n \"help\": \"choose either product or product_group as is required by TikTok events API\",\n \"defaultValue\": \"product\",\n \"enablingConditions\": []\n }\n ]\n },\n {\n \"value\": \"name\",\n \"displayValue\": \"content_name \\u0027 \\u0027\",\n \"help\": \"will only return value if there is one object in product array, since content_name parameter is applicable only to single product (type) events\"\n },\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"numitems\",\n \"displayValue\": \"num_items\",\n \"subParams\": []\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"tiktok\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"twitter_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"contents\",\n \"displayValue\": \"contents [{ }]\"\n },\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"subParams\": [],\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"twitter\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"criteo_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"items\",\n \"displayValue\": \"items [{ }]\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"criteo\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"pinterest_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"items\",\n \"displayValue\": \"line_items [{ }]\"\n },\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"subParams\": [],\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"ids\",\n \"displayValue\": \"content_ids [ ]\"\n },\n {\n \"value\": \"contents\",\n \"displayValue\": \"contents [{ }]\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"pinterest\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"snap_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"value\",\n \"displayValue\": \"price\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"ids\",\n \"displayValue\": \"item_ids\"\n },\n {\n \"value\": \"numitems\",\n \"displayValue\": \"number_items\",\n \"subParams\": []\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"snap\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"reddit_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"numitems\",\n \"displayValue\": \"itemCount\",\n \"subParams\": []\n },\n {\n \"value\": \"items\",\n \"displayValue\": \"products\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"reddit\",\n \"type\": \"EQUALS\"\n }\n ],\n \"help\": \"As described in the \\u003ca href\\u003d\\\"https://business.reddithelp.com/s/article/manual-conversion-events-with-the-reddit-pixel\\\"\\u003eofficial documentation\\u003c/a\\u003e.\"\n },\n {\n \"type\": \"GROUP\",\n \"name\": \"arrayGroup\",\n \"displayName\": \"Input Array\",\n \"groupStyle\": \"ZIPPY_OPEN\",\n \"subParams\": [\n {\n \"type\": \"TEXT\",\n \"name\": \"orderItems\",\n \"displayName\": \"Array of Objects\",\n \"simpleValueType\": true,\n \"help\": \"[{}] any structured array of item objects\",\n \"valueValidators\": [\n {\n \"type\": \"NON_EMPTY\"\n }\n ]\n }\n ]\n },\n {\n \"type\": \"GROUP\",\n \"name\": \"keyGroup\",\n \"displayName\": \"Input Array Keys\",\n \"groupStyle\": \"ZIPPY_OPEN\",\n \"subParams\": [\n {\n \"type\": \"TEXT\",\n \"name\": \"keyId\",\n \"displayName\": \"Product ID/SKU\",\n \"simpleValueType\": true,\n \"valueValidators\": [\n {\n \"type\": \"NON_EMPTY\"\n }\n ]\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyNm\",\n \"displayName\": \"Product Name\",\n \"simpleValueType\": true,\n \"valueValidators\": [\n {\n \"type\": \"NON_EMPTY\"\n }\n ]\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyPr\",\n \"displayName\": \"Product Price\",\n \"simpleValueType\": true,\n \"valueValidators\": [\n {\n \"type\": \"NON_EMPTY\"\n }\n ]\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyQt\",\n \"displayName\": \"Product Quantity\",\n \"simpleValueType\": true,\n \"valueValidators\": [\n {\n \"type\": \"NON_EMPTY\"\n }\n ]\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyCat\",\n \"displayName\": \"Product Category\",\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"ga4\",\n \"type\": \"EQUALS\"\n },\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"klaviyo\",\n \"type\": \"EQUALS\"\n },\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"twitter\",\n \"type\": \"EQUALS\"\n },\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"reddit\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyImg\",\n \"displayName\": \"Product Image URL\",\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"klaviyo\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"CHECKBOX\",\n \"name\": \"buildCatTree\",\n \"checkboxText\": \"Build Category Tree?\",\n \"simpleValueType\": true,\n \"help\": \"If your items have multiple categories, create a tree in \\u0027cat1 \\u003e cat2 \\u003e cat3\\u0027 string format\",\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"rakuten\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyCatList\",\n \"displayName\": \"Category parameter keys\",\n \"simpleValueType\": true,\n \"help\": \"coma separated in order of appearance in tree, like: \\u0027cat_key,mykey,custom_var_7\\u0027\",\n \"enablingConditions\": [\n {\n \"paramName\": \"buildCatTree\",\n \"paramValue\": true,\n \"type\": \"EQUALS\"\n }\n ]\n }\n ],\n \"help\": \"keys for corresponding parameters within input array\"\n },\n {\n \"type\": \"GROUP\",\n \"name\": \"rakDiscountGroup\",\n \"displayName\": \"Discount Configuration\",\n \"groupStyle\": \"ZIPPY_OPEN\",\n \"subParams\": [\n {\n \"type\": \"SELECT\",\n \"name\": \"discConfig\",\n \"displayName\": \"Choose discount configuration method\",\n \"macrosInSelect\": false,\n \"selectItems\": [\n {\n \"value\": \"item_level\",\n \"displayValue\": \"From item-level\"\n },\n {\n \"value\": \"order_level\",\n \"displayValue\": \"From order-level\"\n },\n {\n \"value\": \"none\",\n \"displayValue\": \"None\"\n }\n ],\n \"simpleValueType\": true,\n \"defaultValue\": \"none\"\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyDiscItemLevel\",\n \"displayName\": \"Parameter key for item-level discount\",\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"discConfig\",\n \"paramValue\": \"item_level\",\n \"type\": \"EQUALS\"\n }\n ],\n \"help\": \"discount parameter KEY for input array\"\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"discOrderLevel\",\n \"displayName\": \"Order-level discount amount\",\n \"simpleValueType\": true,\n \"help\": \"order-level discount VALUE\",\n \"enablingConditions\": [\n {\n \"paramName\": \"discConfig\",\n \"paramValue\": \"order_level\",\n \"type\": \"EQUALS\"\n }\n ]\n }\n ],\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"rakuten\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"GROUP\",\n \"name\": \"rakTaxGroup\",\n \"displayName\": \"Tax Configuration\",\n \"groupStyle\": \"ZIPPY_OPEN\",\n \"subParams\": [\n {\n \"type\": \"SELECT\",\n \"name\": \"taxPriceConfig\",\n \"displayName\": \"Item price tax application\",\n \"macrosInSelect\": false,\n \"selectItems\": [\n {\n \"value\": \"priceTaxless\",\n \"displayValue\": \"Item price is taxless\"\n },\n {\n \"value\": \"priceDeduct\",\n \"displayValue\": \"Item price needs tax deduction\"\n }\n ],\n \"simpleValueType\": true\n },\n {\n \"type\": \"SELECT\",\n \"name\": \"taxDiscountConfig\",\n \"displayName\": \"Discount tax application\",\n \"macrosInSelect\": false,\n \"selectItems\": [\n {\n \"value\": \"discTaxless\",\n \"displayValue\": \"Discount is taxless\"\n },\n {\n \"value\": \"discDeduct\",\n \"displayValue\": \"Discount needs tax deduction\"\n }\n ],\n \"simpleValueType\": true\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"taxDeductPercent\",\n \"displayName\": \"Tax %\",\n \"simpleValueType\": true,\n \"help\": \"just number, no % symbol\",\n \"valueValidators\": [],\n \"enablingConditions\": [\n {\n \"paramName\": \"taxPriceConfig\",\n \"paramValue\": \"priceDeduct\",\n \"type\": \"EQUALS\"\n },\n {\n \"paramName\": \"taxDiscountConfig\",\n \"paramValue\": \"discDeduct\",\n \"type\": \"EQUALS\"\n }\n ]\n }\n ],\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"rakuten\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"GROUP\",\n \"name\": \"customParamsGroup\",\n \"displayName\": \"Additional/Optional Parameters\",\n \"groupStyle\": \"ZIPPY_OPEN\",\n \"subParams\": [\n {\n \"type\": \"SIMPLE_TABLE\",\n \"name\": \"customParams\",\n \"displayName\": \"\",\n \"simpleTableColumns\": [\n {\n \"defaultValue\": \"\",\n \"displayName\": \"Custom parameter key in your array\",\n \"name\": \"cusKey\",\n \"type\": \"TEXT\"\n },\n {\n \"defaultValue\": \"\",\n \"displayName\": \"Custom parameter name to return\",\n \"name\": \"cusName\",\n \"type\": \"TEXT\"\n }\n ],\n \"newRowButtonText\": \"Add Custom Parameter\",\n \"alwaysInSummary\": false\n }\n ],\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"ga4\",\n \"type\": \"EQUALS\"\n },\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"klaviyo\",\n \"type\": \"EQUALS\"\n },\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"rakuten\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"SELECT\",\n \"name\": \"idFormatType\",\n \"displayName\": \"Product ID Format\",\n \"macrosInSelect\": true,\n \"selectItems\": [\n {\n \"value\": \"default\",\n \"displayValue\": \"Default (use \\u0027Product ID/SKU\\u0027 field)\"\n },\n {\n \"value\": \"variantId\",\n \"displayValue\": \"Variant ID (use \\u0027Item Variant ID\\u0027 field)\"\n },\n {\n \"value\": \"sku\",\n \"displayValue\": \"SKU (use \\u0027Item SKU\\u0027 field)\"\n },\n {\n \"value\": \"customShopify\",\n \"displayValue\": \"Custom Shopify (shopify_MARKET_prod_var)\"\n }\n ],\n \"simpleValueType\": true\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyVariantId\",\n \"displayName\": \"Item Variant ID Key\",\n \"simpleValueType\": true\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keySku\",\n \"displayName\": \"Item SKU Key\",\n \"simpleValueType\": true\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"marketCode\",\n \"displayName\": \"ISO Market Code (for Custom Shopify) e.g., GB, US\",\n \"simpleValueType\": true\n }\n]\n\n\n___SANDBOXED_JS_FOR_WEB_TEMPLATE___\n\nconst makeInteger = require('makeInteger');\nconst makeNumber = require('makeNumber');\nconst makeString = require('makeString');\nconst makeTableMap = require('makeTableMap');\nconst getType = require('getType');\nconst math = require('Math');\nconst Object = require('Object');\n\n/*==============================================================================\n==============================================================================*/\n\n// Original Key fields\nconst keyId = data.keyId;\nconst keyPr = data.keyPr;\nconst keyNm = data.keyNm;\nconst keyQt = data.keyQt;\nconst keyCat = data.keyCat;\nconst keyImg = data.keyImg;\nconst contentType = data.contentType;\nconst taxDeductPercent = toFixed2(makeNumber(data.taxDeductPercent));\nconst keyDisc = data.keyDiscItemLevel;\n\n// --- NEW FIELDS ---\nconst idFormat = data.idFormatType;\nconst keySku = data.keySku;\nconst keyVariantId = data.keyVariantId;\nconst market = data.marketCode || '';\n// --- END NEW FIELDS ---\n\nconst customParamMap = data.customParams\n ? makeTableMap(data.customParams, 'cusKey', 'cusName')\n : {};\nconst keyCatList = data.keyCatList ? data.keyCatList.split(',') : [];\nconst inputArray = data.orderItems;\n\nif (getType(inputArray) === 'array' && inputArray.length) return runTask();\nelse return;\n\n/*==============================================================================\n Vendor related functions\n==============================================================================*/\n\nfunction runTask() {\n const platform = data.platform;\n const dataKeys = Object.keys(data);\n let task;\n\n for (const key of dataKeys) {\n if (endsWith(key, '_task') && data[key]) {\n task = data[key];\n break;\n }\n }\n\n switch (task) {\n case 'contents':\n return getContents(inputArray, platform);\n case 'ids':\n return getContentIds(inputArray);\n case 'name':\n return getContentName(inputArray);\n case 'value':\n return getValue(inputArray);\n case 'numitems':\n return getNumItems(inputArray);\n case 'items':\n return getItems(inputArray, platform);\n case 'item':\n return getItem(inputArray);\n }\n}\n\nfunction getNumItems(arr) {\n const num_items = arr.reduce((acc, curr) => {\n const quantity = curr[keyQt] ? makeInteger(curr[keyQt]) : 1;\n return acc + quantity;\n }, 0);\n return num_items;\n}\n\nfunction getContentName(arr) {\n if (arr.length !== 1) return '';\n return arr[0][keyNm];\n}\n\nfunction getContentIds(arr) {\n // --- MODIFIED ---\n const content_ids = arr.map((item) => getFormattedId(item));\n // --- END MODIFIED ---\n return content_ids;\n}\n\nfunction getItem(arr) {\n let cat = [];\n cat.push(arr[0][keyCat]);\n\n let item = {\n // --- MODIFIED ---\n ProductID: getFormattedId(arr[0]),\n // --- END MODIFIED ---\n ProductName: arr[0][keyNm],\n Price: arr[0][keyPr],\n ImageURL: arr[0][keyImg],\n Categories: cat\n };\n\n return item;\n}\n\nfunction getValue(arr) {\n const value = arr.reduce((acc, curr) => {\n const itemPrice = curr[keyPr] ? makeNumber(curr[keyPr]) : 0;\n const itemQuantity = curr[keyQt];\n return acc + (itemQuantity ? makeInteger(itemQuantity) * itemPrice : itemPrice);\n }, 0);\n return toFixed2(value);\n}\n\nfunction getContents(arr, platform) {\n let contents = [];\n\n for (let i = 0; i < arr.length; i++) {\n let qt = 1;\n if (arr[i][keyQt]) qt = makeNumber(arr[i][keyQt]);\n\n if (platform === 'meta') {\n contents.push({\n // --- MODIFIED ---\n id: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n quantity: qt,\n item_price: arr[i][keyPr]\n });\n }\n\n if (platform === 'tiktok') {\n contents.push({\n // --- MODIFIED ---\n content_id: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n content_type: contentType,\n content_category: arr[i][keyCat],\n content_name: arr[i][keyNm],\n quantity: qt,\n price: arr[i][keyPr] ? makeNumber(arr[i][keyPr]) : 0\n });\n }\n\n if (platform === 'twitter') {\n contents.push({\n // --- MODIFIED ---\n content_id: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n content_name: arr[i][keyNm],\n content_type: arr[i][keyCat],\n num_items: qt,\n content_price: arr[i][keyPr]\n });\n }\n\n if (platform === 'pinterest') {\n contents.push({\n quantity: qt,\n item_price: arr[i][keyPr] ? makeString(arr[i][keyPr]) : '0'\n // Pinterest 'contents' array doesn't use ID\n });\n }\n }\n\n return contents;\n}\n\nfunction getItems(arr, platform) {\n let items = [];\n let totalDiscount = 0;\n if (data.discConfig === 'order_level') {\n totalDiscount = makeNumber(data.discOrderLevel);\n\n if (data.taxDiscountConfig == 'discDeduct') {\n totalDiscount = totalDiscount / (taxDeductPercent / 100 + 1);\n }\n }\n\n for (let i = 0; i < arr.length; i++) {\n let qt = 1;\n if (arr[i][keyQt]) qt = makeInteger(arr[i][keyQt]);\n\n if (platform === 'ga4') {\n let itemObj = {\n // --- MODIFIED ---\n item_id: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n item_name: arr[i][keyNm],\n quantity: qt,\n price: arr[i][keyPr],\n item_category: arr[i][keyCat]\n };\n\n for (let prop in customParamMap) {\n if (customParamMap[prop]) {\n if (prop === 'discountamountqty') itemObj[customParamMap[prop]] = arr[i][prop] * qt;\n else itemObj[customParamMap[prop]] = arr[i][prop];\n }\n }\n\n items.push(itemObj);\n }\n\n if (platform === 'klaviyo') {\n let cat = [];\n cat.push(arr[i][keyCat]);\n\n let itemObj = {\n // --- MODIFIED ---\n ProductID: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n ProductName: arr[i][keyNm],\n Quantity: qt,\n ItemPrice: arr[i][keyPr],\n ImageURL: arr[i][keyImg],\n ProductCategories: cat\n };\n\n for (let prop in customParamMap) {\n if (customParamMap[prop]) {\n itemObj[customParamMap[prop]] = arr[i][prop];\n }\n }\n\n items.push(itemObj);\n }\n\n if (platform === 'criteo') {\n let itemObj = {\n // --- MODIFIED ---\n id: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n quantity: qt,\n price: arr[i][keyPr]\n };\n\n items.push(itemObj);\n }\n\n if (platform === 'gAdsOff') {\n items.push({\n // --- MODIFIED ---\n productId: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n quantity: qt,\n unitPrice: makeNumber(arr[i][keyPr])\n });\n }\n\n if (platform === 'pinterest') {\n items.push({\n // --- MODIFIED ---\n product_id: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n product_name: arr[i][keyNm],\n product_quantity: qt,\n product_price: arr[i][keyPr] ? makeNumber(arr[i][keyPr]) : 0\n });\n }\n\n if (platform === 'reddit') {\n items.push({\n // --- MODIFIED ---\n id: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n category: arr[i][keyCat],\n name: arr[i][keyNm]\n });\n }\n\n if (platform === 'rakuten') {\n //let p = arr[i][keyPr];\n let p = toFixed2(makeNumber(arr[i][keyPr]));\n let price;\n\n if (data.taxPriceConfig === 'priceDeduct' && taxDeductPercent && taxDeductPercent > 0) {\n let tmp = p / (taxDeductPercent / 100 + 1);\n price = (math.round(tmp * 100) / 100) * 100 * qt;\n price = math.round(price * 100) / 100;\n }\n\n if (data.discConfig === 'item_level' && arr[i][keyDisc]) {\n let tmp;\n let disc = makeNumber(arr[i][keyDisc]); //.replace(',','')\n if (data.taxDiscountConfig == 'discDeduct' && taxDeductPercent && taxDeductPercent > 0) {\n disc = disc / (taxDeductPercent / 100 + 1);\n }\n\n totalDiscount = toFixed2(makeNumber(totalDiscount) + toFixed2(disc));\n }\n\n let itemObj = {\n // --- MODIFIED ---\n sku: getFormattedId(arr[i]), // Rakuten uses SKU as the main ID\n // --- END MODIFIED ---\n product_name: arr[i][keyNm],\n quantity: qt,\n amount: price > 0 ? price : toFixed2(p * 100 * qt),\n optional_data: {}\n };\n\n if (keyCatList.length > 0) {\n let catArr = [];\n keyCatList.forEach((element) => catArr.push(arr[i][element]));\n itemObj.optional_data = { category: catArr.join(' > ') };\n }\n\n for (let prop in customParamMap) {\n if (customParamMap[prop]) {\n itemObj.optional_data[customParamMap[prop]] = arr[i][prop];\n }\n }\n\n items.push(itemObj);\n }\n }\n\n if (platform === 'rakuten' && totalDiscount > 0) {\n items.push({\n sku: 'Discount',\n quantity: '0',\n amount: toFixed2((totalDiscount - totalDiscount * 2) * 100),\n product_name: 'Discount'\n });\n }\n\n return items;\n}\n\n/*==============================================================================\n Helpers\n==============================================================================*/\n\n// --- NEW HELPER FUNCTION ---\n// This function builds the ID based on the user's selection\nfunction getFormattedId(item) {\n // 1. Get the default ID (from the 'keyId' field) as a fallback\n const defaultId = item[keyId] ? makeString(item[keyId]) : undefined;\n\n // 2. Check the format dropdown\n switch (idFormat) {\n case 'sku':\n // Use 'keySku' field if available, otherwise fall back to default\n return item[keySku] ? makeString(item[keySku]) : defaultId;\n\n case 'variantId':\n // Use 'keyVariantId' field if available, otherwise fall back to default\n return item[keyVariantId] ? makeString(item[keyVariantId]) : defaultId;\n\n case 'customShopify':\n // Use 'keyId' (as Product) and 'keyVariantId' (as Variant)\n const prodId = item[keyId];\n const varId = item[keyVariantId];\n\n if (prodId && varId) {\n // Build the custom string\n return 'shopify_' + market + '_' + makeString(prodId) + '_' + makeString(varId);\n }\n // If data is missing, fall back to default\n return defaultId;\n \n case 'default':\n default:\n // Return the default ID\n return defaultId;\n }\n}\n// --- END NEW HELPER FUNCTION ---\n\nfunction toFixed2(num) {\n return math.round(num * 100) / 100;\n}\n\nfunction endsWith(str, suffix) {\n return str.indexOf(suffix, str.length - suffix.length) !== -1;\n}\n\n\n___TESTS___\n\nscenarios: []\n\n\n___NOTES___\n\nCreated on 12/10/2021, 11:11:20\n\n\n", + "galleryReference": { + "host": "github.com", + "owner": "stape-io", + "repository": "universal-conversions-variable", + "version": "1effdefd3dceb2231152dab2618a0e350445fa6f", + "isModified": true, + "signature": "169cae7872c2e87e5481cac059bd11096da1cf56a7a4be62343db84172543856", + "galleryTemplateId": "WF8HR" + } + } ] - }, - { - "type": "TEXT", - "name": "keyCatList", - "displayName": "Category parameter keys", - "simpleValueType": true, - "help": "coma separated in order of appearance in tree, like: \u0027cat_key,mykey,custom_var_7\u0027", - "enablingConditions": [ - { - "paramName": "buildCatTree", - "paramValue": true, - "type": "EQUALS" - } - ] - } - ], - "help": "keys for corresponding parameters within input array" - }, - { - "type": "GROUP", - "name": "rakDiscountGroup", - "displayName": "Discount Configuration", - "groupStyle": "ZIPPY_OPEN", - "subParams": [ - { - "type": "SELECT", - "name": "discConfig", - "displayName": "Choose discount configuration method", - "macrosInSelect": false, - "selectItems": [ - { - "value": "item_level", - "displayValue": "From item-level" - }, - { - "value": "order_level", - "displayValue": "From order-level" - }, - { - "value": "none", - "displayValue": "None" - } - ], - "simpleValueType": true, - "defaultValue": "none" - }, - { - "type": "TEXT", - "name": "keyDiscItemLevel", - "displayName": "Parameter key for item-level discount", - "simpleValueType": true, - "enablingConditions": [ - { - "paramName": "discConfig", - "paramValue": "item_level", - "type": "EQUALS" - } - ], - "help": "discount parameter KEY for input array" - }, - { - "type": "TEXT", - "name": "discOrderLevel", - "displayName": "Order-level discount amount", - "simpleValueType": true, - "help": "order-level discount VALUE", - "enablingConditions": [ - { - "paramName": "discConfig", - "paramValue": "order_level", - "type": "EQUALS" - } - ] - } - ], - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "rakuten", - "type": "EQUALS" - } - ] - }, - { - "type": "GROUP", - "name": "rakTaxGroup", - "displayName": "Tax Configuration", - "groupStyle": "ZIPPY_OPEN", - "subParams": [ - { - "type": "SELECT", - "name": "taxPriceConfig", - "displayName": "Item price tax application", - "macrosInSelect": false, - "selectItems": [ - { - "value": "priceTaxless", - "displayValue": "Item price is taxless" - }, - { - "value": "priceDeduct", - "displayValue": "Item price needs tax deduction" - } - ], - "simpleValueType": true - }, - { - "type": "SELECT", - "name": "taxDiscountConfig", - "displayName": "Discount tax application", - "macrosInSelect": false, - "selectItems": [ - { - "value": "discTaxless", - "displayValue": "Discount is taxless" - }, - { - "value": "discDeduct", - "displayValue": "Discount needs tax deduction" - } - ], - "simpleValueType": true - }, - { - "type": "TEXT", - "name": "taxDeductPercent", - "displayName": "Tax %", - "simpleValueType": true, - "help": "just number, no % symbol", - "valueValidators": [], - "enablingConditions": [ - { - "paramName": "taxPriceConfig", - "paramValue": "priceDeduct", - "type": "EQUALS" - }, - { - "paramName": "taxDiscountConfig", - "paramValue": "discDeduct", - "type": "EQUALS" - } - ] - } - ], - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "rakuten", - "type": "EQUALS" - } - ] - }, - { - "type": "GROUP", - "name": "customParamsGroup", - "displayName": "Additional/Optional Parameters", - "groupStyle": "ZIPPY_OPEN", - "subParams": [ - { - "type": "SIMPLE_TABLE", - "name": "customParams", - "displayName": "", - "simpleTableColumns": [ - { - "defaultValue": "", - "displayName": "Custom parameter key in your array", - "name": "cusKey", - "type": "TEXT" - }, - { - "defaultValue": "", - "displayName": "Custom parameter name to return", - "name": "cusName", - "type": "TEXT" - } - ], - "newRowButtonText": "Add Custom Parameter", - "alwaysInSummary": false - } - ], - "enablingConditions": [ - { - "paramName": "platform", - "paramValue": "ga4", - "type": "EQUALS" - }, - { - "paramName": "platform", - "paramValue": "klaviyo", - "type": "EQUALS" - }, - { - "paramName": "platform", - "paramValue": "rakuten", - "type": "EQUALS" - } - ] - } -] - - -___SANDBOXED_JS_FOR_WEB_TEMPLATE___ - -const makeInteger = require('makeInteger'); -const makeNumber = require('makeNumber'); -const makeString = require('makeString'); -const makeTableMap = require('makeTableMap'); -const getType = require('getType'); -const math = require('Math'); -const Object = require('Object'); - -/*============================================================================== -==============================================================================*/ - -const keyId = data.keyId; -const keyPr = data.keyPr; -const keyNm = data.keyNm; -const keyQt = data.keyQt; -const keyCat = data.keyCat; -const keyImg = data.keyImg; -const contentType = data.contentType; -const taxDeductPercent = toFixed2(makeNumber(data.taxDeductPercent)); -const keyDisc = data.keyDiscItemLevel; -const customParamMap = data.customParams - ? makeTableMap(data.customParams, 'cusKey', 'cusName') - : {}; -const keyCatList = data.keyCatList ? data.keyCatList.split(',') : []; -const inputArray = data.orderItems; - -if (getType(inputArray) === 'array' && inputArray.length) return runTask(); -else return; - -/*============================================================================== - Vendor related functions -==============================================================================*/ - -function runTask() { - const platform = data.platform; - const dataKeys = Object.keys(data); - let task; - - for (const key of dataKeys) { - if (endsWith(key, '_task') && data[key]) { - task = data[key]; - break; } - } - - switch (task) { - case 'contents': - return getContents(inputArray, platform); - case 'ids': - return getContentIds(inputArray); - case 'name': - return getContentName(inputArray); - case 'value': - return getValue(inputArray); - case 'numitems': - return getNumItems(inputArray); - case 'items': - return getItems(inputArray, platform); - case 'item': - return getItem(inputArray); - } -} - -function getNumItems(arr) { - const num_items = arr.reduce((acc, curr) => { - const quantity = curr[keyQt] ? makeInteger(curr[keyQt]) : 1; - return acc + quantity; - }, 0); - return num_items; -} - -function getContentName(arr) { - if (arr.length !== 1) return ''; - return arr[0][keyNm]; -} - -function getContentIds(arr) { - const content_ids = arr.map((item) => item[keyId]); - return content_ids; -} - -function getItem(arr) { - let cat = []; - cat.push(arr[0][keyCat]); - - let item = { - ProductID: arr[0][keyId], - ProductName: arr[0][keyNm], - Price: arr[0][keyPr], - ImageURL: arr[0][keyImg], - Categories: cat - }; - - return item; -} - -function getValue(arr) { - const value = arr.reduce((acc, curr) => { - const itemPrice = curr[keyPr] ? makeNumber(curr[keyPr]) : 0; - const itemQuantity = curr[keyQt]; - return acc + (itemQuantity ? makeInteger(itemQuantity) * itemPrice : itemPrice); - }, 0); - return toFixed2(value); -} - -function getContents(arr, platform) { - let contents = []; - - for (let i = 0; i < arr.length; i++) { - let qt = 1; - if (arr[i][keyQt]) qt = makeNumber(arr[i][keyQt]); - - if (platform === 'meta') { - contents.push({ - id: arr[i][keyId], - quantity: qt, - item_price: arr[i][keyPr] - }); - } - - if (platform === 'tiktok') { - contents.push({ - content_id: makeString(arr[i][keyId]), - content_type: contentType, - content_category: arr[i][keyCat], - content_name: arr[i][keyNm], - quantity: qt, - price: arr[i][keyPr] ? makeNumber(arr[i][keyPr]) : 0 - }); - } - - if (platform === 'twitter') { - contents.push({ - content_id: arr[i][keyId], - content_name: arr[i][keyNm], - content_type: arr[i][keyCat], - num_items: qt, - content_price: arr[i][keyPr] - }); - } - - if (platform === 'pinterest') { - contents.push({ - quantity: qt, - item_price: arr[i][keyPr] ? makeString(arr[i][keyPr]) : '0' - }); - } - } - - return contents; -} - -function getItems(arr, platform) { - let items = []; - let totalDiscount = 0; - if (data.discConfig === 'order_level') { - totalDiscount = makeNumber(data.discOrderLevel); - - if (data.taxDiscountConfig == 'discDeduct') { - totalDiscount = totalDiscount / (taxDeductPercent / 100 + 1); - } - } - - for (let i = 0; i < arr.length; i++) { - let qt = 1; - if (arr[i][keyQt]) qt = makeInteger(arr[i][keyQt]); - - if (platform === 'ga4') { - let itemObj = { - item_id: arr[i][keyId], - item_name: arr[i][keyNm], - quantity: qt, - price: arr[i][keyPr], - item_category: arr[i][keyCat] - }; - - for (let prop in customParamMap) { - if (customParamMap[prop]) { - if (prop === 'discountamountqty') itemObj[customParamMap[prop]] = arr[i][prop] * qt; - else itemObj[customParamMap[prop]] = arr[i][prop]; - } - } - - items.push(itemObj); - } - - if (platform === 'klaviyo') { - let cat = []; - cat.push(arr[i][keyCat]); - - let itemObj = { - ProductID: arr[i][keyId], - ProductName: arr[i][keyNm], - Quantity: qt, - ItemPrice: arr[i][keyPr], - ImageURL: arr[i][keyImg], - ProductCategories: cat - }; - - for (let prop in customParamMap) { - if (customParamMap[prop]) { - itemObj[customParamMap[prop]] = arr[i][prop]; - } - } - - items.push(itemObj); - } - - if (platform === 'criteo') { - let itemObj = { - id: arr[i][keyId], - quantity: qt, - price: arr[i][keyPr] - }; - - items.push(itemObj); - } - - if (platform === 'gAdsOff') { - items.push({ - productId: makeString(arr[i][keyId]), - quantity: qt, - unitPrice: makeNumber(arr[i][keyPr]) - }); - } - - if (platform === 'pinterest') { - items.push({ - product_id: arr[i][keyId], - product_name: arr[i][keyNm], - product_quantity: qt, - product_price: arr[i][keyPr] ? makeNumber(arr[i][keyPr]) : 0 - }); - } - - if (platform === 'reddit') { - items.push({ - id: arr[i][keyId], - category: arr[i][keyCat], - name: arr[i][keyNm] - }); - } - - if (platform === 'rakuten') { - //let p = arr[i][keyPr]; - let p = toFixed2(makeNumber(arr[i][keyPr])); - let price; - - if (data.taxPriceConfig === 'priceDeduct' && taxDeductPercent && taxDeductPercent > 0) { - let tmp = p / (taxDeductPercent / 100 + 1); - price = (math.round(tmp * 100) / 100) * 100 * qt; - price = math.round(price * 100) / 100; - } - - if (data.discConfig === 'item_level' && arr[i][keyDisc]) { - let tmp; - let disc = makeNumber(arr[i][keyDisc]); //.replace(',','') - if (data.taxDiscountConfig == 'discDeduct' && taxDeductPercent && taxDeductPercent > 0) { - disc = disc / (taxDeductPercent / 100 + 1); - } - - totalDiscount = toFixed2(makeNumber(totalDiscount) + toFixed2(disc)); - } - - let itemObj = { - sku: arr[i][keyId], - product_name: arr[i][keyNm], - quantity: qt, - amount: price > 0 ? price : toFixed2(p * 100 * qt), - optional_data: {} - }; - - if (keyCatList.length > 0) { - let catArr = []; - keyCatList.forEach((element) => catArr.push(arr[i][element])); - itemObj.optional_data = { category: catArr.join(' > ') }; - } - - for (let prop in customParamMap) { - if (customParamMap[prop]) { - itemObj.optional_data[customParamMap[prop]] = arr[i][prop]; - } - } - - items.push(itemObj); - } - } - - if (platform === 'rakuten' && totalDiscount > 0) { - items.push({ - sku: 'Discount', - quantity: '0', - amount: toFixed2((totalDiscount - totalDiscount * 2) * 100), - product_name: 'Discount' - }); - } - - return items; -} - -/*============================================================================== - Helpers -==============================================================================*/ - -function toFixed2(num) { - return math.round(num * 100) / 100; -} - -function endsWith(str, suffix) { - return str.indexOf(suffix, str.length - suffix.length) !== -1; } - - -___TESTS___ - -scenarios: [] - - -___NOTES___ - -Created on 12/10/2021, 11:11:20 - - From fec3c8d1ad6f2c775cde0ecd61e71fa1c82f8c2b Mon Sep 17 00:00:00 2001 From: Shubham Sharma Date: Tue, 4 Nov 2025 12:16:36 +0000 Subject: [PATCH 2/4] feat: Add flexible item ID formatting options This commit introduces a new feature to the Universal Conversions Variable, allowing users to select the format for product IDs sent to various platforms. Previously, the template only used the value from the 'Product ID/SKU' field. This update adds a "Product ID Format" dropdown menu with four options: - Default (uses the original 'Product ID/SKU' field) - Variant ID (uses the new 'Item Variant ID Key' field) - SKU (uses the new 'Item SKU Key' field) - Custom Shopify (builds 'shopify_MARKET_prod_var') To support this, the following new template fields were added: - `idFormatType` (the dropdown) - `keyVariantId` - `keySku` - `marketCode` A new helper function, `getFormattedId(item)`, has been created to contain this new logic. All internal functions (getItems, getContents, getContentIds, etc.) have been updated to use this helper function instead of directly accessing the original `keyId` value. --- template.js | 94 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 82 insertions(+), 12 deletions(-) diff --git a/template.js b/template.js index 1cec319..faaa586 100644 --- a/template.js +++ b/template.js @@ -9,6 +9,7 @@ const Object = require('Object'); /*============================================================================== ==============================================================================*/ +// Original Key fields const keyId = data.keyId; const keyPr = data.keyPr; const keyNm = data.keyNm; @@ -18,6 +19,14 @@ const keyImg = data.keyImg; const contentType = data.contentType; const taxDeductPercent = toFixed2(makeNumber(data.taxDeductPercent)); const keyDisc = data.keyDiscItemLevel; + +// --- NEW FIELDS --- +const idFormat = data.idFormatType; +const keySku = data.keySku; +const keyVariantId = data.keyVariantId; +const market = data.marketCode || ''; +// --- END NEW FIELDS --- + const customParamMap = data.customParams ? makeTableMap(data.customParams, 'cusKey', 'cusName') : {}; @@ -75,7 +84,9 @@ function getContentName(arr) { } function getContentIds(arr) { - const content_ids = arr.map((item) => item[keyId]); + // --- MODIFIED --- + const content_ids = arr.map((item) => getFormattedId(item)); + // --- END MODIFIED --- return content_ids; } @@ -84,7 +95,9 @@ function getItem(arr) { cat.push(arr[0][keyCat]); let item = { - ProductID: arr[0][keyId], + // --- MODIFIED --- + ProductID: getFormattedId(arr[0]), + // --- END MODIFIED --- ProductName: arr[0][keyNm], Price: arr[0][keyPr], ImageURL: arr[0][keyImg], @@ -112,7 +125,9 @@ function getContents(arr, platform) { if (platform === 'meta') { contents.push({ - id: arr[i][keyId], + // --- MODIFIED --- + id: getFormattedId(arr[i]), + // --- END MODIFIED --- quantity: qt, item_price: arr[i][keyPr] }); @@ -120,7 +135,9 @@ function getContents(arr, platform) { if (platform === 'tiktok') { contents.push({ - content_id: makeString(arr[i][keyId]), + // --- MODIFIED --- + content_id: getFormattedId(arr[i]), + // --- END MODIFIED --- content_type: contentType, content_category: arr[i][keyCat], content_name: arr[i][keyNm], @@ -131,7 +148,9 @@ function getContents(arr, platform) { if (platform === 'twitter') { contents.push({ - content_id: arr[i][keyId], + // --- MODIFIED --- + content_id: getFormattedId(arr[i]), + // --- END MODIFIED --- content_name: arr[i][keyNm], content_type: arr[i][keyCat], num_items: qt, @@ -143,6 +162,7 @@ function getContents(arr, platform) { contents.push({ quantity: qt, item_price: arr[i][keyPr] ? makeString(arr[i][keyPr]) : '0' + // Pinterest 'contents' array doesn't use ID }); } } @@ -167,7 +187,9 @@ function getItems(arr, platform) { if (platform === 'ga4') { let itemObj = { - item_id: arr[i][keyId], + // --- MODIFIED --- + item_id: getFormattedId(arr[i]), + // --- END MODIFIED --- item_name: arr[i][keyNm], quantity: qt, price: arr[i][keyPr], @@ -189,7 +211,9 @@ function getItems(arr, platform) { cat.push(arr[i][keyCat]); let itemObj = { - ProductID: arr[i][keyId], + // --- MODIFIED --- + ProductID: getFormattedId(arr[i]), + // --- END MODIFIED --- ProductName: arr[i][keyNm], Quantity: qt, ItemPrice: arr[i][keyPr], @@ -208,7 +232,9 @@ function getItems(arr, platform) { if (platform === 'criteo') { let itemObj = { - id: arr[i][keyId], + // --- MODIFIED --- + id: getFormattedId(arr[i]), + // --- END MODIFIED --- quantity: qt, price: arr[i][keyPr] }; @@ -218,7 +244,9 @@ function getItems(arr, platform) { if (platform === 'gAdsOff') { items.push({ - productId: makeString(arr[i][keyId]), + // --- MODIFIED --- + productId: getFormattedId(arr[i]), + // --- END MODIFIED --- quantity: qt, unitPrice: makeNumber(arr[i][keyPr]) }); @@ -226,7 +254,9 @@ function getItems(arr, platform) { if (platform === 'pinterest') { items.push({ - product_id: arr[i][keyId], + // --- MODIFIED --- + product_id: getFormattedId(arr[i]), + // --- END MODIFIED --- product_name: arr[i][keyNm], product_quantity: qt, product_price: arr[i][keyPr] ? makeNumber(arr[i][keyPr]) : 0 @@ -235,7 +265,9 @@ function getItems(arr, platform) { if (platform === 'reddit') { items.push({ - id: arr[i][keyId], + // --- MODIFIED --- + id: getFormattedId(arr[i]), + // --- END MODIFIED --- category: arr[i][keyCat], name: arr[i][keyNm] }); @@ -263,7 +295,9 @@ function getItems(arr, platform) { } let itemObj = { - sku: arr[i][keyId], + // --- MODIFIED --- + sku: getFormattedId(arr[i]), // Rakuten uses SKU as the main ID + // --- END MODIFIED --- product_name: arr[i][keyNm], quantity: qt, amount: price > 0 ? price : toFixed2(p * 100 * qt), @@ -302,6 +336,42 @@ function getItems(arr, platform) { Helpers ==============================================================================*/ +// --- NEW HELPER FUNCTION --- +// This function builds the ID based on the user's selection +function getFormattedId(item) { + // 1. Get the default ID (from the 'keyId' field) as a fallback + const defaultId = item[keyId] ? makeString(item[keyId]) : undefined; + + // 2. Check the format dropdown + switch (idFormat) { + case 'sku': + // Use 'keySku' field if available, otherwise fall back to default + return item[keySku] ? makeString(item[keySku]) : defaultId; + + case 'variantId': + // Use 'keyVariantId' field if available, otherwise fall back to default + return item[keyVariantId] ? makeString(item[keyVariantId]) : defaultId; + + case 'customShopify': + // Use 'keyId' (as Product) and 'keyVariantId' (as Variant) + const prodId = item[keyId]; + const varId = item[keyVariantId]; + + if (prodId && varId) { + // Build the custom string + return 'shopify_' + market + '_' + makeString(prodId) + '_' + makeString(varId); + } + // If data is missing, fall back to default + return defaultId; + + case 'default': + default: + // Return the default ID + return defaultId; + } +} +// --- END NEW HELPER FUNCTION --- + function toFixed2(num) { return math.round(num * 100) / 100; } From 91055b68ea4b29f8b724f6d940a0b8316d3d61c9 Mon Sep 17 00:00:00 2001 From: Shubham Sharma Date: Tue, 4 Nov 2025 12:17:25 +0000 Subject: [PATCH 3/4] Add Product ID Formatting section to README Added a section on Product ID formatting with details on various ID options and requirements. --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 014dc62..50fddd6 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,17 @@ The **Universal Conversions Variable for Google Tag Manager Web container** allo - **item count** - etc. +- ### Product ID Formatting + +This template includes advanced options for formatting the product IDs sent to various platforms. + +| Field | Description | +| :--- | :--- | +| **Product ID Format** | A dropdown menu to select which ID to use.
- **Default:** Uses the original `Product ID/SKU` field.
- **Variant ID:** Uses the ID from the `Item Variant ID Key` field.
- **SKU:** Uses the ID from the `Item SKU Key` field.
- **Custom Shopify:** Builds a custom string in the format `shopify_MARKET_prod_var`. | +| **Item Variant ID Key** | **Required for 'Variant ID' & 'Custom Shopify'.** The Data Layer key for your item's variant ID (e.g., `item_variant`). | +| **Item SKU Key** | **Required for 'SKU'.** The Data Layer key for your item's SKU (e.g., `item_sku`). | +| **Market Code** | **Used for 'Custom Shopify'.** The market code (e.g., `GB`, `US`) to be prefixed to the custom ID. | + It currenly supports the following platforms: - Meta Pixel/CAPI - Google Analytics 4 From 4990d6714780d373eb15427f50b56ce626b647f8 Mon Sep 17 00:00:00 2001 From: Shubham Sharma Date: Tue, 4 Nov 2025 15:16:08 +0000 Subject: [PATCH 4/4] Fixed template issue --- template.tpl | 1258 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 1201 insertions(+), 57 deletions(-) diff --git a/template.tpl b/template.tpl index 2fe3de9..68b89b5 100644 --- a/template.tpl +++ b/template.tpl @@ -1,62 +1,1206 @@ +___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___ + { - "exportFormatVersion": 2, - "exportTime": "2025-11-04 12:06:21", - "containerVersion": { - "path": "accounts/6007116146/containers/67573214/versions/0", - "accountId": "6007116146", - "containerId": "67573214", - "containerVersionId": "0", - "container": { - "path": "accounts/6007116146/containers/67573214", - "accountId": "6007116146", - "containerId": "67573214", - "name": "velstar.co.uk", - "publicId": "GTM-5CRSQR9", - "usageContext": [ - "WEB" + "type": "MACRO", + "id": "cvt_WF8HR", + "version": 1, + "displayName": "Universal Conversions Variable", + "categories": [ + "UTILITY", + "CONVERSIONS", + "REMARKETING" + ], + "description": "Generates the desired parameter from an array.\nBy stape.io.", + "containerContexts": [ + "WEB" + ], + "securityGroups": [] +} + + +___TEMPLATE_PARAMETERS___ + +[ + { + "type": "SELECT", + "name": "platform", + "displayName": "Platform", + "macrosInSelect": false, + "selectItems": [ + { + "value": "meta", + "displayValue": "Meta Pixel/CAPI" + }, + { + "value": "ga4", + "displayValue": "Google Analytics 4" + }, + { + "value": "tiktok", + "displayValue": "TikTok CAPI" + }, + { + "value": "twitter", + "displayValue": "Twitter CAPI" + }, + { + "value": "microsoft", + "displayValue": "Microsoft Ads" + }, + { + "value": "klaviyo", + "displayValue": "Klaviyo" + }, + { + "value": "snap", + "displayValue": "Snapchat" + }, + { + "value": "gAdsOff", + "displayValue": "Google Ads Offline" + }, + { + "value": "pinterest", + "displayValue": "Pinterest" + }, + { + "value": "rakuten", + "displayValue": "Rakuten" + }, + { + "value": "criteo", + "displayValue": "Criteo" + }, + { + "value": "reddit", + "displayValue": "Reddit" + } + ], + "simpleValueType": true, + "help": "Choose which platform to generate parameter" + }, + { + "type": "RADIO", + "name": "meta_task", + "displayName": "What to return", + "radioItems": [ + { + "value": "contents", + "displayValue": "contents [ {} ]" + }, + { + "value": "ids", + "displayValue": "content_ids [ ]" + }, + { + "value": "name", + "displayValue": "content_name \u0027 \u0027", + "help": "will only return value if there is one object in product array, since content_name parameter is applicable only to single product (type) events" + }, + { + "value": "value", + "displayValue": "value", + "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" + }, + { + "value": "numitems", + "displayValue": "num_items", + "subParams": [] + } + ], + "simpleValueType": true, + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "meta", + "type": "EQUALS" + } + ] + }, + { + "type": "RADIO", + "name": "ga4_task", + "displayName": "What to return", + "radioItems": [ + { + "value": "value", + "displayValue": "value", + "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" + }, + { + "value": "items", + "displayValue": "items", + "help": "" + }, + { + "value": "ids", + "displayValue": "ecomm_prodid", + "help": "for Ads Remarketing" + } + ], + "simpleValueType": true, + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "ga4", + "type": "EQUALS" + } + ] + }, + { + "type": "RADIO", + "name": "rakuten_task", + "displayName": "What to return", + "radioItems": [ + { + "value": "items", + "displayValue": "line_items", + "help": "", + "subParams": [] + } + ], + "simpleValueType": true, + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "rakuten", + "type": "EQUALS" + } + ] + }, + { + "type": "RADIO", + "name": "gAdsOff_task", + "displayName": "What to return", + "radioItems": [ + { + "value": "value", + "displayValue": "value", + "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" + }, + { + "value": "items", + "displayValue": "items" + } + ], + "simpleValueType": true, + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "gAdsOff", + "type": "EQUALS" + } + ] + }, + { + "type": "RADIO", + "name": "klaviyo_task", + "displayName": "What to return", + "radioItems": [ + { + "value": "value", + "displayValue": "value", + "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" + }, + { + "value": "item", + "displayValue": "item", + "help": "for ViewedProduct event" + }, + { + "value": "items", + "displayValue": "items" + } + ], + "simpleValueType": true, + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "klaviyo", + "type": "EQUALS" + } + ] + }, + { + "type": "RADIO", + "name": "microsoft_task", + "displayName": "What to return", + "radioItems": [ + { + "value": "value", + "displayValue": "revenue_value", + "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" + } + ], + "simpleValueType": true, + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "microsoft", + "type": "EQUALS" + } + ] + }, + { + "type": "RADIO", + "name": "tiktok_task", + "displayName": "What to return", + "radioItems": [ + { + "value": "contents", + "displayValue": "contents [ {} ]", + "subParams": [ + { + "type": "SELECT", + "name": "contentType", + "displayName": "content_type parameter", + "macrosInSelect": false, + "selectItems": [ + { + "value": "product", + "displayValue": "product" + }, + { + "value": "product_group", + "displayValue": "product_group" + } ], - "fingerprint": "1742467264542", - "tagManagerUrl": "https://tagmanager.google.com/#/container/accounts/6007116146/containers/67573214/workspaces?apiLink=container", - "features": { - "supportUserPermissions": true, - "supportEnvironments": true, - "supportWorkspaces": true, - "supportGtagConfigs": false, - "supportBuiltInVariables": true, - "supportClients": false, - "supportFolders": true, - "supportTags": true, - "supportTemplates": true, - "supportTriggers": true, - "supportVariables": true, - "supportVersions": true, - "supportZones": true, - "supportTransformations": false - }, - "tagIds": [ - "GTM-5CRSQR9" - ] - }, - "fingerprint": "1762257981409", - "tagManagerUrl": "https://tagmanager.google.com/#/versions/accounts/6007116146/containers/67573214/versions/0?apiLink=version", - "customTemplate": [ - { - "accountId": "6007116146", - "containerId": "67573214", - "templateId": "73", - "name": "Universal Conversions Variable", - "fingerprint": "1762257596435", - "templateData": "___TERMS_OF_SERVICE___\n\nBy creating or modifying this file you agree to Google Tag Manager's Community\nTemplate Gallery Developer Terms of Service available at\nhttps://developers.google.com/tag-manager/gallery-tos (or such other URL as\nGoogle may provide), as modified from time to time.\n\n\n___INFO___\n\n{\n \"type\": \"MACRO\",\n \"id\": \"cvt_WF8HR\",\n \"version\": 1,\n \"displayName\": \"Universal Conversions Variable\",\n \"categories\": [\n \"UTILITY\",\n \"CONVERSIONS\",\n \"REMARKETING\"\n ],\n \"description\": \"Generates the desired parameter from an array.\\nBy stape.io.\",\n \"containerContexts\": [\n \"WEB\"\n ],\n \"securityGroups\": []\n}\n\n\n___TEMPLATE_PARAMETERS___\n\n[\n {\n \"type\": \"SELECT\",\n \"name\": \"platform\",\n \"displayName\": \"Platform\",\n \"macrosInSelect\": false,\n \"selectItems\": [\n {\n \"value\": \"meta\",\n \"displayValue\": \"Meta Pixel/CAPI\"\n },\n {\n \"value\": \"ga4\",\n \"displayValue\": \"Google Analytics 4\"\n },\n {\n \"value\": \"tiktok\",\n \"displayValue\": \"TikTok CAPI\"\n },\n {\n \"value\": \"twitter\",\n \"displayValue\": \"Twitter CAPI\"\n },\n {\n \"value\": \"microsoft\",\n \"displayValue\": \"Microsoft Ads\"\n },\n {\n \"value\": \"klaviyo\",\n \"displayValue\": \"Klaviyo\"\n },\n {\n \"value\": \"snap\",\n \"displayValue\": \"Snapchat\"\n },\n {\n \"value\": \"gAdsOff\",\n \"displayValue\": \"Google Ads Offline\"\n },\n {\n \"value\": \"pinterest\",\n \"displayValue\": \"Pinterest\"\n },\n {\n \"value\": \"rakuten\",\n \"displayValue\": \"Rakuten\"\n },\n {\n \"value\": \"criteo\",\n \"displayValue\": \"Criteo\"\n },\n {\n \"value\": \"reddit\",\n \"displayValue\": \"Reddit\"\n }\n ],\n \"simpleValueType\": true,\n \"help\": \"Choose which platform to generate parameter\"\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"meta_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"contents\",\n \"displayValue\": \"contents [ {} ]\"\n },\n {\n \"value\": \"ids\",\n \"displayValue\": \"content_ids [ ]\"\n },\n {\n \"value\": \"name\",\n \"displayValue\": \"content_name \\u0027 \\u0027\",\n \"help\": \"will only return value if there is one object in product array, since content_name parameter is applicable only to single product (type) events\"\n },\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"numitems\",\n \"displayValue\": \"num_items\",\n \"subParams\": []\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"meta\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"ga4_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"items\",\n \"displayValue\": \"items\",\n \"help\": \"\"\n },\n {\n \"value\": \"ids\",\n \"displayValue\": \"ecomm_prodid\",\n \"help\": \"for Ads Remarketing\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"ga4\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"rakuten_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"items\",\n \"displayValue\": \"line_items\",\n \"help\": \"\",\n \"subParams\": []\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"rakuten\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"gAdsOff_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"items\",\n \"displayValue\": \"items\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"gAdsOff\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"klaviyo_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"item\",\n \"displayValue\": \"item\",\n \"help\": \"for ViewedProduct event\"\n },\n {\n \"value\": \"items\",\n \"displayValue\": \"items\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"klaviyo\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"microsoft_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"value\",\n \"displayValue\": \"revenue_value\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"microsoft\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"tiktok_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"contents\",\n \"displayValue\": \"contents [ {} ]\",\n \"subParams\": [\n {\n \"type\": \"SELECT\",\n \"name\": \"contentType\",\n \"displayName\": \"content_type parameter\",\n \"macrosInSelect\": false,\n \"selectItems\": [\n {\n \"value\": \"product\",\n \"displayValue\": \"product\"\n },\n {\n \"value\": \"product_group\",\n \"displayValue\": \"product_group\"\n }\n ],\n \"simpleValueType\": true,\n \"help\": \"choose either product or product_group as is required by TikTok events API\",\n \"defaultValue\": \"product\",\n \"enablingConditions\": []\n }\n ]\n },\n {\n \"value\": \"name\",\n \"displayValue\": \"content_name \\u0027 \\u0027\",\n \"help\": \"will only return value if there is one object in product array, since content_name parameter is applicable only to single product (type) events\"\n },\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"numitems\",\n \"displayValue\": \"num_items\",\n \"subParams\": []\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"tiktok\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"twitter_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"contents\",\n \"displayValue\": \"contents [{ }]\"\n },\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"subParams\": [],\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"twitter\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"criteo_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"items\",\n \"displayValue\": \"items [{ }]\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"criteo\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"pinterest_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"items\",\n \"displayValue\": \"line_items [{ }]\"\n },\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"subParams\": [],\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"ids\",\n \"displayValue\": \"content_ids [ ]\"\n },\n {\n \"value\": \"contents\",\n \"displayValue\": \"contents [{ }]\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"pinterest\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"snap_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"value\",\n \"displayValue\": \"price\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"ids\",\n \"displayValue\": \"item_ids\"\n },\n {\n \"value\": \"numitems\",\n \"displayValue\": \"number_items\",\n \"subParams\": []\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"snap\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"RADIO\",\n \"name\": \"reddit_task\",\n \"displayName\": \"What to return\",\n \"radioItems\": [\n {\n \"value\": \"value\",\n \"displayValue\": \"value\",\n \"help\": \"use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events\"\n },\n {\n \"value\": \"numitems\",\n \"displayValue\": \"itemCount\",\n \"subParams\": []\n },\n {\n \"value\": \"items\",\n \"displayValue\": \"products\"\n }\n ],\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"reddit\",\n \"type\": \"EQUALS\"\n }\n ],\n \"help\": \"As described in the \\u003ca href\\u003d\\\"https://business.reddithelp.com/s/article/manual-conversion-events-with-the-reddit-pixel\\\"\\u003eofficial documentation\\u003c/a\\u003e.\"\n },\n {\n \"type\": \"GROUP\",\n \"name\": \"arrayGroup\",\n \"displayName\": \"Input Array\",\n \"groupStyle\": \"ZIPPY_OPEN\",\n \"subParams\": [\n {\n \"type\": \"TEXT\",\n \"name\": \"orderItems\",\n \"displayName\": \"Array of Objects\",\n \"simpleValueType\": true,\n \"help\": \"[{}] any structured array of item objects\",\n \"valueValidators\": [\n {\n \"type\": \"NON_EMPTY\"\n }\n ]\n }\n ]\n },\n {\n \"type\": \"GROUP\",\n \"name\": \"keyGroup\",\n \"displayName\": \"Input Array Keys\",\n \"groupStyle\": \"ZIPPY_OPEN\",\n \"subParams\": [\n {\n \"type\": \"TEXT\",\n \"name\": \"keyId\",\n \"displayName\": \"Product ID/SKU\",\n \"simpleValueType\": true,\n \"valueValidators\": [\n {\n \"type\": \"NON_EMPTY\"\n }\n ]\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyNm\",\n \"displayName\": \"Product Name\",\n \"simpleValueType\": true,\n \"valueValidators\": [\n {\n \"type\": \"NON_EMPTY\"\n }\n ]\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyPr\",\n \"displayName\": \"Product Price\",\n \"simpleValueType\": true,\n \"valueValidators\": [\n {\n \"type\": \"NON_EMPTY\"\n }\n ]\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyQt\",\n \"displayName\": \"Product Quantity\",\n \"simpleValueType\": true,\n \"valueValidators\": [\n {\n \"type\": \"NON_EMPTY\"\n }\n ]\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyCat\",\n \"displayName\": \"Product Category\",\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"ga4\",\n \"type\": \"EQUALS\"\n },\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"klaviyo\",\n \"type\": \"EQUALS\"\n },\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"twitter\",\n \"type\": \"EQUALS\"\n },\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"reddit\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyImg\",\n \"displayName\": \"Product Image URL\",\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"klaviyo\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"CHECKBOX\",\n \"name\": \"buildCatTree\",\n \"checkboxText\": \"Build Category Tree?\",\n \"simpleValueType\": true,\n \"help\": \"If your items have multiple categories, create a tree in \\u0027cat1 \\u003e cat2 \\u003e cat3\\u0027 string format\",\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"rakuten\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyCatList\",\n \"displayName\": \"Category parameter keys\",\n \"simpleValueType\": true,\n \"help\": \"coma separated in order of appearance in tree, like: \\u0027cat_key,mykey,custom_var_7\\u0027\",\n \"enablingConditions\": [\n {\n \"paramName\": \"buildCatTree\",\n \"paramValue\": true,\n \"type\": \"EQUALS\"\n }\n ]\n }\n ],\n \"help\": \"keys for corresponding parameters within input array\"\n },\n {\n \"type\": \"GROUP\",\n \"name\": \"rakDiscountGroup\",\n \"displayName\": \"Discount Configuration\",\n \"groupStyle\": \"ZIPPY_OPEN\",\n \"subParams\": [\n {\n \"type\": \"SELECT\",\n \"name\": \"discConfig\",\n \"displayName\": \"Choose discount configuration method\",\n \"macrosInSelect\": false,\n \"selectItems\": [\n {\n \"value\": \"item_level\",\n \"displayValue\": \"From item-level\"\n },\n {\n \"value\": \"order_level\",\n \"displayValue\": \"From order-level\"\n },\n {\n \"value\": \"none\",\n \"displayValue\": \"None\"\n }\n ],\n \"simpleValueType\": true,\n \"defaultValue\": \"none\"\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyDiscItemLevel\",\n \"displayName\": \"Parameter key for item-level discount\",\n \"simpleValueType\": true,\n \"enablingConditions\": [\n {\n \"paramName\": \"discConfig\",\n \"paramValue\": \"item_level\",\n \"type\": \"EQUALS\"\n }\n ],\n \"help\": \"discount parameter KEY for input array\"\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"discOrderLevel\",\n \"displayName\": \"Order-level discount amount\",\n \"simpleValueType\": true,\n \"help\": \"order-level discount VALUE\",\n \"enablingConditions\": [\n {\n \"paramName\": \"discConfig\",\n \"paramValue\": \"order_level\",\n \"type\": \"EQUALS\"\n }\n ]\n }\n ],\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"rakuten\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"GROUP\",\n \"name\": \"rakTaxGroup\",\n \"displayName\": \"Tax Configuration\",\n \"groupStyle\": \"ZIPPY_OPEN\",\n \"subParams\": [\n {\n \"type\": \"SELECT\",\n \"name\": \"taxPriceConfig\",\n \"displayName\": \"Item price tax application\",\n \"macrosInSelect\": false,\n \"selectItems\": [\n {\n \"value\": \"priceTaxless\",\n \"displayValue\": \"Item price is taxless\"\n },\n {\n \"value\": \"priceDeduct\",\n \"displayValue\": \"Item price needs tax deduction\"\n }\n ],\n \"simpleValueType\": true\n },\n {\n \"type\": \"SELECT\",\n \"name\": \"taxDiscountConfig\",\n \"displayName\": \"Discount tax application\",\n \"macrosInSelect\": false,\n \"selectItems\": [\n {\n \"value\": \"discTaxless\",\n \"displayValue\": \"Discount is taxless\"\n },\n {\n \"value\": \"discDeduct\",\n \"displayValue\": \"Discount needs tax deduction\"\n }\n ],\n \"simpleValueType\": true\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"taxDeductPercent\",\n \"displayName\": \"Tax %\",\n \"simpleValueType\": true,\n \"help\": \"just number, no % symbol\",\n \"valueValidators\": [],\n \"enablingConditions\": [\n {\n \"paramName\": \"taxPriceConfig\",\n \"paramValue\": \"priceDeduct\",\n \"type\": \"EQUALS\"\n },\n {\n \"paramName\": \"taxDiscountConfig\",\n \"paramValue\": \"discDeduct\",\n \"type\": \"EQUALS\"\n }\n ]\n }\n ],\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"rakuten\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"GROUP\",\n \"name\": \"customParamsGroup\",\n \"displayName\": \"Additional/Optional Parameters\",\n \"groupStyle\": \"ZIPPY_OPEN\",\n \"subParams\": [\n {\n \"type\": \"SIMPLE_TABLE\",\n \"name\": \"customParams\",\n \"displayName\": \"\",\n \"simpleTableColumns\": [\n {\n \"defaultValue\": \"\",\n \"displayName\": \"Custom parameter key in your array\",\n \"name\": \"cusKey\",\n \"type\": \"TEXT\"\n },\n {\n \"defaultValue\": \"\",\n \"displayName\": \"Custom parameter name to return\",\n \"name\": \"cusName\",\n \"type\": \"TEXT\"\n }\n ],\n \"newRowButtonText\": \"Add Custom Parameter\",\n \"alwaysInSummary\": false\n }\n ],\n \"enablingConditions\": [\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"ga4\",\n \"type\": \"EQUALS\"\n },\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"klaviyo\",\n \"type\": \"EQUALS\"\n },\n {\n \"paramName\": \"platform\",\n \"paramValue\": \"rakuten\",\n \"type\": \"EQUALS\"\n }\n ]\n },\n {\n \"type\": \"SELECT\",\n \"name\": \"idFormatType\",\n \"displayName\": \"Product ID Format\",\n \"macrosInSelect\": true,\n \"selectItems\": [\n {\n \"value\": \"default\",\n \"displayValue\": \"Default (use \\u0027Product ID/SKU\\u0027 field)\"\n },\n {\n \"value\": \"variantId\",\n \"displayValue\": \"Variant ID (use \\u0027Item Variant ID\\u0027 field)\"\n },\n {\n \"value\": \"sku\",\n \"displayValue\": \"SKU (use \\u0027Item SKU\\u0027 field)\"\n },\n {\n \"value\": \"customShopify\",\n \"displayValue\": \"Custom Shopify (shopify_MARKET_prod_var)\"\n }\n ],\n \"simpleValueType\": true\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keyVariantId\",\n \"displayName\": \"Item Variant ID Key\",\n \"simpleValueType\": true\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"keySku\",\n \"displayName\": \"Item SKU Key\",\n \"simpleValueType\": true\n },\n {\n \"type\": \"TEXT\",\n \"name\": \"marketCode\",\n \"displayName\": \"ISO Market Code (for Custom Shopify) e.g., GB, US\",\n \"simpleValueType\": true\n }\n]\n\n\n___SANDBOXED_JS_FOR_WEB_TEMPLATE___\n\nconst makeInteger = require('makeInteger');\nconst makeNumber = require('makeNumber');\nconst makeString = require('makeString');\nconst makeTableMap = require('makeTableMap');\nconst getType = require('getType');\nconst math = require('Math');\nconst Object = require('Object');\n\n/*==============================================================================\n==============================================================================*/\n\n// Original Key fields\nconst keyId = data.keyId;\nconst keyPr = data.keyPr;\nconst keyNm = data.keyNm;\nconst keyQt = data.keyQt;\nconst keyCat = data.keyCat;\nconst keyImg = data.keyImg;\nconst contentType = data.contentType;\nconst taxDeductPercent = toFixed2(makeNumber(data.taxDeductPercent));\nconst keyDisc = data.keyDiscItemLevel;\n\n// --- NEW FIELDS ---\nconst idFormat = data.idFormatType;\nconst keySku = data.keySku;\nconst keyVariantId = data.keyVariantId;\nconst market = data.marketCode || '';\n// --- END NEW FIELDS ---\n\nconst customParamMap = data.customParams\n ? makeTableMap(data.customParams, 'cusKey', 'cusName')\n : {};\nconst keyCatList = data.keyCatList ? data.keyCatList.split(',') : [];\nconst inputArray = data.orderItems;\n\nif (getType(inputArray) === 'array' && inputArray.length) return runTask();\nelse return;\n\n/*==============================================================================\n Vendor related functions\n==============================================================================*/\n\nfunction runTask() {\n const platform = data.platform;\n const dataKeys = Object.keys(data);\n let task;\n\n for (const key of dataKeys) {\n if (endsWith(key, '_task') && data[key]) {\n task = data[key];\n break;\n }\n }\n\n switch (task) {\n case 'contents':\n return getContents(inputArray, platform);\n case 'ids':\n return getContentIds(inputArray);\n case 'name':\n return getContentName(inputArray);\n case 'value':\n return getValue(inputArray);\n case 'numitems':\n return getNumItems(inputArray);\n case 'items':\n return getItems(inputArray, platform);\n case 'item':\n return getItem(inputArray);\n }\n}\n\nfunction getNumItems(arr) {\n const num_items = arr.reduce((acc, curr) => {\n const quantity = curr[keyQt] ? makeInteger(curr[keyQt]) : 1;\n return acc + quantity;\n }, 0);\n return num_items;\n}\n\nfunction getContentName(arr) {\n if (arr.length !== 1) return '';\n return arr[0][keyNm];\n}\n\nfunction getContentIds(arr) {\n // --- MODIFIED ---\n const content_ids = arr.map((item) => getFormattedId(item));\n // --- END MODIFIED ---\n return content_ids;\n}\n\nfunction getItem(arr) {\n let cat = [];\n cat.push(arr[0][keyCat]);\n\n let item = {\n // --- MODIFIED ---\n ProductID: getFormattedId(arr[0]),\n // --- END MODIFIED ---\n ProductName: arr[0][keyNm],\n Price: arr[0][keyPr],\n ImageURL: arr[0][keyImg],\n Categories: cat\n };\n\n return item;\n}\n\nfunction getValue(arr) {\n const value = arr.reduce((acc, curr) => {\n const itemPrice = curr[keyPr] ? makeNumber(curr[keyPr]) : 0;\n const itemQuantity = curr[keyQt];\n return acc + (itemQuantity ? makeInteger(itemQuantity) * itemPrice : itemPrice);\n }, 0);\n return toFixed2(value);\n}\n\nfunction getContents(arr, platform) {\n let contents = [];\n\n for (let i = 0; i < arr.length; i++) {\n let qt = 1;\n if (arr[i][keyQt]) qt = makeNumber(arr[i][keyQt]);\n\n if (platform === 'meta') {\n contents.push({\n // --- MODIFIED ---\n id: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n quantity: qt,\n item_price: arr[i][keyPr]\n });\n }\n\n if (platform === 'tiktok') {\n contents.push({\n // --- MODIFIED ---\n content_id: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n content_type: contentType,\n content_category: arr[i][keyCat],\n content_name: arr[i][keyNm],\n quantity: qt,\n price: arr[i][keyPr] ? makeNumber(arr[i][keyPr]) : 0\n });\n }\n\n if (platform === 'twitter') {\n contents.push({\n // --- MODIFIED ---\n content_id: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n content_name: arr[i][keyNm],\n content_type: arr[i][keyCat],\n num_items: qt,\n content_price: arr[i][keyPr]\n });\n }\n\n if (platform === 'pinterest') {\n contents.push({\n quantity: qt,\n item_price: arr[i][keyPr] ? makeString(arr[i][keyPr]) : '0'\n // Pinterest 'contents' array doesn't use ID\n });\n }\n }\n\n return contents;\n}\n\nfunction getItems(arr, platform) {\n let items = [];\n let totalDiscount = 0;\n if (data.discConfig === 'order_level') {\n totalDiscount = makeNumber(data.discOrderLevel);\n\n if (data.taxDiscountConfig == 'discDeduct') {\n totalDiscount = totalDiscount / (taxDeductPercent / 100 + 1);\n }\n }\n\n for (let i = 0; i < arr.length; i++) {\n let qt = 1;\n if (arr[i][keyQt]) qt = makeInteger(arr[i][keyQt]);\n\n if (platform === 'ga4') {\n let itemObj = {\n // --- MODIFIED ---\n item_id: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n item_name: arr[i][keyNm],\n quantity: qt,\n price: arr[i][keyPr],\n item_category: arr[i][keyCat]\n };\n\n for (let prop in customParamMap) {\n if (customParamMap[prop]) {\n if (prop === 'discountamountqty') itemObj[customParamMap[prop]] = arr[i][prop] * qt;\n else itemObj[customParamMap[prop]] = arr[i][prop];\n }\n }\n\n items.push(itemObj);\n }\n\n if (platform === 'klaviyo') {\n let cat = [];\n cat.push(arr[i][keyCat]);\n\n let itemObj = {\n // --- MODIFIED ---\n ProductID: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n ProductName: arr[i][keyNm],\n Quantity: qt,\n ItemPrice: arr[i][keyPr],\n ImageURL: arr[i][keyImg],\n ProductCategories: cat\n };\n\n for (let prop in customParamMap) {\n if (customParamMap[prop]) {\n itemObj[customParamMap[prop]] = arr[i][prop];\n }\n }\n\n items.push(itemObj);\n }\n\n if (platform === 'criteo') {\n let itemObj = {\n // --- MODIFIED ---\n id: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n quantity: qt,\n price: arr[i][keyPr]\n };\n\n items.push(itemObj);\n }\n\n if (platform === 'gAdsOff') {\n items.push({\n // --- MODIFIED ---\n productId: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n quantity: qt,\n unitPrice: makeNumber(arr[i][keyPr])\n });\n }\n\n if (platform === 'pinterest') {\n items.push({\n // --- MODIFIED ---\n product_id: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n product_name: arr[i][keyNm],\n product_quantity: qt,\n product_price: arr[i][keyPr] ? makeNumber(arr[i][keyPr]) : 0\n });\n }\n\n if (platform === 'reddit') {\n items.push({\n // --- MODIFIED ---\n id: getFormattedId(arr[i]),\n // --- END MODIFIED ---\n category: arr[i][keyCat],\n name: arr[i][keyNm]\n });\n }\n\n if (platform === 'rakuten') {\n //let p = arr[i][keyPr];\n let p = toFixed2(makeNumber(arr[i][keyPr]));\n let price;\n\n if (data.taxPriceConfig === 'priceDeduct' && taxDeductPercent && taxDeductPercent > 0) {\n let tmp = p / (taxDeductPercent / 100 + 1);\n price = (math.round(tmp * 100) / 100) * 100 * qt;\n price = math.round(price * 100) / 100;\n }\n\n if (data.discConfig === 'item_level' && arr[i][keyDisc]) {\n let tmp;\n let disc = makeNumber(arr[i][keyDisc]); //.replace(',','')\n if (data.taxDiscountConfig == 'discDeduct' && taxDeductPercent && taxDeductPercent > 0) {\n disc = disc / (taxDeductPercent / 100 + 1);\n }\n\n totalDiscount = toFixed2(makeNumber(totalDiscount) + toFixed2(disc));\n }\n\n let itemObj = {\n // --- MODIFIED ---\n sku: getFormattedId(arr[i]), // Rakuten uses SKU as the main ID\n // --- END MODIFIED ---\n product_name: arr[i][keyNm],\n quantity: qt,\n amount: price > 0 ? price : toFixed2(p * 100 * qt),\n optional_data: {}\n };\n\n if (keyCatList.length > 0) {\n let catArr = [];\n keyCatList.forEach((element) => catArr.push(arr[i][element]));\n itemObj.optional_data = { category: catArr.join(' > ') };\n }\n\n for (let prop in customParamMap) {\n if (customParamMap[prop]) {\n itemObj.optional_data[customParamMap[prop]] = arr[i][prop];\n }\n }\n\n items.push(itemObj);\n }\n }\n\n if (platform === 'rakuten' && totalDiscount > 0) {\n items.push({\n sku: 'Discount',\n quantity: '0',\n amount: toFixed2((totalDiscount - totalDiscount * 2) * 100),\n product_name: 'Discount'\n });\n }\n\n return items;\n}\n\n/*==============================================================================\n Helpers\n==============================================================================*/\n\n// --- NEW HELPER FUNCTION ---\n// This function builds the ID based on the user's selection\nfunction getFormattedId(item) {\n // 1. Get the default ID (from the 'keyId' field) as a fallback\n const defaultId = item[keyId] ? makeString(item[keyId]) : undefined;\n\n // 2. Check the format dropdown\n switch (idFormat) {\n case 'sku':\n // Use 'keySku' field if available, otherwise fall back to default\n return item[keySku] ? makeString(item[keySku]) : defaultId;\n\n case 'variantId':\n // Use 'keyVariantId' field if available, otherwise fall back to default\n return item[keyVariantId] ? makeString(item[keyVariantId]) : defaultId;\n\n case 'customShopify':\n // Use 'keyId' (as Product) and 'keyVariantId' (as Variant)\n const prodId = item[keyId];\n const varId = item[keyVariantId];\n\n if (prodId && varId) {\n // Build the custom string\n return 'shopify_' + market + '_' + makeString(prodId) + '_' + makeString(varId);\n }\n // If data is missing, fall back to default\n return defaultId;\n \n case 'default':\n default:\n // Return the default ID\n return defaultId;\n }\n}\n// --- END NEW HELPER FUNCTION ---\n\nfunction toFixed2(num) {\n return math.round(num * 100) / 100;\n}\n\nfunction endsWith(str, suffix) {\n return str.indexOf(suffix, str.length - suffix.length) !== -1;\n}\n\n\n___TESTS___\n\nscenarios: []\n\n\n___NOTES___\n\nCreated on 12/10/2021, 11:11:20\n\n\n", - "galleryReference": { - "host": "github.com", - "owner": "stape-io", - "repository": "universal-conversions-variable", - "version": "1effdefd3dceb2231152dab2618a0e350445fa6f", - "isModified": true, - "signature": "169cae7872c2e87e5481cac059bd11096da1cf56a7a4be62343db84172543856", - "galleryTemplateId": "WF8HR" - } - } + "simpleValueType": true, + "help": "choose either product or product_group as is required by TikTok events API", + "defaultValue": "product", + "enablingConditions": [] + } + ] + }, + { + "value": "name", + "displayValue": "content_name \u0027 \u0027", + "help": "will only return value if there is one object in product array, since content_name parameter is applicable only to single product (type) events" + }, + { + "value": "value", + "displayValue": "value", + "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" + }, + { + "value": "numitems", + "displayValue": "num_items", + "subParams": [] + } + ], + "simpleValueType": true, + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "tiktok", + "type": "EQUALS" + } + ] + }, + { + "type": "RADIO", + "name": "twitter_task", + "displayName": "What to return", + "radioItems": [ + { + "value": "contents", + "displayValue": "contents [{ }]" + }, + { + "value": "value", + "displayValue": "value", + "subParams": [], + "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" + } + ], + "simpleValueType": true, + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "twitter", + "type": "EQUALS" + } + ] + }, + { + "type": "RADIO", + "name": "criteo_task", + "displayName": "What to return", + "radioItems": [ + { + "value": "items", + "displayValue": "items [{ }]" + } + ], + "simpleValueType": true, + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "criteo", + "type": "EQUALS" + } + ] + }, + { + "type": "RADIO", + "name": "pinterest_task", + "displayName": "What to return", + "radioItems": [ + { + "value": "items", + "displayValue": "line_items [{ }]" + }, + { + "value": "value", + "displayValue": "value", + "subParams": [], + "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" + }, + { + "value": "ids", + "displayValue": "content_ids [ ]" + }, + { + "value": "contents", + "displayValue": "contents [{ }]" + } + ], + "simpleValueType": true, + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "pinterest", + "type": "EQUALS" + } + ] + }, + { + "type": "RADIO", + "name": "snap_task", + "displayName": "What to return", + "radioItems": [ + { + "value": "value", + "displayValue": "price", + "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" + }, + { + "value": "ids", + "displayValue": "item_ids" + }, + { + "value": "numitems", + "displayValue": "number_items", + "subParams": [] + } + ], + "simpleValueType": true, + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "snap", + "type": "EQUALS" + } + ] + }, + { + "type": "RADIO", + "name": "reddit_task", + "displayName": "What to return", + "radioItems": [ + { + "value": "value", + "displayValue": "value", + "help": "use wisely, value will be calculated based on product prices and will not account for discounts. not recommended for purchase events" + }, + { + "value": "numitems", + "displayValue": "itemCount", + "subParams": [] + }, + { + "value": "items", + "displayValue": "products" + } + ], + "simpleValueType": true, + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "reddit", + "type": "EQUALS" + } + ], + "help": "As described in the \u003ca href\u003d\"https://business.reddithelp.com/s/article/manual-conversion-events-with-the-reddit-pixel\"\u003eofficial documentation\u003c/a\u003e." + }, + { + "type": "GROUP", + "name": "arrayGroup", + "displayName": "Input Array", + "groupStyle": "ZIPPY_OPEN", + "subParams": [ + { + "type": "TEXT", + "name": "orderItems", + "displayName": "Array of Objects", + "simpleValueType": true, + "help": "[{}] any structured array of item objects", + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ] + } + ] + }, + { + "type": "GROUP", + "name": "keyGroup", + "displayName": "Input Array Keys", + "groupStyle": "ZIPPY_OPEN", + "subParams": [ + { + "type": "TEXT", + "name": "keyId", + "displayName": "Product ID/SKU", + "simpleValueType": true, + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ] + }, + { + "type": "TEXT", + "name": "keyNm", + "displayName": "Product Name", + "simpleValueType": true, + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ] + }, + { + "type": "TEXT", + "name": "keyPr", + "displayName": "Product Price", + "simpleValueType": true, + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ] + }, + { + "type": "TEXT", + "name": "keyQt", + "displayName": "Product Quantity", + "simpleValueType": true, + "valueValidators": [ + { + "type": "NON_EMPTY" + } + ] + }, + { + "type": "TEXT", + "name": "keyCat", + "displayName": "Product Category", + "simpleValueType": true, + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "ga4", + "type": "EQUALS" + }, + { + "paramName": "platform", + "paramValue": "klaviyo", + "type": "EQUALS" + }, + { + "paramName": "platform", + "paramValue": "twitter", + "type": "EQUALS" + }, + { + "paramName": "platform", + "paramValue": "reddit", + "type": "EQUALS" + } + ] + }, + { + "type": "TEXT", + "name": "keyImg", + "displayName": "Product Image URL", + "simpleValueType": true, + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "klaviyo", + "type": "EQUALS" + } + ] + }, + { + "type": "CHECKBOX", + "name": "buildCatTree", + "checkboxText": "Build Category Tree?", + "simpleValueType": true, + "help": "If your items have multiple categories, create a tree in \u0027cat1 \u003e cat2 \u003e cat3\u0027 string format", + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "rakuten", + "type": "EQUALS" + } ] + }, + { + "type": "TEXT", + "name": "keyCatList", + "displayName": "Category parameter keys", + "simpleValueType": true, + "help": "coma separated in order of appearance in tree, like: \u0027cat_key,mykey,custom_var_7\u0027", + "enablingConditions": [ + { + "paramName": "buildCatTree", + "paramValue": true, + "type": "EQUALS" + } + ] + } + ], + "help": "keys for corresponding parameters within input array" + }, + { + "type": "GROUP", + "name": "rakDiscountGroup", + "displayName": "Discount Configuration", + "groupStyle": "ZIPPY_OPEN", + "subParams": [ + { + "type": "SELECT", + "name": "discConfig", + "displayName": "Choose discount configuration method", + "macrosInSelect": false, + "selectItems": [ + { + "value": "item_level", + "displayValue": "From item-level" + }, + { + "value": "order_level", + "displayValue": "From order-level" + }, + { + "value": "none", + "displayValue": "None" + } + ], + "simpleValueType": true, + "defaultValue": "none" + }, + { + "type": "TEXT", + "name": "keyDiscItemLevel", + "displayName": "Parameter key for item-level discount", + "simpleValueType": true, + "enablingConditions": [ + { + "paramName": "discConfig", + "paramValue": "item_level", + "type": "EQUALS" + } + ], + "help": "discount parameter KEY for input array" + }, + { + "type": "TEXT", + "name": "discOrderLevel", + "displayName": "Order-level discount amount", + "simpleValueType": true, + "help": "order-level discount VALUE", + "enablingConditions": [ + { + "paramName": "discConfig", + "paramValue": "order_level", + "type": "EQUALS" + } + ] + } + ], + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "rakuten", + "type": "EQUALS" + } + ] + }, + { + "type": "GROUP", + "name": "rakTaxGroup", + "displayName": "Tax Configuration", + "groupStyle": "ZIPPY_OPEN", + "subParams": [ + { + "type": "SELECT", + "name": "taxPriceConfig", + "displayName": "Item price tax application", + "macrosInSelect": false, + "selectItems": [ + { + "value": "priceTaxless", + "displayValue": "Item price is taxless" + }, + { + "value": "priceDeduct", + "displayValue": "Item price needs tax deduction" + } + ], + "simpleValueType": true + }, + { + "type": "SELECT", + "name": "taxDiscountConfig", + "displayName": "Discount tax application", + "macrosInSelect": false, + "selectItems": [ + { + "value": "discTaxless", + "displayValue": "Discount is taxless" + }, + { + "value": "discDeduct", + "displayValue": "Discount needs tax deduction" + } + ], + "simpleValueType": true + }, + { + "type": "TEXT", + "name": "taxDeductPercent", + "displayName": "Tax %", + "simpleValueType": true, + "help": "just number, no % symbol", + "valueValidators": [], + "enablingConditions": [ + { + "paramName": "taxPriceConfig", + "paramValue": "priceDeduct", + "type": "EQUALS" + }, + { + "paramName": "taxDiscountConfig", + "paramValue": "discDeduct", + "type": "EQUALS" + } + ] + } + ], + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "rakuten", + "type": "EQUALS" + } + ] + }, + { + "type": "GROUP", + "name": "customParamsGroup", + "displayName": "Additional/Optional Parameters", + "groupStyle": "ZIPPY_OPEN", + "subParams": [ + { + "type": "SIMPLE_TABLE", + "name": "customParams", + "displayName": "", + "simpleTableColumns": [ + { + "defaultValue": "", + "displayName": "Custom parameter key in your array", + "name": "cusKey", + "type": "TEXT" + }, + { + "defaultValue": "", + "displayName": "Custom parameter name to return", + "name": "cusName", + "type": "TEXT" + } + ], + "newRowButtonText": "Add Custom Parameter", + "alwaysInSummary": false + } + ], + "enablingConditions": [ + { + "paramName": "platform", + "paramValue": "ga4", + "type": "EQUALS" + }, + { + "paramName": "platform", + "paramValue": "klaviyo", + "type": "EQUALS" + }, + { + "paramName": "platform", + "paramValue": "rakuten", + "type": "EQUALS" + } + ] + }, + { + "type": "SELECT", + "name": "idFormatType", + "displayName": "Product ID Format", + "macrosInSelect": true, + "selectItems": [ + { + "value": "default", + "displayValue": "Default (use \u0027Product ID/SKU\u0027 field)" + }, + { + "value": "variantId", + "displayValue": "Variant ID (use \u0027Item Variant ID\u0027 field)" + }, + { + "value": "sku", + "displayValue": "SKU (use \u0027Item SKU\u0027 field)" + }, + { + "value": "customShopify", + "displayValue": "Custom Shopify (shopify_MARKET_prod_var)" + } + ], + "simpleValueType": true + }, + { + "type": "TEXT", + "name": "keyVariantId", + "displayName": "Item Variant ID Key", + "simpleValueType": true + }, + { + "type": "TEXT", + "name": "keySku", + "displayName": "Item SKU Key", + "simpleValueType": true + }, + { + "type": "TEXT", + "name": "marketCode", + "displayName": "ISO Market Code (for Custom Shopify) e.g., GB, US", + "simpleValueType": true + } +] + + +___SANDBOXED_JS_FOR_WEB_TEMPLATE___ + +const makeInteger = require('makeInteger'); +const makeNumber = require('makeNumber'); +const makeString = require('makeString'); +const makeTableMap = require('makeTableMap'); +const getType = require('getType'); +const math = require('Math'); +const Object = require('Object'); + +/*============================================================================== +==============================================================================*/ + +// Original Key fields +const keyId = data.keyId; +const keyPr = data.keyPr; +const keyNm = data.keyNm; +const keyQt = data.keyQt; +const keyCat = data.keyCat; +const keyImg = data.keyImg; +const contentType = data.contentType; +const taxDeductPercent = toFixed2(makeNumber(data.taxDeductPercent)); +const keyDisc = data.keyDiscItemLevel; + +// --- NEW FIELDS --- +const idFormat = data.idFormatType; +const keySku = data.keySku; +const keyVariantId = data.keyVariantId; +const market = data.marketCode || ''; +// --- END NEW FIELDS --- + +const customParamMap = data.customParams + ? makeTableMap(data.customParams, 'cusKey', 'cusName') + : {}; +const keyCatList = data.keyCatList ? data.keyCatList.split(',') : []; +const inputArray = data.orderItems; + +if (getType(inputArray) === 'array' && inputArray.length) return runTask(); +else return; + +/*============================================================================== + Vendor related functions +==============================================================================*/ + +function runTask() { + const platform = data.platform; + const dataKeys = Object.keys(data); + let task; + + for (const key of dataKeys) { + if (endsWith(key, '_task') && data[key]) { + task = data[key]; + break; + } + } + + switch (task) { + case 'contents': + return getContents(inputArray, platform); + case 'ids': + return getContentIds(inputArray); + case 'name': + return getContentName(inputArray); + case 'value': + return getValue(inputArray); + case 'numitems': + return getNumItems(inputArray); + case 'items': + return getItems(inputArray, platform); + case 'item': + return getItem(inputArray); + } +} + +function getNumItems(arr) { + const num_items = arr.reduce((acc, curr) => { + const quantity = curr[keyQt] ? makeInteger(curr[keyQt]) : 1; + return acc + quantity; + }, 0); + return num_items; +} + +function getContentName(arr) { + if (arr.length !== 1) return ''; + return arr[0][keyNm]; +} + +function getContentIds(arr) { + // --- MODIFIED --- + const content_ids = arr.map((item) => getFormattedId(item)); + // --- END MODIFIED --- + return content_ids; +} + +function getItem(arr) { + let cat = []; + cat.push(arr[0][keyCat]); + + let item = { + // --- MODIFIED --- + ProductID: getFormattedId(arr[0]), + // --- END MODIFIED --- + ProductName: arr[0][keyNm], + Price: arr[0][keyPr], + ImageURL: arr[0][keyImg], + Categories: cat + }; + + return item; +} + +function getValue(arr) { + const value = arr.reduce((acc, curr) => { + const itemPrice = curr[keyPr] ? makeNumber(curr[keyPr]) : 0; + const itemQuantity = curr[keyQt]; + return acc + (itemQuantity ? makeInteger(itemQuantity) * itemPrice : itemPrice); + }, 0); + return toFixed2(value); +} + +function getContents(arr, platform) { + let contents = []; + + for (let i = 0; i < arr.length; i++) { + let qt = 1; + if (arr[i][keyQt]) qt = makeNumber(arr[i][keyQt]); + + if (platform === 'meta') { + contents.push({ + // --- MODIFIED --- + id: getFormattedId(arr[i]), + // --- END MODIFIED --- + quantity: qt, + item_price: arr[i][keyPr] + }); + } + + if (platform === 'tiktok') { + contents.push({ + // --- MODIFIED --- + content_id: getFormattedId(arr[i]), + // --- END MODIFIED --- + content_type: contentType, + content_category: arr[i][keyCat], + content_name: arr[i][keyNm], + quantity: qt, + price: arr[i][keyPr] ? makeNumber(arr[i][keyPr]) : 0 + }); + } + + if (platform === 'twitter') { + contents.push({ + // --- MODIFIED --- + content_id: getFormattedId(arr[i]), + // --- END MODIFIED --- + content_name: arr[i][keyNm], + content_type: arr[i][keyCat], + num_items: qt, + content_price: arr[i][keyPr] + }); + } + + if (platform === 'pinterest') { + contents.push({ + quantity: qt, + item_price: arr[i][keyPr] ? makeString(arr[i][keyPr]) : '0' + // Pinterest 'contents' array doesn't use ID + }); + } + } + + return contents; +} + +function getItems(arr, platform) { + let items = []; + let totalDiscount = 0; + if (data.discConfig === 'order_level') { + totalDiscount = makeNumber(data.discOrderLevel); + + if (data.taxDiscountConfig == 'discDeduct') { + totalDiscount = totalDiscount / (taxDeductPercent / 100 + 1); + } + } + + for (let i = 0; i < arr.length; i++) { + let qt = 1; + if (arr[i][keyQt]) qt = makeInteger(arr[i][keyQt]); + + if (platform === 'ga4') { + let itemObj = { + // --- MODIFIED --- + item_id: getFormattedId(arr[i]), + // --- END MODIFIED --- + item_name: arr[i][keyNm], + quantity: qt, + price: arr[i][keyPr], + item_category: arr[i][keyCat] + }; + + for (let prop in customParamMap) { + if (customParamMap[prop]) { + if (prop === 'discountamountqty') itemObj[customParamMap[prop]] = arr[i][prop] * qt; + else itemObj[customParamMap[prop]] = arr[i][prop]; + } + } + + items.push(itemObj); + } + + if (platform === 'klaviyo') { + let cat = []; + cat.push(arr[i][keyCat]); + + let itemObj = { + // --- MODIFIED --- + ProductID: getFormattedId(arr[i]), + // --- END MODIFIED --- + ProductName: arr[i][keyNm], + Quantity: qt, + ItemPrice: arr[i][keyPr], + ImageURL: arr[i][keyImg], + ProductCategories: cat + }; + + for (let prop in customParamMap) { + if (customParamMap[prop]) { + itemObj[customParamMap[prop]] = arr[i][prop]; + } + } + + items.push(itemObj); } + + if (platform === 'criteo') { + let itemObj = { + // --- MODIFIED --- + id: getFormattedId(arr[i]), + // --- END MODIFIED --- + quantity: qt, + price: arr[i][keyPr] + }; + + items.push(itemObj); + } + + if (platform === 'gAdsOff') { + items.push({ + // --- MODIFIED --- + productId: getFormattedId(arr[i]), + // --- END MODIFIED --- + quantity: qt, + unitPrice: makeNumber(arr[i][keyPr]) + }); + } + + if (platform === 'pinterest') { + items.push({ + // --- MODIFIED --- + product_id: getFormattedId(arr[i]), + // --- END MODIFIED --- + product_name: arr[i][keyNm], + product_quantity: qt, + product_price: arr[i][keyPr] ? makeNumber(arr[i][keyPr]) : 0 + }); + } + + if (platform === 'reddit') { + items.push({ + // --- MODIFIED --- + id: getFormattedId(arr[i]), + // --- END MODIFIED --- + category: arr[i][keyCat], + name: arr[i][keyNm] + }); + } + + if (platform === 'rakuten') { + //let p = arr[i][keyPr]; + let p = toFixed2(makeNumber(arr[i][keyPr])); + let price; + + if (data.taxPriceConfig === 'priceDeduct' && taxDeductPercent && taxDeductPercent > 0) { + let tmp = p / (taxDeductPercent / 100 + 1); + price = (math.round(tmp * 100) / 100) * 100 * qt; + price = math.round(price * 100) / 100; + } + + if (data.discConfig === 'item_level' && arr[i][keyDisc]) { + let tmp; + let disc = makeNumber(arr[i][keyDisc]); //.replace(',','') + if (data.taxDiscountConfig == 'discDeduct' && taxDeductPercent && taxDeductPercent > 0) { + disc = disc / (taxDeductPercent / 100 + 1); + } + + totalDiscount = toFixed2(makeNumber(totalDiscount) + toFixed2(disc)); + } + + let itemObj = { + // --- MODIFIED --- + sku: getFormattedId(arr[i]), // Rakuten uses SKU as the main ID + // --- END MODIFIED --- + product_name: arr[i][keyNm], + quantity: qt, + amount: price > 0 ? price : toFixed2(p * 100 * qt), + optional_data: {} + }; + + if (keyCatList.length > 0) { + let catArr = []; + keyCatList.forEach((element) => catArr.push(arr[i][element])); + itemObj.optional_data = { category: catArr.join(' > ') }; + } + + for (let prop in customParamMap) { + if (customParamMap[prop]) { + itemObj.optional_data[customParamMap[prop]] = arr[i][prop]; + } + } + + items.push(itemObj); + } + } + + if (platform === 'rakuten' && totalDiscount > 0) { + items.push({ + sku: 'Discount', + quantity: '0', + amount: toFixed2((totalDiscount - totalDiscount * 2) * 100), + product_name: 'Discount' + }); + } + + return items; +} + +/*============================================================================== + Helpers +==============================================================================*/ + +// --- NEW HELPER FUNCTION --- +// This function builds the ID based on the user's selection +function getFormattedId(item) { + // 1. Get the default ID (from the 'keyId' field) as a fallback + const defaultId = item[keyId] ? makeString(item[keyId]) : undefined; + + // 2. Check the format dropdown + switch (idFormat) { + case 'sku': + // Use 'keySku' field if available, otherwise fall back to default + return item[keySku] ? makeString(item[keySku]) : defaultId; + + case 'variantId': + // Use 'keyVariantId' field if available, otherwise fall back to default + return item[keyVariantId] ? makeString(item[keyVariantId]) : defaultId; + + case 'customShopify': + // Use 'keyId' (as Product) and 'keyVariantId' (as Variant) + const prodId = item[keyId]; + const varId = item[keyVariantId]; + + if (prodId && varId) { + // Build the custom string + return 'shopify_' + market + '_' + makeString(prodId) + '_' + makeString(varId); + } + // If data is missing, fall back to default + return defaultId; + + case 'default': + default: + // Return the default ID + return defaultId; + } +} +// --- END NEW HELPER FUNCTION --- + +function toFixed2(num) { + return math.round(num * 100) / 100; +} + +function endsWith(str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; } + + +___TESTS___ + +scenarios: [] + + +___NOTES___ + +Created on 12/10/2021, 11:11:20 + +