Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  By default the system is using secure.ultracart.com in the checkout then the GA tracking is set to use that host. If you have a custom SSL then secure.yourdomain.com is passed to the tracking script as .yourdomain.com. This field will provide advanced user the ability to override this on the Google analytics tracking script. 

Info
titlePro Tip - Use Custom SSL

Given that cookies are used to track the checkout progress, it's best if you use a custom SSL so that your checkout and website are on the same root domain.

So for example you would have:

www.yourdomain.com -> secure.yourdomain.com

Then in your GTM or GA tracking you would set the allow domain to ".yourdomain.com". This will allow the GA tracking cookies to be visible on both domains.

If you have two different root domains then on your www.yourdomain.com you're going to have to implement some of the techniques documented on this page.

https://support.google.com/analytics/answer/1034342?hl=en


Pricing Tiers

If you have pricing tiers configured on your account for wholesale customers then you can track them on a separate Google Analytics account if you would like. There will be an additional row in the configuration for each pricing tier on your store. If you leave these blank then UltraCart will use the default UA- account when tracking the customer even if they have a pricing tier.

...

Tip

For the latest and greatest code, please visit this github site:

https://github.com/ryanjkelly/xml2ga

Ryan has made numerous improvements since the initial development which is listed below.

...

 



"Yes, I have been successfully tracking UC sales and refunds for the past few weeks through XML postback. I don't have a "template" I can send you, but this (link below) PHP code
will pretty much work out of the box if you just place your Google Analytics account ID in it. Note: the Google Analytics account must be the newer (Universal Analytics) type or else this won't work.

https://snipt.net/raw/72a876575e01ca300f34bd67fa26e0a3/?nice

Refunds are recorded into GA with "ref-" appended to the transaction ID so that you can then separate them easily in reports. I also plan to append "auto-" to auto orders to separate them in reports as well but haven't done that yet.

A few things to note when implementing this. Make sure the time zone in the Google Analytics property matches UltraCart's server time zone, which I think is EST. Also, merchants that get sales 24/7 will notice that daily revenue from GA and UC sometimes won't match up. This is because, for example, if a customer orders something from UC at 11:57pm, the order may not get recorded into GA until 12:02am, which it is then the following day."  


Code Block
languagegroovy
titleSample Script
linenumberstrue
collapsetrue
<?php

// ----------------------------------------------- //
//    Generate a unique ID for Google Analytics    //
// ----------------------------------------------- //

function gen_uuid() { // Generates a UUID. A UUID is required for the measurement protocol.
  return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
    // 32 bits for "time_low"
    mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
    // 16 bits for "time_mid"
    mt_rand( 0, 0xffff ),
    // 16 bits for "time_hi_and_version",
    // four most significant bits holds version number 4
    mt_rand( 0, 0x0fff ) | 0x4000,
    // 16 bits, 8 bits for "clk_seq_hi_res",
    // 8 bits for "clk_seq_low",
    // two most significant bits holds zero and one for variant DCE1.1
    mt_rand( 0, 0x3fff ) | 0x8000,
    // 48 bits for "node"
    mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
  );
}


// -------------------- //
//     GA Post Data     //
// -------------------- //

$url = 'https://www.google-analytics.com/collect'; // This is the URL to which we'll be sending the post request.
$user_agent = 'UltraCart/1.0'; // User agent recorded into Google


// ---------------------------------- //
//    Get UltraCart post back data    //
// ---------------------------------- //

// Read in the XML document from the post data
$xml_document = file_get_contents('php://input');

// Parse the XML Document into a DOM Object
$doc = new DOMDocument();
$doc->loadXML($xml_document);

