Registering transactions in Google Analytics with Coldfusion

Google Analytics provide a free wonderful tool for visualizing user activity on your website. It even allows you to track and record all e-commerce transactions close to realtime. Analytics is easy to set up by adding just couple of lines of JavaScript that are used to trigger events on Google servers. It’s all quick and fancy but what are you going to do if you cannot use pre-cooked JavaScript? Consider the following scenarios:

  • You want an Analytics event been registered by one of your backoffice servers that are not a part of your website setup.
  • You want to push your data to Analytics with some delay. Consider delayed transactions that require clearing or confirmation, sometimes days after the initial website visit.

In all such cases you would prefer to make direct http calls from your backoffice to Google. How do you realize it? That’s how I did it:

  • Enable Analytics and make sure the cookie is transferrable
  • Save the user cookie data in a database (I assume the transactions are already in the database)
  • Create a scheduled task that combines transaction data and user cookie data and triggers an Analytics event from the backoffice
  • Test the setup

Walkthrough

Let’s walk through all the steps of the process:
At first you need to enable Google Analytics for your website. Log in to your Analytics account, go to Admin and create your tracking code. It will look like this:

<script>
  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-999999-1']);
  _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>

Add two lines of code to this JS snippet to allow Analytics cookie transfer to other sites:

        _gaq.push(['_setAccount', 'UA-999999-1']);
        _gaq.push(['_trackPageview']);
 
/* Add these two lines of code to liberate your GA cookies: */
 
	_gaq.push(['_setAllowLinker', true]); 
	_gaq.push(['_setDomainName', 'none']);

If you are curious you can dump the cookie scope to see what’s in it: 

Ignore the session cookies and __utmb cookie, we will need only __utma and __utmz cookies for our Analytics call. I would suggest saving the user cookie data in a database at the moment of transaction as we will need it later. Please mind that although the cookie scope is accessible directly from within the components it is probably not a good idea to let the components talk to cookie directly. Isolate!
Feel free to re-hash this code the way it suits you better, It is just an example:

First, extract all relevant data from the cookie scope:

<cfset var cookieList = "__utma,__utmz" />
<cfset var cookieStr = "" />
<cfloop from="1" to="#ListLen(cookieList)#" index="idx">
  <cfif StructKeyExists(cookie,ListGetAt(cookieList,idx))>
    <cfset cookieStr &= ListGetAt(cookieList,idx) & "=" & cookie[ListGetAt(cookieList,idx)] />
    <cfif idx neq listlen(cookieList)>
	<cfset cookieStr &= "%3B%2B" />
    </cfif>
  </cfif>
</cfloop>
 
<cfset processedCookieStr = processCookieString(cookieStr) />

Method processCookieString escapes some characters:

 
<cffunction name="parseCookieString" returntype="string">
  <cfargument name="cookieStr" default="" />
 
  <cfset var cookieProcessedStr = "" /> 
  <cfset var googleVarsArr = ListToArray(arguments.cookieStr,"&") />
  <cfloop from="1" to="#ArrayLen(googleVarsArr)#" index="idx">
    <cfset cookieProcessedStr &= googleVarsArr[idx] /> 
  </cfloop>
 
  <cfset cookieProcessedStr = ReplaceNoCase(cookieProcessedStr,"=","%3D","all") />
  <cfset cookieProcessedStr = ReplaceNoCase(cookieProcessedStr,"|","%7C","all") />
  <cfset cookieProcessedStr = ReplaceNoCase(cookieProcessedStr,";","%3B","all") /> 
  <cfset cookieProcessedStr = ReplaceNoCase(cookieProcessedStr,"+","%2B","all") />
  <cfset cookieProcessedStr = ReplaceNoCase(cookieProcessedStr,"&","&amp;","all") />
 
  <cfreturn cookieProcessedStr />
</cffunction>

Save your prepared cookie string in the database together with your transaction data.
You are more or less set. Now create a new page that selects your transaction data and pushes them to Google.
Code comments explain the flow:

