Checkout Funnel Tracking | Blue Frog Docs

Checkout Funnel Tracking

Diagnosing and fixing checkout funnel tracking issues to understand customer drop-off and optimize conversions.

Checkout Funnel Tracking

What This Means

Checkout funnel tracking issues prevent you from understanding where customers abandon their purchase journey. Without accurate funnel data, you can't identify friction points or optimize the checkout experience.

Complete Checkout Funnel Events:

  1. view_cart - Cart viewed
  2. begin_checkout - Checkout started
  3. add_shipping_info - Shipping details entered
  4. add_payment_info - Payment method selected
  5. purchase - Transaction completed

Impact Assessment

Business Impact

  • Blind Spots: Can't identify where customers drop off
  • Optimization Paralysis: No data to prioritize improvements
  • Revenue Loss: Unknown friction points cause abandonments
  • Misallocated Resources: Guessing at problems instead of knowing

Analytics Impact

  • Incomplete Funnels: GA4 checkout funnel reports are empty
  • False Metrics: Conversion rates based on incomplete data
  • Attribution Gaps: Can't attribute conversions properly

How to Diagnose

Manual Funnel Test

Complete your checkout flow and verify each event:

// Monitor events during checkout
const checkoutEvents = [
  'view_cart',
  'begin_checkout',
  'add_shipping_info',
  'add_payment_info',
  'purchase'
];

const firedEvents = [];

const observer = new MutationObserver(() => {
  dataLayer.forEach(event => {
    if (checkoutEvents.includes(event.event) &&
        !firedEvents.includes(event.event)) {
      firedEvents.push(event.event);
      console.log('✓ Event fired:', event.event, event.ecommerce);
    }
  });
});

// Start monitoring
observer.observe(document.body, { childList: true, subtree: true });

// Check coverage
setTimeout(() => {
  const missing = checkoutEvents.filter(e => !firedEvents.includes(e));
  if (missing.length) console.warn('Missing events:', missing);
}, 10000);

GA4 DebugView

  1. Enable DebugView in GA4
  2. Complete entire checkout flow
  3. Verify each event appears in sequence
  4. Check for parameter errors

Common Missing Events

Stage Event Often Missing Because
Cart view_cart Not triggered on cart page load
Checkout Start begin_checkout Only fires on button click, not page load
Shipping add_shipping_info Single-page checkout doesn't trigger
Payment add_payment_info Third-party payment processors
Purchase purchase Order confirmation page issues

General Fixes

1. Implement Complete Funnel Tracking

Cart View:

// Fire on cart page load
document.addEventListener('DOMContentLoaded', () => {
  if (isCartPage()) {
    dataLayer.push({ ecommerce: null });
    dataLayer.push({
      event: 'view_cart',
      ecommerce: {
        currency: 'USD',
        value: getCartTotal(),
        items: getCartItems()
      }
    });
  }
});

Begin Checkout:

// Fire when checkout page loads OR checkout button clicked
function trackBeginCheckout() {
  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: 'begin_checkout',
    ecommerce: {
      currency: 'USD',
      value: getCartTotal(),
      coupon: getAppliedCoupon(),
      items: getCartItems()
    }
  });
}

Shipping Info:

// Fire when shipping form is completed
document.querySelector('#shipping-form')
  ?.addEventListener('submit', () => {
    dataLayer.push({ ecommerce: null });
    dataLayer.push({
      event: 'add_shipping_info',
      ecommerce: {
        currency: 'USD',
        value: getCartTotal(),
        shipping_tier: getSelectedShipping(),
        coupon: getAppliedCoupon(),
        items: getCartItems()
      }
    });
  });

Payment Info:

// Fire when payment method is selected
document.querySelectorAll('input[name="payment_method"]')
  .forEach(input => {
    input.addEventListener('change', () => {
      dataLayer.push({ ecommerce: null });
      dataLayer.push({
        event: 'add_payment_info',
        ecommerce: {
          currency: 'USD',
          value: getCartTotal(),
          payment_type: input.value,
          coupon: getAppliedCoupon(),
          items: getCartItems()
        }
      });
    });
  });

Purchase:

// Fire on order confirmation page
if (isOrderConfirmationPage()) {
  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: 'purchase',
    ecommerce: {
      transaction_id: getOrderId(),
      value: getOrderTotal(),
      tax: getOrderTax(),
      shipping: getOrderShipping(),
      currency: 'USD',
      coupon: getAppliedCoupon(),
      items: getOrderItems()
    }
  });
}

2. Handle Single-Page Checkouts

// For SPAs or multi-step single-page checkouts
const checkoutSteps = {
  cart: 'view_cart',
  shipping: 'add_shipping_info',
  payment: 'add_payment_info',
  confirm: 'purchase'
};

function onCheckoutStepChange(step) {
  const event = checkoutSteps[step];
  if (event) {
    dataLayer.push({ ecommerce: null });
    dataLayer.push({
      event: event,
      ecommerce: getEcommerceData()
    });
  }
}

// Monitor step changes
const checkout = document.querySelector('.checkout-container');
const observer = new MutationObserver(() => {
  const currentStep = document.querySelector('.step.active')?.dataset.step;
  if (currentStep && currentStep !== lastStep) {
    onCheckoutStepChange(currentStep);
    lastStep = currentStep;
  }
});

observer.observe(checkout, { subtree: true, attributes: true });

3. Handle Third-Party Payments

// Track before redirecting to payment processor
document.querySelector('#pay-with-paypal')
  ?.addEventListener('click', () => {
    // Track payment info before redirect
    dataLayer.push({ ecommerce: null });
    dataLayer.push({
      event: 'add_payment_info',
      ecommerce: {
        payment_type: 'PayPal',
        value: getCartTotal(),
        currency: 'USD',
        items: getCartItems()
      }
    });

    // Store order data for confirmation page
    sessionStorage.setItem('pending_order', JSON.stringify(getOrderData()));
  });

// On return from payment processor
if (isPendingOrderConfirmation()) {
  const orderData = JSON.parse(sessionStorage.getItem('pending_order'));
  if (orderData && isOrderSuccessful()) {
    dataLayer.push({ ecommerce: null });
    dataLayer.push({
      event: 'purchase',
      ecommerce: orderData
    });
    sessionStorage.removeItem('pending_order');
  }
}

4. Server-Side Purchase Tracking

// For guaranteed purchase tracking
// Send from server after order is confirmed

// Node.js example
const { BetaAnalyticsDataClient } = require('@google-analytics/data');

async function trackPurchase(order) {
  const measurementId = 'G-XXXXXXXX';
  const apiSecret = 'YOUR_API_SECRET';

  await fetch(`https://www.google-analytics.com/mp/collect?measurement_id=${measurementId}&api_secret=${apiSecret}`, {
    method: 'POST',
    body: JSON.stringify({
      client_id: order.clientId,
      events: [{
        name: 'purchase',
        params: {
          transaction_id: order.id,
          value: order.total,
          currency: order.currency,
          items: order.items
        }
      }]
    })
  });
}

Validation Checklist

  • view_cart fires on cart page load
  • begin_checkout fires at checkout start
  • add_shipping_info includes shipping_tier
  • add_payment_info includes payment_type
  • purchase has unique transaction_id
  • All events include complete items array
  • Currency is consistent across all events
  • Value matches expected totals

Further Reading

// SYS.FOOTER