Sunday, October 17, 2010

Integrating Google Checkout with Analytics (and GWT)

 

I use Google Analytics to track how my site is used, so it is natural to want to track sales made via Google Checkout. There’s information on how to link these in the docs at the Google Checkout site.

Here’s how it works: First you grab the analytics tracking data that was generated for that web page. This data is represented as one big string that is Base64 encoded. The Google Checkout folks created some javascript code to help extract the analytics data and encode it for you. Once you have this encoded analytics data, you place it into a field of the XML structure that is sent to Google Checkout when the order is placed.

However, I have a couple problems with the solution presented in that document at the Google Checkout site. First, it uses an “old” version of analytics code, which at first was disconcerting. Second, it presumes you are using a form to POST, either directly to Google, or to yourself if you are doing a server-to-server order placement. But I am not using a FORM. I am using GWT and taking action in a onClick() function.

So here is what I did to adapt.

It turns out that it doesn’t matter if you are using the “old” or “new” style of analytics javascript code. The important thing is that both have a function _gat._getTracker("UA-XXXXXXX-X"), and they both do. Here’s how you can tell which you have. The “new” flavor of analytics javascript code looks something like this that follows, and is recommended to be put into your <head> section:

<script type="text/javascript">
  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-XXXXXXX-X']);
  _gaq.push(['_trackPageview']);
  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();
</script>

 

The “old” style, (the one used in the Google Checkout docs) looks something like this that follows, and this style of analytics was recommended to be added just before the closing <body> tag:

<script type="text/javascript">
  var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
  document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
  try {
    var pageTracker = _gat._getTracker("UA-XXXXXXX-X");
    pageTracker._trackPageview();
  } catch(err) {
  }
</script>

 

Just following the analytics code, I added the ga_post.js code snippet from the Google Checkout folks:

<script src="http://checkout.google.com/files/digital/ga_post.js"
  type="text/javascript">
</script>

 

Let’s have a look at that code:

var URCHINFIELD = "analyticsdata";
var uacct, userv, uwv, ufsc, utitle, uflash, ugifpath;
function encode64(input) {
  var k = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  var out = "",c1,c2,c3,e1,e2,e3,e4,i = 0;
  do {
    c1 = input.charCodeAt(i++);
    c2 = input.charCodeAt(i++);
    c3 = input.charCodeAt(i++);
    e1 = c1 >> 2;
    e2 = ((c1 & 3) << 4) | (c2 >> 4);
    e3 = ((c2 & 15) << 2) | (c3 >> 6);
    e4 = c3 & 63;
    if (isNaN(c2)) {
      e3 = e4 = 64;
    } else if (isNaN(c3)) {
      e4 = 64;
    }
    out = out + k.charAt(e1) + k.charAt(e2) + k.charAt(e3) + k.charAt(e4);
  } while (i < input.length);
  return out;
}
function _uGC(l, n) {
  if (!l || l == "" || !n || n == "") return "";
  var i = l.indexOf(n);
  if (i > -1) {
    var i2 = l.indexOf(";", i);
    if (i2 < 0) {
      i2 = l.length;
    }
    var i3 = n.indexOf("=") + 1;
    return l.substring((i + i3), i2);
  }
  return "";
}
function getUrchinFieldValue() {
  var s = "__uacct=" + uacct + ";";
  s += "__userv=" + userv + ";";
  if (userv == 0 || userv == 2) {
    var i,l = document.location;
    s += "__ugifpath=" + l.protocol + "//" + l.host + ugifpath + ";";
  }
  if (typeof uwv != "undefined") s += "__uwv=" + uwv + ";";
  if (typeof ufsc != "undefined") s += "__ufsc=" + ufsc + ";";
  if (typeof utitle != "undefined") s += "__utitle=" + utitle + ";";
  if (typeof uflash != "undefined") s += "__uflash=" + uflash + ";";
  var dc = document.cookie,umcval = "";
  umcval += "__utma=" + _uGC(dc, "__utma=") + ";";
  umcval += "__utmb=" + _uGC(dc, "__utmb=") + ";";
  umcval += "__utmc=" + _uGC(dc, "__utmc=") + ";";
  umcval += "__utmz=" + _uGC(dc, "__utmz=") + ";";
  umcval += "__utmv=" + _uGC(dc, "__utmv=") + ";";
  umcval += "__utmx=" + _uGC(dc, "__utmx=") + ";";
  umcval += "__utmxx=" + _uGC(dc, "__utmxx=") + ";";
  s += "__umcval=" + escape(umcval) + ";";
  return encode64(s);
}
function getUrchinInputCode() {
  return "<input type=\"hidden\" name=\"" + URCHINFIELD + "\" value=\""
      + getUrchinFieldValue() + "\">";
}
function setUrchinInputCodeOld() {
  if (typeof uacct == "undefined") return;
  for (var i = 0; i < document.forms.length; i++) {
    if (document.forms[i].urchindata)
      document.forms[i].urchindata.value = getUrchinFieldValue();
    if (document.forms[i].analyticsdata)
      document.forms[i].analyticsdata.value = getUrchinFieldValue();
  }
}
function setUrchinInputCode(obj) {
  if (typeof _gat == "undefined") return;
  uacct = obj._getAccount();
  userv = obj._getServiceMode();
  uwv = obj._getVersion();
  ufsc = obj._getClientInfo();
  utitle = obj._getDetectTitle();
  uflash = obj._getDetectFlash();
  ugifpath = obj._getLocalGifPath();
  setUrchinInputCodeOld();
}

 