// Extract key fields
$exports = $doc->getElementsByTagName("export");
foreach ($exports as $export) {
	$orders = $export->getElementsByTagName("order");
	
	// Get order element
	foreach($orders as $order) {
		
		// Get order data
		$orderCS = $order->getElementsByTagName("current_stage")->item(0)->nodeValue;           // gets the current stage of the order
		$orderPS = $order->getElementsByTagName("payment_status")->item(0)->nodeValue;          // gets the payment status of the order
		$orderSM = $order->getElementsByTagName("shipping_method")->item(0)->nodeValue;         // gets the shipping method of the order
		$orderTI = $order->getElementsByTagName("order_id")->item(0)->nodeValue;                // gets the transaction ID
		$orderTR = $order->getElementsByTagName("total")->item(0)->nodeValue;                   // gets the transaction revenue
		$orderTS = $order->getElementsByTagName("shipping_handling_total")->item(0)->nodeValue; // gets the transaction shipping
		$orderTT = $order->getElementsByTagName("tax")->item(0)->nodeValue;                     // gets the transaction tax
		$customField1 = $order->getElementsByTagName("custom_field_1")->item(0)->nodeValue;     // gets the product
		$customField2 = $order->getElementsByTagName("custom_field_2")->item(0)->nodeValue;     // gets the product category
		$customField4 = $order->getElementsByTagName("custom_field_4")->item(0)->nodeValue;     // gets the GA client ID
		$customField5 = $order->getElementsByTagName("custom_field_5")->item(0)->nodeValue;     // gets the subid value
		$orderOTI = $order->getElementsByTagName("auto_order_original_order_id")->item(0)->nodeValue; // gets the original transaction ID
		$orderRTR = $order->getElementsByTagName("total_refunded")->item(0)->nodeValue;               // gets the refunded transaction revenue
		$orderRBU = $order->getElementsByTagName("refund_by_user")->item(0)->nodeValue;               // gets the user who processed the refund
		$orderRMN = $order->getElementsByTagName("merchant_notes")->item(0)->nodeValue;               // gets the merchant notes for refund

			// ----------------------------------------- //
			//    POST Order Data to GOOGLE ANALYTICS    //
			// ----------------------------------------- //

			// generate (or pass through) unique ID for GA CID
			if (empty($customField4) || $customField4 == 'undefined') { $orderCID = gen_uuid(); } else { $orderCID = $customField4; }

			$orderData = array( // This is an associative array that will contain all the parameters that we'll send to Google Analytics
			  'v' => 1, // The version of the measurement protocol
			  'tid' => 'UA-######-###', // Google Analytics account ID
			  'cid' => $orderCID, // The client ID or UUID
			  't' => 'transaction' // Hit Type
			);
				
				// ------------------------------------------------------------ //
				//    If order is in 'Completed Order' stage -AND- 'Processed'  //
				//       -AND- not coming from the 'Shipping Department'        //
				//                             -OR-                             //
				//        in the 'Shipping Department' -AND- 'Processed'        //
				// ------------------------------------------------------------ //
				
				if (($orderCS === 'CO' && $orderPS === 'Processed' && empty($orderSM)) || ($orderCS === 'SD' && $orderPS === 'Processed')) {
					$orderData['dh'] = 'secure.ultracart.com'; // Sets the document hostname for GA
					$orderData['dp'] = '/processed'; // Sets the document path for GA
					$orderData['dt'] = 'Order Processed'; // Sets the document title for GA
					$orderData['ti'] = $orderTI; // Sets the transaction ID for GA
					$orderData['tr'] = $orderTR; // Sets the transaction revenue for GA
					$orderData['ts'] = $orderTS; // Sets the transaction shipping for GA
					$orderData['tt'] = $orderTT; // Sets the transaction tax for GA
					$orderData['cd3'] = $customField2; // Sets a custom dimension (Product Category)
					$orderData['cd4'] = $customField5; // Sets a custom dimension (Subid)
				}

				// --------------------------------------------------- //
				//           If order has been refunded from           //
				//    'Completed Orders' -OR- 'Shipping Department'    //
				// --------------------------------------------------- //

				if (($orderCS === 'CO' && $orderPS === 'Refunded') || ($orderCS === 'SD' && $orderPS === 'Refunded')) {
					$orderData['dh'] = 'secure.ultracart.com'; // Sets the document hostname for GA
					$orderData['dp'] = '/refunded'; // Sets the document path for GA
					$orderData['dt'] = 'Order Refunded'; // Sets the document title for GA
					$orderData['ti'] = 'ref-'.$orderOTI; // Sets the transaction ID for GA
					$orderData['tr'] = '-'.$orderRTR; // Sets the transaction revenue for GA
					$orderData['cd11'] = $orderRBU; // Sets a custom dimension (Refund by User)
					$orderData['cd5'] = $orderRMN; // Sets a custom dimension (Merchant Notes)
					$orderData['cd3'] = $customField2; // Sets a custom dimension (Product Category)
					$orderData['cd4'] = $customField5; // Sets a custom dimension (Subid)
				}

			$orderContent = http_build_query($orderData); // The body of the post must include exactly 1 URI encoded payload and must be no longer than 8192 bytes. See http_build_query.
			$orderContent = utf8_encode($orderContent); // The payload must be UTF-8 encoded.

			$orderCH = curl_init();
			curl_setopt($orderCH,CURLOPT_USERAGENT, $user_agent);
			curl_setopt($orderCH,CURLOPT_URL, $url);
			curl_setopt($orderCH,CURLOPT_HTTPHEADER,array('Content-type: application/x-www-form-urlencoded'));
			curl_setopt($orderCH,CURLOPT_HTTP_VERSION,CURL_HTTP_VERSION_1_1);
			curl_setopt($orderCH,CURLOPT_POST, TRUE);
			curl_setopt($orderCH,CURLOPT_POSTFIELDS, $orderContent);
			curl_exec($orderCH);
			curl_close($orderCH);

	} // END foreach($orders as $order)

		// Get item element/s
		$items = $order->getElementsByTagName("item");
		$i = 0; // set increment number
		foreach($items as $item) {

			// Get item data
			$itemIN = $item->getElementsByTagName("item_id")->item(0)->nodeValue;                  // gets the item name
			$itemIP = $item->getElementsByTagName("total_cost_with_discount")->item(0)->nodeValue; // gets the item price
			$itemIQ = $item->getElementsByTagName("quantity")->item(0)->nodeValue;                 // gets the item quantity
			
			// ------------------------------------------------------------ //
			//    If order is in 'Completed Order' stage -AND- 'Processed'  //
			//       -AND- not coming from the 'Shipping Department'        //
			//                             -OR-                             //
			//        in the 'Shipping Department' -AND- 'Processed'        //
			// ------------------------------------------------------------ //
			
			if (($orderCS === 'CO' && $orderPS === 'Processed' && empty($orderSM)) || ($orderCS === 'SD' && $orderPS === 'Processed')) { 

			// ---------------------------------------- //
			//    POST Item Data to GOOGLE ANALYTICS    //
			// ---------------------------------------- //

			$i++; // increase number for each $item
			$itemData = 'itemData'.$i; // append increment number to $itemData variable
			$itemContent = 'itemContent'.$i; // append increment number to $itemContent variable
			$itemCH = 'itemCH'.$i; // append increment number to $itemContent variable
			$itemCID = 'itemCID'.$i; // append increment number to $itemCID variable

			${$itemCID} = $orderCID; // use $orderCID as the GA CID

			${$itemData} = array( // This is an associative array that will contain all the parameters that we'll send to Google Analytics
			  'v' => 1, // The version of the measurement protocol
			  'tid' => 'UA-######-###', // Google Analytics account ID
			  'cid' => ${$itemCID}, // The client ID or UUID
			  't' => 'item' // Hit Type
			);

			${$itemData}['ti'] = $orderTI; // Sets the transaction ID for GA
			${$itemData}['in'] = $itemIN; // Sets the item name for GA
			${$itemData}['ip'] = $itemIP; // Sets the item price for GA
			${$itemData}['iq'] = $itemIQ; // Sets the item quantity for GA
			${$itemData}['cd3'] = $customField2; // Sets a custom dimension (Product Category)
			${$itemData}['cd4'] = $customField5; // Sets a custom dimension (Subid)

			${$itemContent} = http_build_query(${$itemData}); // The body of the post must include exactly 1 URI encoded payload and must be no longer than 8192 bytes. See http_build_query.
			${$itemContent} = utf8_encode(${$itemContent}); // The payload must be UTF-8 encoded.

			${$itemCH} = curl_init();
			curl_setopt(${$itemCH},CURLOPT_USERAGENT, $user_agent);
			curl_setopt(${$itemCH},CURLOPT_URL, $url);
			curl_setopt(${$itemCH},CURLOPT_HTTPHEADER,array('Content-type: application/x-www-form-urlencoded'));
			curl_setopt(${$itemCH},CURLOPT_HTTP_VERSION,CURL_HTTP_VERSION_1_1);
			curl_setopt(${$itemCH},CURLOPT_POST, TRUE);
			curl_setopt(${$itemCH},CURLOPT_POSTFIELDS, ${$itemContent});
			curl_exec(${$itemCH});
			curl_close(${$itemCH});
			
			} // END IF ORDER IS COMPLETED/SHIPPING AND PROCESSED

		} // END foreach($items as $item)
			
} // END foreach ($exports as $export)



