Skip to content

Releases: FX-Klaviyo/Klaviyo_Shopify_Cart_tracking_Enhancement

first version

19 Feb 17:40
cbfdf0b
Compare
Choose a tag to compare
first version Pre-release
Pre-release
`<script>
document.addEventListener('DOMContentLoaded', () => {
    const klaviyo = window.klaviyo || []; // Initialize Klaviyo tracking array
    let lastCartState = null; // Store the last known cart state for comparison

    // Function to fetch the latest cart data from Shopify
    const fetchCartData = async () => {
        try {
            const response = await fetch(`${window.location.origin}/cart.js`);
            if (!response.ok) throw new Error('Failed to fetch cart');
            return await response.json();
        } catch (error) {
            console.error('Error fetching cart data:', error);
            return null;
        }
    };

    // Function to retrieve product collections based on product ID
    const getCollectionsForProduct = (productId) => {
        return window.productCollections?.[productId] || [];
    };

    // Function to record cart changes and send tracking events to Klaviyo
    const recordCartChange = async () => {
        const newCart = await fetchCartData(); // Fetch updated cart data
        if (!newCart) return;

        const cartValue = newCart.total_price / 100; // Convert price from cents to dollars
        const pageURL = window.location.href; // Capture the URL where the event occurs

        // Convert cart items into a structured array with collections data
        const items = newCart.items.map(item => ({
            ProductID: item.product_id || item.id,
            Name: item.product_title,
            Price: item.final_line_price / 100,
            Quantity: item.quantity,
            Currency: newCart.currency,
            VariantID: item.variant_id || null,
            Collections: getCollectionsForProduct(item.product_id) // Include product collections
        }));

        console.log("Cart Items with Collections:", items);

        // Check if the cart is empty and send a 'Cart Emptied' event
        if (newCart.items.length === 0) {
            console.log('Cart Emptied');
            klaviyo.push(['track', 'Cart Emptied', {
                total_price: 0,
                $value: 0,
                total_discount: 0,
                original_total_price: 0,
                items: [],
                Currency: newCart.currency,
                PageURL: pageURL
            }]);
        }

        if (lastCartState) {
            let addedItems = [];
            let removedItems = [];
            let quantityReducedItems = [];

            // Create maps for easy comparison of previous and new cart states
            let prevCartMap = new Map(lastCartState.items.map(item => [item.id, item]));
            let newCartMap = new Map(newCart.items.map(item => [item.id, item]));

            // Identify added items
            newCart.items.forEach(newItem => {
                let prevItem = prevCartMap.get(newItem.id);
                if (!prevItem || newItem.quantity > prevItem.quantity) {
                    addedItems.push(newItem);
                }
            });

            // Identify removed or quantity-reduced items
            lastCartState.items.forEach(prevItem => {
                let newItem = newCartMap.get(prevItem.id);
                if (!newItem) {
                    removedItems.push(prevItem); // Item removed completely
                } else if (newItem.quantity < prevItem.quantity) {
                    quantityReducedItems.push({
                        ...prevItem,
                        quantity: prevItem.quantity - newItem.quantity,
                        unit_price: prevItem.final_line_price / prevItem.quantity / 100
                    });
                }
            });

            // Track 'Added to Cart' events
            addedItems.forEach(item => {
                console.log('Item Added:', item);
                klaviyo.push(['track', 'Added to Cart', {
                    ProductID: item.product_id || item.id,
                    Name: item.product_title,
                    ImageURL: item.image.src,
                    ProductURL: item.url,
                    Brand: item.vendor,
                    Price: item.final_line_price / item.quantity / 100,
                    CompareAtPrice: item.compare_at_price / 100,
                    PresentmentCurrency: newCart.currency,
                    VariantID: item.variant_id || null,
                    VariantTitle: item.variant_title || '',
                    Collections: getCollectionsForProduct(item.product_id), // Include collections
                    items: items,
                    $value: cartValue,
                    RecordedEventURL: pageURL
                }]);
            });

            // Track 'Removed from Cart' events
            removedItems.forEach(item => {
                console.log('Item Removed Completely:', item);
                klaviyo.push(['track', 'Removed from Cart', {
                    ProductID: item.product_id || item.id,
                    Name: item.product_title,
                    ImageURL: item.image.src,
                    ProductURL: item.url,
                    Price: item.final_line_price / item.quantity / 100,
                    Quantity: item.quantity,
                    Currency: newCart.currency,
                    VariantID: item.variant_id || null,
                    Collections: getCollectionsForProduct(item.product_id), // Include collections
                    items: items,
                    $value: cartValue,
                    RecordedEventURL: pageURL
                }]);
            });

            // Track 'Quantity Reduced' events
            quantityReducedItems.forEach(item => {
                console.log('Item Quantity Reduced:', item);
                klaviyo.push(['track', 'Quantity Reduced', {
                    ProductID: item.product_id || item.id,
                    Name: item.product_title,
                    ImageURL: item.image.src,
                    ProductURL: item.url,
                    Price: item.unit_price,
                    Quantity: item.quantity,
                    Currency: newCart.currency,
                    VariantID: item.variant_id || null,
                    Collections: getCollectionsForProduct(item.product_id), // Include collections
                    items: items,
                    $value: cartValue,
                    RecordedEventURL: pageURL
                }]);
            });
        }

        lastCartState = newCart; // Update the last known cart state
    };

    // Function to intercept cart changes by overriding fetch and XMLHttpRequest
    const interceptCartChanges = () => {
        const originalFetch = window.fetch.bind(window);
        window.fetch = (...args) => {
            return originalFetch(...args).then(async response => {
                if (response.url.includes(`${window.location.origin}/cart/`)) {
                    await recordCartChange(); // Trigger cart tracking when cart is updated
                }
                return response;
            });
        };

        // Override XMLHttpRequest to track cart updates in AJAX requests
        const originalXHR = window.XMLHttpRequest.prototype.open;
        window.XMLHttpRequest.prototype.open = function(method, url) {
            if (url.includes('/cart/')) {
                this.addEventListener('load', async function() {
                    await recordCartChange();
                });
            }
            return originalXHR.apply(this, arguments);
        };
    };

    // Initialize tracking: Fetch initial cart state and start intercepting changes
    (async () => {
        lastCartState = await fetchCartData();
        interceptCartChanges();
    })();
});

</script>
`