{"id":1109,"date":"2014-01-11T08:00:26","date_gmt":"2014-01-11T18:00:26","guid":{"rendered":"http:\/\/mymonkeydo.com\/?p=1109"},"modified":"2014-01-11T08:00:26","modified_gmt":"2014-01-11T18:00:26","slug":"google-analytics-measurement-protocol-for-subscriptions","status":"publish","type":"post","link":"https:\/\/mymonkeydo.com\/google-analytics-measurement-protocol-for-subscriptions\/","title":{"rendered":"Google Analytics Measurement Protocol for Subscriptions"},"content":{"rendered":"

Send Analytics Data Directly From Your Server<\/h2>\n

Google Analytics Measurement Protocol<\/a> is available for Google’s Universal Analytics. \u00a0It was released in the first part of 2013 and is as of this writing still in public beta. \u00a0It allows developers to make HTTP requests to send raw data directly to Google Analytics.<\/p>\n

Subscription Renewals for eCommerce Sites<\/h2>\n

I found and used the Measurement Protocol specifically to solve a problem with tracking subscription renewals. \u00a0I was using WooCommerce and the Subscription extension as my eCommerce subscription site and found a couple things not working for me when I was using the built-in Analytics eCommerce tracking.<\/p>\n

For one, I had Universal Analytics but WooCommerce was using the legacy ga.js analytics. \u00a0So nothing was being tracked.<\/p>\n

Secondly, the WooCommerce code to track purchases was done on the Thank You page. \u00a0While this is fine for normal purchases, I wanted to track renewals via the wp cron jobs also.<\/p>\n

How to Use Google Measurement Protocol in WordPress and WooCommerce<\/h2>\n

First, let me say at the time of this writing there are very little examples on implementing the code. \u00a0While Google’s documentation is great, I still had questions. \u00a0This post on using the measurement protocol<\/a>\u00a0from Stu Miller\u00a0got me started and almost completed the work for me. \u00a0I am in no way an expert at WooCommerce or Subscriptions so I didn’t want to put this code into a plugin. \u00a0Also, WooCommerce may soon update their analytics plugin<\/a> making this code obsolete.<\/p>\n

1. The CID – The Client Id<\/h3>\n

The most confusing thing about the Measurement Protocol is how to link up the user to Google’s “anonymous” user. \u00a0As of this writing Google is intending to have a user ID field, but it is not yet available. \u00a0From the google groups forum<\/a>\u00a0and the documentation, the CID field should be either a UUID Version 4 or their legacy anonymous identifier format “X.Y” where each are 32-bit random numbers. \u00a0The X and Y are actually the Google assigned random id and the timestamp which the id was assigned as noted in this stackoverflow post<\/a>.<\/p>\n

So to link a Google Analytics user with your user\/subscriber you would need to parse the “_ga” cookie and save the id.timestamp portion like I do in my_ga_parse_user()<\/em> and my_setup_new_user()<\/em>.<\/p>\n

2. The Renewal Order (WooCommerce)<\/h3>\n

The second most confusing thing (for me anyway) was recording the correct order. \u00a0In WooCommerce every subscription renewal creates a new order. \u00a0In my case the original order is a 7 day free trial and then $10 every month after that. \u00a0So the original order’s total price along with the transaction is $0. \u00a0The renewal order should be $10, but analytics was not recording it. \u00a0The reason is that when a renewal order is created almost all of the original orders postmeta data is copied into the renewal order. \u00a0This means that _ga_tracked<\/em> is also copied. In\u00a0my_ga_track_payment()<\/em> the _ga_tracked<\/em> postemeta is checked first to make sure an order isn’t recorded more than once. \u00a0If this is copied to the renewal order the renewal order will never get tracked.\u00a0 The function hooked to the \u00a0woocommerce_subscriptions_renewal_order_meta_query<\/em> filter solved this problem.<\/p>\n

3. Debugging<\/h3>\n

I had a tough time trying things out in subscriptions because a renewal order is created once a day at most. \u00a0To get around this I created\u00a0my_ga_debug_next_payment()<\/em> to speed up the payment process. \u00a0This method sets the next payment date to 5 minutes from the current time. \u00a0I was using the Stripe payment gateway so I’m not sure if this will work for PayPal or any other gateways.<\/p>\n

Here’s the Code<\/h2>\n
\n\n<?php\n\/**\n* See https:\/\/groups.google.com\/forum\/#!msg\/google-analytics-measurement-protocol\/rE9otWYDFHw\/8JlJJV-UmKcJ\n* a person from Google says cid should be X.Y format.\n* and https:\/\/developers.google.com\/analytics\/devguides\/collection\/protocol\/v1\/devguide\n* Apparently whether hit is recorded or not 200 is returned.\n* Returns true if ga was contacted.\n*\/\nfunction my_ga_fire_hit( $user_id, $data ) {\nif ( !defined( 'GOOGLE_ANALYTICS_ID' ) )\nreturn;\n\n$defaults = array(\n'v' => 1,\n'tid' => GOOGLE_ANALYTICS_ID,\n'cid' => my_ga_user_id($user_id)\n);\n\n$payload_data = array_merge($defaults, $data);\n$endpoint = 'https:\/\/ssl.google-analytics.com\/collect?';\n$parameters = http_build_query( $payload_data );\n$result = wp_remote_post( $endpoint . $parameters );\nif ( isset( $result['response'] ) ) {\nreturn ( $result['code'] == 200 );\n}\nreturn false;\n}\n\n\/**\n* Save the google analytics UUID.\n*\/\nfunction my_ga_parse_user($my_user_options) {\nif ( is_user_logged_in() && isset($_COOKIE['_ga'])) {\n$my_user_options = get_user_meta($user_id, 'my_user_options', true);\nif ( empty($my_user_options) ) {\nlist($version,$domainDepth, $id, $ts) = split('[.]', $_COOKIE[\"_ga\"], 4);\n$contents = array('version' => $version, 'domainDepth' => $domainDepth, 'cid' => $id . '.' . $ts);\n\n\/\/3rd spot is the cid according to multiple sources\n$my_user_options['ga_cid'] = $contents['cid'];\n\nupdate_user_meta( $user_id, 'my_user_options', $my_user_options);\n}\n}\n}\nadd_action('template_redirect', 'my_ga_parse_user');\n\n\/**\n* Return the ga cid. (or what will be called user_id)\n*\/\nfunction my_ga_user_id($user_id) {\n$my_user_options = get_user_meta($user_id, 'my_user_options', true);\n$cid = isset( $my_user_options['ga_cid'] ) ? $my_user_options['ga_cid'] : '';\nif ( empty( $cid ) ) {\n$my_user_options = my_ga_parse_user($my_user_options);\n$cid = isset( $my_user_options['ga_cid'] ) ? $my_user_options['ga_cid'] : '';\n}\nreturn $cid;\n}\n\n\/**\n* Don't copy over _ga_tracked meta.\n*\/\nfunction my_ga_remove_renewal_order_meta( $order_meta_query, $order_id ) {\n$order_meta_query .= \" AND `meta_key` NOT IN (\"\n. \"'_ga_tracked' ) \";\nreturn $order_meta_query;\n}\nadd_filter ( 'woocommerce_subscriptions_renewal_order_meta_query', 'my_ga_remove_renewal_order_meta', 10, 2);\n\n\/**\n* Let ga know a subscription payment has been processed.\n*\/\nfunction my_ga_track_payment( $order_id ) {\n$order = new WC_Order( $order_id );\nif ( empty($order) || get_post_meta( $order->id, '_ga_tracked', true ) == 1 || !defined('GOOGLE_ANALYTICS_ID') )\nreturn;\n\n$tracking_id = GOOGLE_ANALYTICS_ID;\nif ( empty( $tracking_id ) )\nreturn;\n\n$user_id = $order->customer_user;\n\nmy_ga_fire_hit( $user_id, array(\n't' => 'transaction', \/\/ Transaction hit type.\n'ti' => $order->get_order_number(), \/\/ transaction ID. Required.\n'ta' => get_bloginfo('name'), \/\/ Transaction affiliation.\n'tr' => $order->get_total(), \/\/ Transaction revenue.\n\/\/ Transaction shipping. (not required)\n'tt' => $order->get_total_tax() \/\/ Transaction tax.\n) );\n\nif ( $order->get_items() ) {\nforeach ( $order->get_items() as $item ) {\n$_product = $order->get_product_from_item( $item );\n$_category = \"\";\nif ( isset( $_product->variation_data ) ) {\n\n$_category .= woocommerce_get_formatted_variation( $_product->variation_data, true );\n\n}\nelse {\n$out = array();\n$categories = get_the_terms($_product->id, 'product_cat');\nif ( $categories ) {\nforeach ( $categories as $category ){\n$out[] = $category->name;\n}\n}\n$_category .= join( \"\/\", $out);\n}\n$item_name = $item['name'];\n\/\/ If we have a child renewal order, we need the original order's ID\nif ( WC_Subscriptions_Renewal_Order::is_renewal( $order_id, array( 'order_role' => 'child' ) ) ) {\n$original_order_id = WC_Subscriptions_Renewal_Order::get_parent_order_id( $order_id );\n$original_order = new WC_Order( $original_order_id );\n$order_items = WC_Subscriptions_Order::get_recurring_items( $original_order );\nif ( !empty( $order_items ) ) {\n$order_item = end($order_items);\n$item_name = sprintf( __( 'Renewal of \"%s\"', 'lis' ), $order_item['name'] );\n}\n}\n\nmy_ga_fire_hit( $user_id, array(\n't' => 'item', \/\/ Transaction hit type.\n'ti' => $order->get_order_number(), \/\/ transaction ID. Required.\n'in' => $item_name, \/\/ Item name. Required.\n'ip' => $order->get_item_total( $item ), \/\/ Item price.\n'iq' => $item['qty'], \/\/ Item quantity.\n'ic' => $_product->get_sku() ? __( 'SKU:', 'woocommerce' ) . ' ' . $_product->get_sku() : $_product->id, \/\/ Item code \/ SKU.\n'iv' => $_category \/\/ Item variation \/ category.\n) );\n}\n}\n\nupdate_post_meta( $order->id, '_ga_tracked', 1 );\n\n}\nadd_action( 'woocommerce_payment_complete', 'my_ga_track_payment');\n\n\/*\n\/\/ set next payment ot be in 5 minutes.\nfunction my_ga_debug_next_payment() {\n$subscription_key = '1234_5678';\n$user_id = '1';\n\n$next_payment_date = WC_Subscriptions_Manager::get_next_payment_date($subscription_key, $user_id, 'timestamp');\n$new_next_payment_date = strtotime(\"+5 minutes\");\n\nif ($next_payment_date > strtotime(\"+7 minutes\") ) {\n\/\/update the next payment date for referrer\n$ret = WC_Subscriptions_Manager::update_next_payment_date($new_next_payment_date, $subscription_key, $user_id);\n}\n\n}\nadd_action('init', 'my_ga_debug_next_payment');\n*\/\n\n<\/pre>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_genesis_hide_title":false,"_genesis_hide_breadcrumbs":false,"_genesis_hide_singular_image":false,"_genesis_hide_footer_widgets":false,"_genesis_custom_body_class":"","_genesis_custom_post_class":"","_genesis_layout":"","footnotes":""},"categories":[2],"tags":[20,25,26,54,63,64],"yoast_head":"\nGoogle Analytics Measurement Protocol for Subscriptions - My Monkey Do<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/mymonkeydo.com\/google-analytics-measurement-protocol-for-subscriptions\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Google Analytics Measurement Protocol for Subscriptions - My Monkey Do\" \/>\n<meta property=\"og:url\" content=\"https:\/\/mymonkeydo.com\/google-analytics-measurement-protocol-for-subscriptions\/\" \/>\n<meta property=\"og:site_name\" content=\"My Monkey Do\" \/>\n<meta property=\"article:published_time\" content=\"2014-01-11T18:00:26+00:00\" \/>\n<meta name=\"author\" content=\"Webhead\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Webhead\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"7 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/mymonkeydo.com\/google-analytics-measurement-protocol-for-subscriptions\/\",\"url\":\"https:\/\/mymonkeydo.com\/google-analytics-measurement-protocol-for-subscriptions\/\",\"name\":\"Google Analytics Measurement Protocol for Subscriptions - My Monkey Do\",\"isPartOf\":{\"@id\":\"https:\/\/mymonkeydo.com\/#website\"},\"datePublished\":\"2014-01-11T18:00:26+00:00\",\"dateModified\":\"2014-01-11T18:00:26+00:00\",\"author\":{\"@id\":\"https:\/\/mymonkeydo.com\/#\/schema\/person\/b16fc650a8c182faaac896bab099b829\"},\"breadcrumb\":{\"@id\":\"https:\/\/mymonkeydo.com\/google-analytics-measurement-protocol-for-subscriptions\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/mymonkeydo.com\/google-analytics-measurement-protocol-for-subscriptions\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/mymonkeydo.com\/google-analytics-measurement-protocol-for-subscriptions\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/mymonkeydo.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Google Analytics Measurement Protocol for Subscriptions\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/mymonkeydo.com\/#website\",\"url\":\"https:\/\/mymonkeydo.com\/\",\"name\":\"My Monkey Do\",\"description\":\"A Log of Coding Solutions\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/mymonkeydo.com\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/mymonkeydo.com\/#\/schema\/person\/b16fc650a8c182faaac896bab099b829\",\"name\":\"Webhead\",\"url\":\"https:\/\/mymonkeydo.com\/author\/corey\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Google Analytics Measurement Protocol for Subscriptions - My Monkey Do","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/mymonkeydo.com\/google-analytics-measurement-protocol-for-subscriptions\/","og_locale":"en_US","og_type":"article","og_title":"Google Analytics Measurement Protocol for Subscriptions - My Monkey Do","og_url":"https:\/\/mymonkeydo.com\/google-analytics-measurement-protocol-for-subscriptions\/","og_site_name":"My Monkey Do","article_published_time":"2014-01-11T18:00:26+00:00","author":"Webhead","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Webhead","Est. reading time":"7 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/mymonkeydo.com\/google-analytics-measurement-protocol-for-subscriptions\/","url":"https:\/\/mymonkeydo.com\/google-analytics-measurement-protocol-for-subscriptions\/","name":"Google Analytics Measurement Protocol for Subscriptions - My Monkey Do","isPartOf":{"@id":"https:\/\/mymonkeydo.com\/#website"},"datePublished":"2014-01-11T18:00:26+00:00","dateModified":"2014-01-11T18:00:26+00:00","author":{"@id":"https:\/\/mymonkeydo.com\/#\/schema\/person\/b16fc650a8c182faaac896bab099b829"},"breadcrumb":{"@id":"https:\/\/mymonkeydo.com\/google-analytics-measurement-protocol-for-subscriptions\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/mymonkeydo.com\/google-analytics-measurement-protocol-for-subscriptions\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/mymonkeydo.com\/google-analytics-measurement-protocol-for-subscriptions\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/mymonkeydo.com\/"},{"@type":"ListItem","position":2,"name":"Google Analytics Measurement Protocol for Subscriptions"}]},{"@type":"WebSite","@id":"https:\/\/mymonkeydo.com\/#website","url":"https:\/\/mymonkeydo.com\/","name":"My Monkey Do","description":"A Log of Coding Solutions","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/mymonkeydo.com\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/mymonkeydo.com\/#\/schema\/person\/b16fc650a8c182faaac896bab099b829","name":"Webhead","url":"https:\/\/mymonkeydo.com\/author\/corey\/"}]}},"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/mymonkeydo.com\/wp-json\/wp\/v2\/posts\/1109"}],"collection":[{"href":"https:\/\/mymonkeydo.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mymonkeydo.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mymonkeydo.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mymonkeydo.com\/wp-json\/wp\/v2\/comments?post=1109"}],"version-history":[{"count":0,"href":"https:\/\/mymonkeydo.com\/wp-json\/wp\/v2\/posts\/1109\/revisions"}],"wp:attachment":[{"href":"https:\/\/mymonkeydo.com\/wp-json\/wp\/v2\/media?parent=1109"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mymonkeydo.com\/wp-json\/wp\/v2\/categories?post=1109"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mymonkeydo.com\/wp-json\/wp\/v2\/tags?post=1109"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}