GA4 Ecommerce Tracking on Salesforce Commerce Cloud
This guide covers full ecommerce funnel tracking for GA4 on Salesforce Commerce Cloud, from product impressions to purchase confirmation.
Ecommerce Funnel Overview
Product List → Product View → Add to Cart → Checkout → Purchase
↓ ↓ ↓ ↓ ↓
view_item_list view_item add_to_cart begin_checkout purchase
Product List Tracking
Track product impressions on category and search pages.
Category Page Implementation
Controller Extension:
// cartridges/app_custom/cartridge/controllers/Search.js
'use strict';
var server = require('server');
var Search = module.superModule;
server.extend(Search);
server.append('Show', function (req, res, next) {
var viewData = res.getViewData();
var productSearch = viewData.productSearch;
if (productSearch && productSearch.productIds) {
var impressions = [];
productSearch.productIds.forEach(function(productId, index) {
var product = productSearch.getProduct(productId.productSearchHit);
impressions.push({
item_id: productId.productID,
item_name: product.productName,
item_category: viewData.productSearch.category.displayName || '',
item_list_name: 'Category: ' + (viewData.productSearch.category.displayName || 'Search'),
index: index,
price: product.price.sales ? product.price.sales.value : 0
});
});
viewData.ga4Impressions = {
item_list_name: 'Category: ' + (viewData.productSearch.category.displayName || 'Search'),
items: impressions
};
}
res.setViewData(viewData);
return next();
});
module.exports = server.exports();
Client-side Tracking:
// Track product impressions on page load
(function() {
var impressions = window.ga4Impressions;
if (impressions && impressions.items.length > 0) {
gtag('event', 'view_item_list', {
item_list_name: impressions.item_list_name,
items: impressions.items
});
}
})();
// Track product click
$(document).on('click', '.product-tile a', function() {
var $tile = $(this).closest('.product-tile');
var productData = $tile.data('product');
gtag('event', 'select_item', {
item_list_name: window.ga4Impressions.item_list_name,
items: [{
item_id: productData.id,
item_name: productData.name,
item_category: productData.category,
index: $tile.index(),
price: productData.price
}]
});
});
Purchase Tracking
The purchase event is critical for revenue tracking.
Order Confirmation Page
Controller Extension:
// cartridges/app_custom/cartridge/controllers/Order.js
'use strict';
var server = require('server');
var Order = module.superModule;
server.extend(Order);
server.append('Confirm', function (req, res, next) {
var viewData = res.getViewData();
var order = viewData.order;
if (order) {
var items = [];
order.items.items.forEach(function(item) {
items.push({
item_id: item.id,
item_name: item.productName,
item_category: item.categoryName || '',
item_brand: item.brand || '',
price: item.priceTotal.price.sales.value / item.quantity,
quantity: item.quantity,
discount: item.priceTotal.adjustedPrice ?
(item.priceTotal.price.sales.value - item.priceTotal.adjustedPrice.value) : 0
});
});
viewData.ga4Purchase = {
transaction_id: order.orderNumber,
value: order.totals.grandTotal.replace(/[^0-9.]/g, ''),
currency: order.totals.currencyCode,
tax: order.totals.totalTax.replace(/[^0-9.]/g, ''),
shipping: order.totals.totalShippingCost.replace(/[^0-9.]/g, ''),
coupon: order.totals.discounts.length > 0 ?
order.totals.discounts[0].couponCode : '',
items: items
};
}
res.setViewData(viewData);
return next();
});
module.exports = server.exports();
ISML Template Integration:
<isif condition="${pdict.ga4Purchase}">
<script>
gtag('event', 'purchase', {
transaction_id: '${pdict.ga4Purchase.transaction_id}',
value: ${pdict.ga4Purchase.value},
currency: '${pdict.ga4Purchase.currency}',
tax: ${pdict.ga4Purchase.tax},
shipping: ${pdict.ga4Purchase.shipping},
<isif condition="${pdict.ga4Purchase.coupon}">
coupon: '${pdict.ga4Purchase.coupon}',
</isif>
items: <isprint value="${JSON.stringify(pdict.ga4Purchase.items)}" encoding="off"/>
});
</script>
</isif>
Checkout Step Tracking
Track each step of the checkout process.
Checkout Flow Events
// Shipping step
function trackShippingStep(checkoutData) {
gtag('event', 'add_shipping_info', {
currency: checkoutData.currency,
value: checkoutData.subtotal,
coupon: checkoutData.couponCode || '',
shipping_tier: checkoutData.shippingMethodName,
items: checkoutData.items
});
}
// Payment step
function trackPaymentStep(checkoutData) {
gtag('event', 'add_payment_info', {
currency: checkoutData.currency,
value: checkoutData.subtotal,
coupon: checkoutData.couponCode || '',
payment_type: checkoutData.paymentMethodType,
items: checkoutData.items
});
}
Promotion Tracking
Track promotional interactions.
// View promotion
function trackViewPromotion(promoData) {
gtag('event', 'view_promotion', {
creative_name: promoData.creativeName,
creative_slot: promoData.slotName,
promotion_id: promoData.id,
promotion_name: promoData.name
});
}
// Click promotion
function trackSelectPromotion(promoData) {
gtag('event', 'select_promotion', {
creative_name: promoData.creativeName,
creative_slot: promoData.slotName,
promotion_id: promoData.id,
promotion_name: promoData.name
});
}
Refund Tracking
Track order refunds server-side.
// Server-side refund tracking via Measurement Protocol
var https = require('https');
function trackRefund(orderNumber, refundAmount, currency) {
var measurementId = 'G-XXXXXXXXXX';
var apiSecret = 'YOUR_API_SECRET';
var payload = {
client_id: 'SFCC-Backend',
events: [{
name: 'refund',
params: {
transaction_id: orderNumber,
value: refundAmount,
currency: currency
}
}]
};
var postData = JSON.stringify(payload);
var options = {
hostname: 'www.google-analytics.com',
port: 443,
path: '/mp/collect?measurement_id=' + measurementId + '&api_secret=' + apiSecret,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData)
}
};
var req = https.request(options);
req.write(postData);
req.end();
}
Data Quality Checklist
Required Validations
- Transaction ID is unique per order
- Revenue matches order total (excluding tax and shipping)
- Product prices are unit prices, not totals
- Currency code is ISO 4217 format
- No PII in event parameters
Common Issues
| Issue | Cause | Solution |
|---|---|---|
| Duplicate purchases | Page refresh | Use dataLayer.push with event callback |
| Missing revenue | Formatted currency strings | Strip currency symbols |
| Wrong item count | Including bundled items | Count parent products only |
Testing Recommendations
- Use GA4 DebugView for real-time validation
- Test complete funnel from product view to purchase
- Verify refund tracking with test orders
- Check cross-device tracking consistency