Preventing duplicate transactions in Universal Analytics with Google Tag Manager

One of the most common headaches while implementing the ecommerce tracking on a site is trying to match the tracked transactions by the shop backend to Google Analytics. As most tracking solutions are JavaScript based, there’s a small chance of losing some of them and there’s nothing we can do without playing with the measurement protocol and some server-side tracking.

Another problem that is usually present is having duplicated transactions. And this hopefully is something we can prevent with some code.

We can setup a tag to write a cookie each time a visitor views our “thank you” page, that is not a bad approach, but that way we won’t be sure that the transaction has been really tracked on Google Analytics.

We’re going to use the hitCallback feature available for Universal Analytics, to set the cookies just right after the data has been successfully sent to Google Analytics.

We’ll need to set the hitCallback value in our Google Tag Manager Tag to a Custom JavaScript Variable. As I was pointed by Simo Ahava on Twitter , hitCallback is expecting a function, so we’re going to return a function that does the following:

1. Grabs the current transactionId from the dataLayer
2. Checks for the “transactions” cookie
2.1. If if doesn’t exists we’ll create it with the current transactionId value
2.2. If the cookies already exists, we’ll check the current values, and if the current transaction is not there, we’ll add it.

To avoid having a new cookie for each transaction, we’ll be using just one cookie with all the transactions joined by a pipe ( “|” ) symbol.

Ok, now every time that a transaction hit is sent to Google Analytics, the current transactionId will be added to our tracking cookie.

We’ll need a 1st party variable too to grab the transacctions cookie this way:

You may noticed that the first 2 lines of the code is checking for the transactionId, this is because in this example we’re using the Enhanced Ecommerce feature and populating the transaction info from the dataLayer, and we don’t want to do anything for all the pageviews on the site but just for our thankyou page one. You may need to tune this for your needs.

Ok, let’s move on. Now we’ll need to add another customJS variable to check if the current transaction is already in the cookie, and we’ll use this variable to create a blocking trigger for our tag.

I’ve named it as “Should I Track Transaction”, (yeah, not the best name), but it helps to understand the trigger:

We only need to add this blocking rule to our pageview tag and we’ll be finish.

Let’s do a resume of the tracking flow:

  1. “Should I Track Transaction”, will return “blockTransaction” if the current transactionId is present in our tracking Cookie
  2. “Block Transaction” Trigger will block the pageview tag firing if #1 is true.
  3. If the first 2 points are not met, the pageview tag will be fired.
  4. When the pageview tag is fired, the hitCallback function will be executed right after the transactions has been sent to Google Analytics Endpoint
  5. The hitCallback will execute the function returned by the variable “transactionCallback”, which will be in charge of creating the cookie if is doesn’t exist and adding the current transactionId to it.

I know that this will not be functional for some cases and there’re a lot of different implementations, sending the transaction based on events, sending the transactions based on a macro value (enhanced ecommerce), but that’s something you’ll need to figure out as isn’t there any stardard tracking solution. Hopefully you have learnt how hitcallbacks work in Google Tag Manager and you could get it working for your enviroment, if not drop a message in the post and I (or any other reader), will try to help you.

As could not be otherwise Sir Simo already did something similar for old ecommerce tracking some months ago.

transactionCallback Code

function()
{
  	// If isn't there a transaction ID, we don't need to do anything.
	if(!{{transactionId From DL}})
    	return;
  
	return function(){    
		var transactionId = {{transactionId From DL}};
		if({{transactions}}){
 			var trackedTransactions = {{transactions}}.split("|");
   			if(trackedTransactions.indexOf(transactionId)==-1){         
          		trackedTransactions.push(transactionId);
				var d = new Date();
    			d.setTime(d.getTime() + (180*24*60*60*1000));
    			var expires = "expires="+d.toUTCString();           
    			document.cookie = "transactions=" + trackedTransactions.join('|') + "; " + expires;
        	}
		}else{
      		var trackedTransactions = [];
      		trackedTransactions.push(transactionId);
  			var d = new Date();
    		d.setTime(d.getTime() + (180*24*60*60*1000));
    		var expires = "expires="+d.toUTCString();           
    		document.cookie = "transactions=" + trackedTransactions.join('|') + "; " + expires;
		}
	}
}