// BEGIN $order/$export
foreach ($exports as $export) { foreach($orders as $order) {

	// ---------------------------------------------- //
	//    POST #1 to GOOGLE ANALYTICS (Event Data)    //
	// ---------------------------------------------- //

// ------------------------------------------------------------ //
//    If order is in 'Completed Order' stage -AND- 'Processed'  //
//       -AND- not coming from the 'Shipping Department'        //
//                             -OR-                             //
//        in the 'Shipping Department' -AND- 'Processed'        //
// ------------------------------------------------------------ //

if (($orderCS === 'CO' && $orderPS === 'Processed' && empty($orderSM)) || ($orderCS === 'SD' && $orderPS === 'Processed')) { 

	$data1 = array( // This is an associative array that will contain all the parameters that we'll send to Google Analytics
	  'v' => 1, // The version of the measurement protocol
	  'tid' => 'UA-######-###', // Google Analytics account ID
	  'cid' => $orderCID, // The client ID or UUID
	  't' => 'event' // Hit Type
	);
	
	$data1['dh'] = 'secure.ultracart.com'; // The GA document host name
	$data1['ec'] = 'Sales'; // The GA event category
	$data1['ea'] = 'Order Processed'; // The GA event action
	$data1['el'] = (isset($orderTI) ? $orderTI : 'No Order ID'); // The GA event label
	$data1['cd3'] = $customField2; // Sets a custom dimension (Product Category)
	$data1['cd4'] = $customField5; // Sets a custom dimension (Subid)
	
	$content1 = http_build_query($data1); // The body of the post must include exactly 1 URI encoded payload and must be no longer than 8192 bytes. See http_build_query.
	$content1 = utf8_encode($content1); // The payload must be UTF-8 encoded.
	
	$ch1 = curl_init();
	curl_setopt($ch1,CURLOPT_USERAGENT, $user_agent);
	curl_setopt($ch1,CURLOPT_URL, $url);
	curl_setopt($ch1,CURLOPT_HTTPHEADER,array('Content-type: application/x-www-form-urlencoded'));
	curl_setopt($ch1,CURLOPT_HTTP_VERSION,CURL_HTTP_VERSION_1_1);
	curl_setopt($ch1,CURLOPT_POST, TRUE);
	curl_setopt($ch1,CURLOPT_POSTFIELDS, $content1);
	curl_exec($ch1);
	curl_close($ch1);
	
} // END IF ORDER IS COMPLETED/SHIPPING AND PROCESSED

}} // END $order/$export


