Overview
GA4 Enhanced Ecommerce provides comprehensive tracking of the customer journey from product discovery to purchase completion. This includes:
- Product impressions and clicks
- Add to cart and remove from cart
- Checkout steps and abandonment
- Transaction and revenue tracking
- Refunds and returns
Implementation Approaches
Approach 1: Premium Extension (Recommended)
Premium marketplace extensions provide complete ecommerce tracking out-of-the-box:
iSenseLabs Enhanced Google Analytics 4
- Automatic ecommerce event tracking
- Purchase conversion tracking
- Product performance analytics
- Customer lifetime value tracking
Installation
Admin Panel > Extensions > Installer > Upload extension
Admin Panel > Extensions > Extensions > Analytics > Install
Admin Panel > Extensions > Extensions > Analytics > Edit > Configure
Enable ecommerce features:
- Enhanced Ecommerce: Enabled
- Track Transactions: Enabled
- Track Refunds: Enabled
- Product Impressions: Enabled
Approach 2: Custom Implementation
For full control or custom requirements, implement ecommerce tracking manually.
Product Impression Tracking
Track when products are viewed in listings (category pages, search results, homepage).
Category Page Impressions
File: catalog/controller/product/category.php
Add to the index() method after product loading:
// After $data['products'] is populated
$data['ga4_items'] = array();
foreach ($data['products'] as $index => $product) {
$data['ga4_items'][] = array(
'item_id' => $product['product_id'],
'item_name' => $product['name'],
'item_list_name' => 'Category: ' . $this->document->getTitle(),
'item_category' => $this->getCategoryPath($category_id),
'price' => $this->currency->format($product['price'], $this->session->data['currency'], '', false),
'index' => $index,
'quantity' => 1
);
}
File: catalog/view/theme/[your-theme]/template/product/category.twig
Add before closing </body> or in footer:
{% if ga4_items %}
<script>
gtag('event', 'view_item_list', {
'item_list_id': 'category_{{ category_id }}',
'item_list_name': '{{ heading_title }}',
'items': {{ ga4_items|json_encode|raw }}
});
</script>
{% endif %}
Product Click Tracking
File: catalog/view/theme/[your-theme]/template/product/category.twig
Add click handler for product links:
<script>
$(document).ready(function() {
$('.product-thumb').each(function(index) {
$(this).on('click', 'a', function() {
var productName = $(this).closest('.product-thumb').find('.caption h4 a').text().trim();
var productId = $(this).attr('href').match(/product_id=(\d+)/);
if (productId) {
gtag('event', 'select_item', {
'item_list_id': 'category_{{ category_id }}',
'item_list_name': '{{ heading_title }}',
'items': [{
'item_id': productId[1],
'item_name': productName,
'index': index
}]
});
}
});
});
});
</script>
Product Detail View Tracking
File: catalog/controller/product/product.php
Add to index() method:
// After product data is loaded
if ($product_info) {
// Get category name
$categories = $this->model_catalog_product->getCategories($product_id);
$category_name = '';
if ($categories) {
$category_info = $this->model_catalog_category->getCategory($categories[0]['category_id']);
$category_name = $category_info ? $category_info['name'] : '';
}
$data['ga4_product'] = array(
'item_id' => $product_info['product_id'],
'item_name' => $product_info['name'],
'item_category' => $category_name,
'price' => $this->currency->format($product_info['price'], $this->session->data['currency'], '', false),
'currency' => $this->session->data['currency']
);
}
File: catalog/view/theme/[your-theme]/template/product/product.twig
{% if ga4_product %}
<script>
gtag('event', 'view_item', {
'currency': '{{ ga4_product.currency }}',
'value': {{ ga4_product.price }},
'items': [{{ ga4_product|json_encode|raw }}]
});
</script>
{% endif %}
Add to Cart Tracking
Frontend Implementation
File: catalog/view/theme/[your-theme]/template/product/product.twig
<script>
$('#button-cart').on('click', function() {
var productId = $('input[name="product_id"]').val();
var productName = $('h1').first().text().trim();
var quantity = parseInt($('input[name="quantity"]').val());
var priceText = $('.price-new').length ? $('.price-new').text() : $('.price').text();
var price = parseFloat(priceText.replace(/[^0-9.]/g, ''));
gtag('event', 'add_to_cart', {
'currency': '{{ currency_code }}',
'value': price * quantity,
'items': [{
'item_id': productId,
'item_name': productName,
'price': price,
'quantity': quantity
}]
});
});
</script>
AJAX Add to Cart (with Success Confirmation)
File: catalog/view/theme/[your-theme]/template/common/cart.twig
Modify the cart add function:
<script>
var cart = {
'add': function(product_id, quantity) {
$.ajax({
url: 'index.php?route=checkout/cart/add',
type: 'post',
data: 'product_id=' + product_id + '&quantity=' + (typeof(quantity) != 'undefined' ? quantity : 1),
dataType: 'json',
success: function(json) {
if (json['success']) {
// Get product data from page
var productName = $('h1').first().text().trim();
var price = parseFloat($('.price-new, .price').first().text().replace(/[^0-9.]/g, ''));
var qty = typeof(quantity) != 'undefined' ? quantity : 1;
// Track add to cart
gtag('event', 'add_to_cart', {
'currency': '{{ currency_code }}',
'value': price * qty,
'items': [{
'item_id': product_id,
'item_name': productName,
'price': price,
'quantity': qty
}]
});
// Original success notification code
$('#cart > button').html('<i class="fa fa-shopping-cart"></i> ' + json['total']);
$('html, body').animate({ scrollTop: 0 }, 'slow');
$('#cart > ul').load('index.php?route=common/cart/info ul li');
}
}
});
}
};
</script>
Remove from Cart Tracking
File: catalog/view/theme/[your-theme]/template/checkout/cart.twig
<script>
$(document).on('click', '.btn-danger[onclick*="cart.remove"]', function() {
var row = $(this).closest('tr');
var productName = row.find('td:eq(1) a').text().trim();
var productId = $(this).attr('onclick').match(/cart\.remove\('(\d+)'\)/)[1];
var price = parseFloat(row.find('.text-right:eq(0)').text().replace(/[^0-9.]/g, ''));
var quantity = parseInt(row.find('input[name^="quantity"]').val());
gtag('event', 'remove_from_cart', {
'currency': '{{ currency_code }}',
'value': price * quantity,
'items': [{
'item_id': productId,
'item_name': productName,
'price': price,
'quantity': quantity
}]
});
});
</script>
Checkout Process Tracking
Begin Checkout
File: catalog/controller/checkout/checkout.php
Add to index() method:
// Get cart items for GA4
$data['ga4_cart_items'] = array();
$cart_value = 0;
foreach ($this->cart->getProducts() as $product) {
$price = $this->currency->format($product['price'], $this->session->data['currency'], '', false);
$cart_value += $price * $product['quantity'];
$data['ga4_cart_items'][] = array(
'item_id' => $product['product_id'],
'item_name' => $product['name'],
'price' => $price,
'quantity' => $product['quantity']
);
}
$data['ga4_cart_value'] = $cart_value;
File: catalog/view/theme/[your-theme]/template/checkout/checkout.twig
<script>
// Fire begin_checkout on checkout page load
gtag('event', 'begin_checkout', {
'currency': '{{ currency_code }}',
'value': {{ ga4_cart_value }},
'items': {{ ga4_cart_items|json_encode|raw }}
});
</script>
Checkout Steps
Track each step of the checkout process:
File: catalog/view/theme/[your-theme]/template/checkout/checkout.twig
<script>
// Track shipping info
$(document).on('click', '#button-shipping-method', function() {
var shippingMethod = $('input[name="shipping_method"]:checked').val();
gtag('event', 'add_shipping_info', {
'currency': '{{ currency_code }}',
'value': {{ ga4_cart_value }},
'shipping_tier': shippingMethod,
'items': {{ ga4_cart_items|json_encode|raw }}
});
});
// Track payment info
$(document).on('click', '#button-payment-method', function() {
var paymentMethod = $('input[name="payment_method"]:checked').val();
gtag('event', 'add_payment_info', {
'currency': '{{ currency_code }}',
'value': {{ ga4_cart_value }},
'payment_type': paymentMethod,
'items': {{ ga4_cart_items|json_encode|raw }}
});
});
</script>
Purchase Tracking
Success Page Tracking
File: catalog/controller/checkout/success.php
Add to index() method:
// Get order data
$order_id = $this->session->data['order_id'];
if ($order_id) {
$this->load->model('checkout/order');
$order_info = $this->model_checkout_order->getOrder($order_id);
if ($order_info) {
// Get order products
$order_products = $this->model_checkout_order->getOrderProducts($order_id);
$data['ga4_transaction'] = array(
'transaction_id' => $order_id,
'value' => $this->currency->format($order_info['total'], $order_info['currency_code'], '', false),
'tax' => $this->currency->format($order_info['total'] - $order_info['total'] / (1 + ($order_info['tax'] / 100)), $order_info['currency_code'], '', false),
'shipping' => $this->currency->format($order_info['shipping'], $order_info['currency_code'], '', false),
'currency' => $order_info['currency_code'],
'coupon' => isset($order_info['coupon']) ? $order_info['coupon'] : '',
'items' => array()
);
foreach ($order_products as $product) {
$data['ga4_transaction']['items'][] = array(
'item_id' => $product['product_id'],
'item_name' => $product['name'],
'price' => $this->currency->format($product['price'], $order_info['currency_code'], '', false),
'quantity' => $product['quantity']
);
}
}
// Clear order_id from session to prevent duplicate tracking
unset($this->session->data['order_id']);
}
File: catalog/view/theme/[your-theme]/template/common/success.twig
{% if ga4_transaction %}
<script>
// Track purchase
gtag('event', 'purchase', {
'transaction_id': '{{ ga4_transaction.transaction_id }}',
'value': {{ ga4_transaction.value }},
'tax': {{ ga4_transaction.tax }},
'shipping': {{ ga4_transaction.shipping }},
'currency': '{{ ga4_transaction.currency }}',
{% if ga4_transaction.coupon %}
'coupon': '{{ ga4_transaction.coupon }}',
{% endif %}
'items': {{ ga4_transaction.items|json_encode|raw }}
});
</script>
{% endif %}
Prevent Duplicate Purchase Events
Add session check to prevent tracking on page refresh:
// In success.php controller
if ($order_id && !isset($this->session->data['ga4_tracked_' . $order_id])) {
// Transaction tracking code here
// Mark as tracked
$this->session->data['ga4_tracked_' . $order_id] = true;
}
Refund Tracking
Admin-Side Refund Tracking
File: admin/controller/sale/order.php
Add custom method for refund tracking:
public function addRefund() {
$this->load->model('sale/order');
if (isset($this->request->get['order_id'])) {
$order_id = $this->request->get['order_id'];
$order_info = $this->model_sale_order->getOrder($order_id);
if ($order_info) {
// Track refund in GA4
$this->trackRefund($order_id, $order_info);
// Original refund processing code
}
}
}
private function trackRefund($order_id, $order_info) {
// You can implement server-side tracking via Measurement Protocol
// or log refund data for batch processing
$ga4_measurement_id = $this->config->get('analytics_ga4_measurement_id');
$ga4_api_secret = $this->config->get('analytics_ga4_api_secret');
if ($ga4_measurement_id && $ga4_api_secret) {
$order_products = $this->model_sale_order->getOrderProducts($order_id);
$items = array();
foreach ($order_products as $product) {
$items[] = array(
'item_id' => $product['product_id'],
'item_name' => $product['name'],
'price' => $product['price'],
'quantity' => $product['quantity']
);
}
$payload = array(
'client_id' => $order_info['customer_id'] ?: 'guest_' . $order_id,
'events' => array(
array(
'name' => 'refund',
'params' => array(
'transaction_id' => $order_id,
'value' => $order_info['total'],
'currency' => $order_info['currency_code'],
'items' => $items
)
)
)
);
// Send to GA4 Measurement Protocol
$url = 'https://www.google-analytics.com/mp/collect?measurement_id=' . $ga4_measurement_id . '&api_secret=' . $ga4_api_secret;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);
}
}
Testing Ecommerce Tracking
1. Enable Debug Mode
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true
});
2. Complete Test Purchase
- Add products to cart
- Proceed through checkout
- Complete a test order
- Verify each event in GA4 DebugView
3. Check Event Sequence
Expected event flow:
view_item_list(category page)select_item(click product)view_item(product page)add_to_cart(add to cart)begin_checkout(checkout page)add_shipping_info(shipping selected)add_payment_info(payment selected)purchase(order success)
4. Verify in GA4 Reports
After 24-48 hours, check:
- Reports > Monetization > Ecommerce purchases
- Reports > Monetization > Purchase journey
- Reports > Monetization > Product performance
Common Issues
Purchase Events Not Tracking
Problem: Success page loads but no purchase event
Solutions:
- Check order_id is available in session
- Verify currency formatting (must be number, not string)
- Check for JavaScript errors on success page
- Ensure GA4 code loads before event fires
Duplicate Purchases
Problem: Same transaction tracked multiple times
Solutions:
- Implement session-based duplicate prevention
- Clear order_id from session after tracking
- Use cookie to mark transaction as tracked
- Check for multiple GA4 codes on page
Incorrect Revenue Values
Problem: Revenue doesn't match actual order totals
Solutions:
- Verify currency conversion is disabled:
$this->currency->format($price, $currency, '', false) // Fourth parameter FALSE prevents formatting - Check tax inclusion/exclusion
- Verify decimal separator (use period, not comma)
Multi-Currency Setup
For stores with multiple currencies:
// Always track in base currency
$base_currency = $this->config->get('config_currency');
$transaction_value = $this->currency->convert(
$order_info['total'],
$order_info['currency_code'],
$base_currency
);
$data['ga4_transaction'] = array(
'value' => $transaction_value,
'currency' => $base_currency,
// ... other fields
);
Or track in transaction currency:
gtag('event', 'purchase', {
'transaction_id': '{{ order_id }}',
'value': {{ order_total }},
'currency': '{{ order_currency }}', // USD, EUR, GBP, etc.
'items': [...]
});