Should I track transaction Code

function()
{
	if(!{{transactionId From DL}})
       return;

    var transactionId = {{transactionId From DL}};	
	if({{transactions}}){
  		var trackedTransactions = {{transactions}}.split("|");
    	if(trackedTransactions.indexOf(transactionId)>-1)
        {
			return "blockTransaction";
        }
    }
}

Comments

21 responses to “Preventing duplicate transactions in Universal Analytics with Google Tag Manager”

  1. Rohit Langde Avatar
    Rohit Langde

    Hey David,
    Thank you for the efforts taken to come up with a solution to avoid duplicate transaction.

    Frankly, I am not so good with JavaScript and used Tag Manager for the first time because your solution looked promising.
    I followed step by step given in the article and matched every value / code as per instructions and even compared screenshots to avoid any error (obviously changed GA Tracking ID 😛 )
    Still, I am not able to get it working.

    I have problem at two steps:

    1. In this screenshot: https://www.thyngster.com/wp-content/uploads/img_55c8367f5af28.png where Value of page is {{gaPagePath}}
    Is that a new variable, if yes, there is no code given for it. As an alternative, I used {{Page Path}} which was available variable.

    2. In this screenshot: https://www.thyngster.com/wp-content/uploads/img_55c8359f22c4e.png where Data Variable name mentioned should be same for all? Your statement “You may need to tune this for your needs.” was confusing. I use WooCommerce platform so how do I need to tune it?

    I’ve spent hours figuring out but in vain.

    Could you please shed some light and help me out on this?

    1. Marko Avatar
      Marko

      Hi Rohit

      Have you found a solution to this?

      I am having the same issue and can’t find anything specific to woocommerce.

  2. James Avatar
    James

    Hi,

    Interesting article, just a quick question though – if you track transactions via an event, will the fix still work? My transactions are sent via an event, not pageview. I assume the principle will still apply, just a case of changing the tag type above from pageview to event.

    Is that the case?

    Many thanks.

    1. David Vallejo Avatar

      James, This just sets a blocking rule, you can use it with any tag,even with adwords conversions ones or anything else.

  3. Edward Upton Avatar

    Excellent guide – setting the call back from the GTM tag is certainly a new idea for me.

    1. David Vallejo Avatar

      Thanks for your comment Edward (=

  4. Chenna Avatar

    n my Google Analytic E-commerce overview total transactions count shows wrong, actually here on the date of 04-Oct-2016 total transactions are 2 only but there count shows 4.

  5. Loryn Thompson Avatar
    Loryn Thompson

    Just in case anyone else is having the same problem — my site has different pages for transaction confirmations, which meant Chrome was creating a different cookie for each transaction confirmation page, instead of appending each transactionId to the same cookie. Adding `”path=/;”` in each instance of setting the cookie in the transactionCallbackCode fixed it for me 🙂

    Also, @Rohit and @Marko, the {{gaPagePath}} in the fields to set area looks to me to be a custom way that David wants to pass his page path to GA. This tutorial should work just fine without it.

    And about getting your transaction ID into the DataLayer — you have to put enhanced ecommerce code on your conversion pages that pushes this value to the DataLayer in order for you to capture it.

    WooCommerce may have a plugin, but I’m not familiar with that community enough to know.

    To check what values are available in the dataLayer, open up JavaScript console while on a transaction confirmation page and type `dataLayer`. This will show you all the objects available in your dataLayer.

    If there is no `impressions` or `purchase` type object, then your transaction values aren’t currently being pushed to your dataLayer, and you will need to address that before you can use this tutorial. If the object does exist, you can use DOM notation to access it with a dataLayer value.

    1. Marko Avatar
      Marko

      Thanks for your reply @Loryn. You are correct, there is a plugin in WordPress. I’ve used it for the sake of simplicity and it works seamlessly. If you run WordPress and you are not 100% comfortable with javascript, the DataLayer and the DOM , I highly recommend it. The plugin is DuracellTomi from Geiger Tamas. https://en-au.wordpress.org/plugins/duracelltomi-google-tag-manager/
      If you are starting fresh and don’t have any tracking in place, it’s really easy to install . A little more thought is required when trying to replace existing Tag Manager/Analytics setups.
      Hope this helps

  6. Antti Nylund Avatar

    Excellent post, thank you.

    I was wondering 2 things:
    1) Should we address the fact that we are appending new IDs to the cookie from here to infinity? Can we run into performance or other issues if there are hundreds (or thousands?) of IDs?

    2) What do you think using referring information as back up for occasional cookie deletion: fire tag only if previous page was /checkout (or similar)?

    1. Antti Nylund Avatar

      Bah,

      digged deeper to Simo’s post comment section and there they were… answers to my both questions… You can leave this unpublished with the original post. My bad.

  7. Letty Avatar
    Letty

    Hi David, thanks so much for the help!!
    I just have one question:

    Is there a way to update all transaction values, instead of blocking them? In my case, users can modify they “purchase” cuz it’s actually a booking, so revenue may change. For example:

    – A user book with us and total revenue is $40 USD.
    – The next day, this user modify their booking and now the revenue is $30 USD.
    – In Analytics i’ll see 2 transactions from the same transaction ID and with a revenue of $70 USD, although in reality it’s $30 USD.

    Any help would be appreciated!

    Regards 🙂

    1. Anton Avatar
      Anton

      I realize this is two years after your question, but:

      You can send the difference. So in your example, you would send -10.00 which would adjust the revenue in the GA report. Or look into refund in EE.

  8. Konrad Avatar
    Konrad

    Hey everybody,

    I have enhanced ecommerce implemented on our side. On our conversion page a Transaction Tag is triggered by a custom event. In this guide we are talking about adjusting a tag which is triggered on all pages, are you adjusting the general Google Analytics Page View tag or the Transaction tag here?

    Thank you in advance and best regards

  9. Rory Avatar
    Rory

    This was very helpful. Been having dupe transaction problems for a while, mostly with mobile devices.

    I found it easier to test this by setting the path to “/” in the 2 lines of code that set the cookie:

    document.cookie = “transactions=” + trackedTransactions.join(‘|’) + “; path=/;” + expires;

    document.cookie = “transactions=” + trackedTransactions.join(‘|’) + “; path=/;” + expires;

    That way I can have a fake receipt page on a different path that I reload to make sure the transaction isnt’ being sent again.

  10. Matt Rhys-Davies Avatar

    Thanks – this was incredibly useful for me.

    I’m still unsure as to what causes the issue – on the platform I am currently experiencing the duplication issue on (Magento), the confirmation page can only be viewed – and thus the GA code called – once, then it cannot be re-navigated to.

    There is an overwhelming pattern in mobile devices on transactions impacted, perhaps mobile browser cache is the culprit.

  11. Stuart Mitchell Avatar

    Thanks David just what I was after. Have saved me a few hours of work and dirty data. Appreciate your efforts.

  12. Surya Avatar
    Surya

    Hi David,
    Thanks for the post.This helps me a lot.
    But I have little confusion on 1st party cookie.If customer bookmark the receipt page and if he access again in future, the receipt page will access but the purchase event should not push data to data layer.To do this we should compare all the transaction id’s in cookie with present receipt transaction id.But I have thousand’s of transactions placed earlier and it takes lot of time to fetch all the transaction id’s.Please help how can I achieve this without using 1st party cookie.
    Thanks in advance.

    1. David Vallejo Avatar

      the transactions ids are saved per user. on their local browsers, do you have users with thousand transactions, how many of them are likely going to go back?.

      In any case, from the site perspective, I think when your site shows a confirmation page you’re pushing your transaction data to the dataLayer, just ask your developers to don’t push anything if the transaction timestamp is older than 5 minutes. That should be enough

  13. […] then fire it). You can check the below sources for the step by step implementation: Sources: https://www.thyngster.com/preventing-duplicate-transactions-in-universal-analytics-with-google-tag-m… […]

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.