//
// NOTICES
//

foreach ($exports as $export) {
	foreach($orders as $order) {
		// Notice: Order processed and placed into 'Completed Orders'.
		if ($orderCS === 'CO' && $orderPS === 'Processed' && empty($orderSM)) { echo 'Order has been processed and placed into Completed Orders. ID is '.$orderTI.', CID is '.$orderCID.' and total is '.$orderTR.'. '; }
		// Notice: Order processed and placed into 'Shipping Department'.
		if ($orderCS === 'SD' && $orderPS === 'Processed') { echo 'Order has been processed and placed into Shipping Department. ID is '.$orderTI.', CID is '.$orderCID.' and total is '.$orderTR.'. '; }
		// Notice: Duplicate order ignored.
		if ($orderCS === 'CO' && $orderPS === 'Processed' && !empty($orderSM)) { echo 'Order from Shipping Department has been ignored. ID is '.$orderTI.' and total is '.$orderTR.'. '; }
		// Notice: Order refunded from 'Completed Orders'.
		if ($orderCS === 'CO' && $orderPS === 'Refunded') { echo 'Order from Completed Orders has been refunded. ID is '.$orderOTI.' and amount refunded is '.$orderRTR.'. Merchant Notes by '.$orderRBU.': '.$orderRMN.'. '; }
		// Notice: Order refunded from 'Shipping Department'.
		if ($orderCS === 'SD' && $orderPS === 'Refunded') { echo 'Order from shipping department has been refunded. ID is '.$orderOTI.' and amount refunded is '.$orderRTR.'. Merchant Notes by '.$orderRBU.': '.$orderRMN.'. '; }
	}
	foreach($items as $item) {
		// Get item data
		$itemIN = $item->getElementsByTagName("item_id")->item(0)->nodeValue;                  // gets the item name
		$itemIP = $item->getElementsByTagName("total_cost_with_discount")->item(0)->nodeValue; // gets the item price
		$itemIQ = $item->getElementsByTagName("quantity")->item(0)->nodeValue;                 // gets the item quantity
		// Notice: Items ordered.
		if ($orderCS === 'CO' && $orderPS === 'Processed' && empty($orderSM) || $orderCS === 'SD' && $orderPS === 'Processed') {echo 'Ordered '.$itemIQ.' of '.$itemIN.' for '.$itemIP.' each. ';}
	} 
}

?>

...