This post shows goes over creating a service for use by a React Redux app that adds transactions to Google Analytics Ecommerce Tracking. It is meant to be called by a Redux action after an order has been processed.

In my circumstance, this is not a single-page application. After the user creates an order the page redirects. The checkout process is a React application and I wanted to perform my Google Tracking right before the redirect.

ecommerce:send and hitCallback

If you check out the documentation for Google Analytics Ecommerce Tracking the method to send data doesn't take any other arguments.

ga('ecommerce:send');

This is problematic when you want to ensure that all Google Tracking data has been sent before redirecting the page.

Fortunately, we can still make this work. The ecommerce module is simply a wrapper for more manual tracking operations. In this sense, it actually performing multiple send operations with the single send command.

Sending hits via trackers is explained in full detail. Shoehorning that into the ecommerce module is actually not that difficult. The key is that you will need to track each hit via the hitCallback individually.

Using ecommerce you create hits when you create the transaction and attach each item. So this is where we attach the hit callback.

So without further ado...

Redux Service

/**
 * Service function that creates a Google Ecommerce Transaction
 * for the an order and returns a promise that is 
 * resolved when:
 *   1. hit tracking is complete
 *   2. hit tracking takes longer than 3s
 *   3. there is a hard failure during hit tracking
 */
function createTransaction(order) {
  return new Promise((resolve) => {

    // create a timeout to always resolve. this is in case there is a problem
    // sending the items/transaction
    let timeout = setTimeout(() => {
      console.log('Google analytics transaction timed out');
      resolve();
    }, 3000);

    // this will be called after the transaction and all
    // items are sent to the server.  once all elements have been
    // sent, it will resolve the promise.
    let hits = order.items.length + 1;  // items + transaction
    let hitCallback = function() {
      hits -= 1;
      if(hits === 0) {
        clearTimeout(timeout); // clear the timeout so promise doesn't resolve twice
        console.log('Google analytics transaction sent');
        resolve();
      }
    };

    // process the transaction and all items
    ga('require', 'ecommerce');
    ga('ecommerce:addTransaction', {
      id: order.id,
      revenue: order.subtotal,
      shipping: order.shipping,
      tax: order.tax,
      hitCallback,  // fires when tx send is done
    });
    for (let item of order.items) {
      ga('ecommerce:addItem', {
        id: order.id,
        name: item.title,
        sku: item.stock_no,
        price: item.price,
        quantity: item.qty,
        hitCallback, // fires when item send is done
      });
    }
    ga('ecommerce:send');

  })
  // handle a hard failure conditions gracefully (ie: ga didn't load)
  .catch((ex) => {
    console.log('Error sending Google analytics transaction');
    console.log(ex);
  });
}

This service calls the Analytics Tracking library and returns a Promise when one of three things happens:

  1. hit tracking is fully complete!
  2. hit tracking takes longer than 3s
  3. a hard failure occurs during hit tracking

In the first instance, everything works, so we're all good! In order to do this, we track the successful hit count (the transaction + each item in the transaction). When the hitCallback function has been called this number of times, tracking is complete and the Promise resolves.

The other two scenarios prevent tracking from hanging the order checkout process because tracking is not critical to the order placement workflow. We don't want the Promise to reject under any failure circumstance. Nor do we want tracking to hang the checkout process. This is why we actually catch hard failures and why the timeout simply does a resolve when it has been reached.