It is very straightforward – here’s a quick tour: The function setUrchinInputCode() accepts a tracker object from the analytics world, and hangs on to the variables uacct, userv, uwv, ufsc, utitle, uflash, and ugifpath. The second function of interest is getUrchinFieldValue() which takes those seven analytics fields, plus all the __utm* fields from the cookies, and a couple of others fields, and assembles them all into a single semicolon delimited string, which is then encoded Base64 and returned. The first function also saves the encoded version of all this data into a form field named “analyticsdata”, if it exists.

This is all great, except I don’t want to save the encoded data into a form field. In my case I don’t have that form field, and it turns out the code executes gracefully without it being present. I just want to grab the encoded analytics data directly in GWT and pass it on to my Java code that places my order with Google Checkout. So in my GWT code, I wrote a JSNI snippet that looks like:

  private static native String getEncodedUrchinCode() /*-{
    $wnd.setUrchinInputCode($wnd._gat._getTracker("UA-XXXXXXX-X"));
    return $wnd.getUrchinFieldValue();
  }-*/;

 

To be clear, the _gat.getTracker() function is defined in the Google Analytics code. As described above, the other two calls, i.e., setUrchinInputCode() and getUrchinFieldValue() are defined in the ga_post.js code from the Google Checkout folks. Furthermore, the use of $wnd. is needed, and was a stumbling point for me. GWT code runs in an iframe and so those javascript functions do not exist there, and you will see “undefined" errors if you do not use $wnd. to specify the main window.

In my app, I have a GWT Image defined which has a click handler, which is where I can grab the Analytics data and pass it on to my code that makes the purchase.

imgCheckoutButton.addClickHandler(new ClickHandler() {
      @Override
      public void onClick(ClickEvent event) {
        String urchinData = getEncodedUrchinCode();
        doGoogleCheckoutOrder(urchinData, orderData);
      }
    });

 

I pass along my urchinData until I eventually arrive at my server side java code that actually places the order with Google Checkout. Here, I am using the Google Checkout Java SDK, which uses JAXB to map java objects into XML structures appropriate for Google Checkout. So it is a matter of finding the correct Java class to instantiate and inject into the XML structure. The answer is you have to create a CheckoutFlowSupport object, and a MerchantCheckoutFlowSupport object, and set your urchinData there. It looks approximately like this:

// This function returns the redirect URL dictated by Google Checkout
// after sending an order. This is the URL to take the user to the proper
// Google Checkout page.
  public String placeOrder(CartOrder order, String urchinData) {
    // CartOrder and orderItems below are objects in my app that I use to build a cart.
    // ApiContext is something the Google Checkout Java SDK created. It has your merchant keys, etc.
    CheckoutShoppingCartBuilder cart = apiContext.cartPoster().makeCart();
    for ( CartOrderItem item : orderItems ) {
      cart.addItem(item.getProductDescription(), item.getRecipientDescription(), item.getPrice(), 1, dc);
    }
    // build cart and add analytics data
    CheckoutShoppingCart checkoutShoppingCart = cart.build();
    CheckoutFlowSupport checkoutFlowSupport = checkoutShoppingCart.getCheckoutFlowSupport();
    if ( checkoutFlowSupport == null ) {
      checkoutFlowSupport = new CheckoutFlowSupport();
    }
    MerchantCheckoutFlowSupport merchantCheckoutFlowSupport = checkoutFlowSupport.getMerchantCheckoutFlowSupport();
    if ( merchantCheckoutFlowSupport == null ) {
      merchantCheckoutFlowSupport = new MerchantCheckoutFlowSupport();
    }
    merchantCheckoutFlowSupport.setAnalyticsData(urchinData);
    checkoutFlowSupport.setMerchantCheckoutFlowSupport(merchantCheckoutFlowSupport);
    checkoutShoppingCart.setCheckoutFlowSupport(checkoutFlowSupport);
    
    CheckoutRedirect checkoutRedirect = apiContext.cartPoster().postCart(checkoutShoppingCart);
    return checkoutRedirect.getRedirectUrl();
  }

 

 

.