PNG %k25u25%fgd5n!
/**
* WCK Started Checkout
*
* Incoming event object
* @typedef {object} kl_checkout
* @property {string} email - Email of current logged in user
*
* @property {object} event_data - Data for started checkout event
* @property {object} $extra - Event data
* @property {string} $service - Value will always be "woocommerce"
* @property {int} value - Total value of checkout event
* @property {array} Categories - Product categories (array of strings)
* @property {string} Currency - Currency type
* @property {string} CurrencySymbol - Currency type symbol
* @property {array} ItemNames - List of items in the cart
*
*/
/**
* Attach event listeners to save billing fields.
*/
// Constants for the klaviyo api url prefix and revision
const KLAVIYO_API_URL_PREFIX = 'https://a.klaviyo.com/';
const KLAVIYO_API_REVISION = '2025-10-15';
var identify_object = {
'company_id': public_key.token,
'properties': {}
};
var klaviyo_cookie_id = '__kla_id';
function buildProfileRequestPayload(event_attributes) {
const topLevelAttributes = ['email', 'first_name', 'last_name'];
// Destructure event_attributes:
// - properties: gets the properties object from event_attributes, defaulting to empty object if not present
// - restAttributes: gets all other fields from event_attributes using the rest operator (...)
const { properties = {}, ...restAttributes } = event_attributes || {};
// Create a new object for the filtered properties to avoid mutating the original input
const filteredProperties = { ...properties };
const dataAttributes = { ...restAttributes };
// Move top level attributes from properties to data level
topLevelAttributes.forEach(field => {
if (filteredProperties[field] !== undefined) {
dataAttributes[field] = filteredProperties[field];
delete filteredProperties[field];
}
});
// Add the filtered properties back to dataAttributes
dataAttributes.properties = filteredProperties;
return JSON.stringify({
data: {
type: "profile",
attributes: dataAttributes
}
})
}
function buildEventRequestPayload(customer_properties, event_properties, metric_attributes) {
return JSON.stringify({
data: {
type: 'event',
attributes: {
properties: {
...event_properties,
},
metric: {
data: {
type: 'metric',
attributes: {
...metric_attributes,
}
}
},
profile: {
data: {
type: 'profile',
attributes: {
...customer_properties,
}
}
}
}
}
})
}
function makePublicAPIcall(endpoint, event_data) {
var company_id = public_key.token;
jQuery.ajax(KLAVIYO_API_URL_PREFIX + endpoint + '?company_id=' + company_id, {
type: "POST",
contentType: "application/json",
data: event_data,
headers: {
'revision': KLAVIYO_API_REVISION,
'X-Klaviyo-User-Agent': plugin_meta_data.data,
}
});
}
function decodeKlaviyoCookieData(cookie) {
var name = klaviyo_cookie_id + "=";
var decodedCookie = decodeURIComponent(cookie);
var ca = decodedCookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return decodeURIComponent(atob(c.substring(name.length, c.length)));
}
}
return "";
}
function getKlaviyoCookie() {
return decodeKlaviyoCookieData(document.cookie);
}
function encodeKlaviyoCookieData(cookie_data) {
return btoa(encodeURIComponent(JSON.stringify(cookie_data)));
}
function setKlaviyoCookie(cookie_data) {
// Get existing cookie data
const existingCookieData = getKlaviyoCookie();
let mergedData = {};
// If we have existing data, parse it and merge with new data
if (existingCookieData) {
try {
const parsedExistingData = typeof existingCookieData === 'string' ?
JSON.parse(existingCookieData) : existingCookieData;
mergedData = { ...parsedExistingData, ...cookie_data };
} catch (e) {
// If parsing fails, just use the new data
mergedData = cookie_data;
}
} else {
mergedData = cookie_data;
}
const cvalue = encodeKlaviyoCookieData(mergedData);
const date = new Date();
date.setTime(date.getTime() + (63072e6)); // adding 2 years in milliseconds to current time
const expires = "expires=" + date.toUTCString();
document.cookie = klaviyo_cookie_id + "=" + cvalue + ";" + expires + "; path=/";
}
/**
* Queries the dom for first_name, last_name, and email inputs being displayed on the checkout page.
* If both shipping and billing forms are present, both input nodes will be returned for the type (ie. first_name)
* @return {object} an object of dom nodes (firstNameNode, lastNameNode, emailNode)
*/
function getTrackingNodes() {
var emailNodes = jQuery('input[id*="email"]:visible, input[name*="email"]:visible');
var firstNameNodes = jQuery('input[id*="first_name"]:visible, input[name*="first_name"]:visible');
var lastNameNodes = jQuery('input[id*="last_name"]:visible, input[name*="last_name"]:visible');
return { firstNameNodes, lastNameNodes, emailNodes};
}
/**
* The event listener to be added to visible email, first_name, and last_name nodes.
* It makes a call to client/profile with the values from the email field and either
* the first_name or last_name value depending on the caller
* @return {undefined}
*/
function identifyUser(nameType, self) {
var { emailNodes } = getTrackingNodes();
var email = emailNodes.val();
var identify_properties = {
[nameType]: jQuery.trim(jQuery(self).val())
}
if (email) {
identify_properties["email"] = email;
setKlaviyoCookie(identify_properties);
identify_object.properties = identify_properties;
makePublicAPIcall('client/profiles/', buildProfileRequestPayload(identify_object));
}
}
/**
* Adds the event listeners for tracking on the first name and last name inputs.
* If both the shipping and billing forms are visible, listeners will be added to all first name and last name nodes
* @return {undefined}
*/
function klIdentifyBillingField() {
var { firstNameNodes, lastNameNodes } = getTrackingNodes();
firstNameNodes.each(function(){
var node = jQuery(this);
node.change(() => identifyUser("first_name", node));
});
lastNameNodes.each(function(){
var node = jQuery(this);
node.change(() => identifyUser("last_name", node));
});
}
window.addEventListener("load", function () {
// Custom checkouts/payment platforms may still load this file but won't
// fire woocommerce_after_checkout_form hook to load checkout data.
if (typeof kl_checkout === 'undefined') {
return;
}
var WCK = WCK || {};
WCK.trackStartedCheckout = function () {
var metric_attributes = {
'name': 'Started Checkout',
'service': 'woocommerce'
}
var customer_properties = {}
if (kl_checkout.email) {
customer_properties['email'] = kl_checkout.email;
// Identify user once we have an email. This will trigger AVAB if enabled.
klaviyo.identify(customer_properties);
} else if (kl_checkout.exchange_id) {
// Klaviyo use is already identified if we have an exchange ID.
customer_properties['_kx'] = kl_checkout.exchange_id;
} else {
return;
}
makePublicAPIcall('client/events/', buildEventRequestPayload(customer_properties, kl_checkout.event_data, metric_attributes));
};
var klCookie = getKlaviyoCookie();
// Priority of emails for syncing Started Checkout event: Logged-in user,
// cookied exchange ID, cookied email, billing email address
if (kl_checkout.email !== "") {
identify_object.properties = {
'email': kl_checkout.email
};
makePublicAPIcall('client/profiles/', buildProfileRequestPayload(identify_object));
setKlaviyoCookie(identify_object.properties);
WCK.trackStartedCheckout();
} else if (klCookie && JSON.parse(klCookie).$exchange_id !== undefined) {
kl_checkout.exchange_id = JSON.parse(klCookie).$exchange_id;
WCK.trackStartedCheckout();
} else if (klCookie && JSON.parse(klCookie).email !== undefined) {
kl_checkout.email = JSON.parse(klCookie).email;
WCK.trackStartedCheckout();
} else {
if (jQuery) {
var { firstNameNodes, lastNameNodes, emailNodes } = getTrackingNodes();
emailNodes.change(function () {
var elem = jQuery(this),
email = jQuery.trim(elem.val());
if (email && /@/.test(email)) {
var params = {
"email": email
};
if (firstNameNodes.length > 0) {
// Values come from first visible input node in the DOM
params["first_name"] = firstNameNodes.val();
}
if (lastNameNodes.length > 0) {
params["last_name"] = lastNameNodes.val();
}
setKlaviyoCookie(params);
kl_checkout.email = params.email;
identify_object.properties = params;
makePublicAPIcall('client/profiles/', buildProfileRequestPayload((identify_object)));
WCK.trackStartedCheckout();
}
});
// Save billing fields
klIdentifyBillingField();
}
}
});