<!--- Set your google account --->
<cfset googleAcc = "UA-999999-1" />
<!--- Get an instance of a component that processes Analytics data. --->
<cfset googleObj = CreateObject("component","com.shared.Google") />
<!--- Fetch qualified transactions --->
<cfset qryTransactions = googleObj.getTransactionForAnalytics() />
<!--- Fetch pricing --->
<cfset productStruct = googleObj.getAmountsPerTransaction() />
<!--- Set the hostname
<cfset siteHostnameStr = "www.my-ecommerce-site.com" />
 
<!--- Loop through transactions and pus them one for one to Google --->
<cfloop query="qryTransactions">
 
  <!--- Process cookie string to make it a part of the URL query string  --->
  <cfset googleVarsStr = ReplaceNoCase(qryTransactions.cookie,"?","&") />
 
  <!--- Escape some characters --->
  <cfset cookieStr = googleObj.processCookieString(googleVarsStr) /> 
 
  <!--- Create the transaction URL
        Note that the following variables are coming from the query. Substitute them for your variables if necessary:
      - orderID
      - transactionID
      - affiliateID (if you use affiliation program)
      - orderTotalMoney - total amount of transaction in a 0.00 format
      - taxMoney - applicable tax in a 0.00 format
      - shippingMoney - shipping costs in a 0.00 format
   --->
<cfset googleVarsStr = "http://www.google-analytics.com/__utm.gif?utmwv=5.2.4&utmn=#orderID#&utmhn=#siteHostnameStr#&utmt=tran&utmtid=#transactionID#&utmtst=#URLEncodedFormat(affiliateID)#&utmtto=#orderTotalMoney#&utmttx=#taxMoney#&utmtsp=#shippingMoney#&utmtci=&utmtrg=&utmtco=&utmac=#googleAcc#&utmcc=#cookieStr#">
<!--- Start your transaction --->				
<cfhttp url="#googleVarsStr#">
 
<!--- Create item URL. In this example transaction always have a single item but you may have as many items associated with a transaction as you want.
       Note that the following variables are coming from the query. Substitute them for your variables if necessary:
     - productSKU
     - productName
     - productVariant (for example: large, medium, small, pink, white, black, green)
     - productPriceMoney - item price in a 0.00 format
     - productQtyInt - number of items
--->
<cfset googleVarsStr = "http://www.google-analytics.com/__utm.gif?utmwv=5.2.4&utmn=#orderID#&utmhn=#siteHostnameStr#&utmt=item&utmtid=#transactionID#&utmipc=#productSKU#&utmipn=#URLEncodedFormat(lcase(productName))#&utmiva=#URLEncodedFormat(productVariant)#&utmipr=#productPriceMoney#&utmiqt=#productQtyInt#&utmac=#googleAcc#&utmcc=#cookieStr#">
 
<!--- Send item data to Google	--->			
<cfhttp url="#googleVarsStr#">
 
 
<!--- Update transaction status to mark it as 'processed' --->	
<cfset googleObj.setTransactionProcessed(transactionID=transactionID, processed=1) />
 
</cfloop>

That’s it. Check your Analytics account for new transactions in the Ecommerce section.
It usually takes a couple of minutes for Google to process the transactions and to show them to you.
Add this page to your scheduled tasks if you want to run it repeatedly

Tips:

  • Create a new Analytics account for test purposes if you don’t want to pollute your main account with testdata. It is just a matter of adding a new tracking ID. Switch back to your main tracking ID when you are ready.
  • Make sure that your server is able to access www.google-analytics.com from behind the firewall
  • Test the query string in your browser first using Charles or Wireshark. It is just easier.
  • Add proxyserver="127.0.0.1" and proxyport="8888" to your cfhttp call to debug http traffic using Charles. That will save you a lot of frustrations and time.
  • The cookie must be issued by the server that is linked to your Analytics account. All calls with ‘wrong cookie data’ will be ignored.