Tracking Refunds & Returns Server-Side for Clean ROAS Reporting in OpenCart

Standard

Most eCommerce setups only send purchase data to analytics platforms, leaving out one critical part of the picture: refunds and returns. Without refund tracking, your Return on Ad Spend (ROAS) is inflated and misleading, especially when using platforms like Google Ads, Meta, and GA4.

By using Server-Side Tagging with OpenCart, we can accurately track refunded transactions and send them back to your analytics toolsβ€”ensuring true revenue-based attribution.


βœ… Why Track Refunds?

Reason Impact
Clean ROAS Exclude refunded revenue from conversions
GA4 Attribution Accuracy Maintain realistic funnel reporting
Google Ads Optimization Avoid over-optimized bids for refunded conversions
Meta Ads CAPI Refund signals improve campaign quality


🧰 Requirements

Item Description
OpenCart Version 3.x or 4.x
Server-Side GTM Configured on a first-party domain
Google Ads, Meta CAPI, GA4 Platforms integrated
Order Status Logic To determine when an order is refunded
Consent Layer (Optional) To handle GDPR/CCPA compliance


🧱 Step 1: Detect Refunded Orders in OpenCart Admin

Modify the refund trigger. In OpenCart, you can hook into order status updates.

In admin/controller/sale/order.php, locate or add code where order status is updated to β€œRefunded” (you may need to define a custom status ID, e.g., 11):

if ($order_status_id == 11) { // 11 = Refunded
$order_info = $this->model_sale_order->getOrder($order_id);
$products = $this->model_sale_order->getOrderProducts($order_id);

$items = [];
foreach ($products as $product) {
$items[] = [
'item_id' => $product['model'],
'item_name' => $product['name'],
'quantity' => $product['quantity'],
'price' => $product['price']
];
}

$refund_payload = [
'event_name' => 'refund',
'event_id' => uniqid('refund_', true),
'transaction_id' => $order_info['order_id'],
'currency' => $order_info['currency_code'],
'value' => -1 * $order_info['total'],
'items' => $items
];

$this->sendRefundToServer($refund_payload);
}


πŸ”Œ Step 2: Send Refund Event to Server-Side GTM

Add this custom helper function in the same file or a reusable module:

private function sendRefundToServer($payload) {
$ch = curl_init('https://gtm.yourdomain.com/event');
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type:application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);
}

βœ… This will trigger every time an order is marked as refunded.


🧠 Step 3: Parse Refunds in Server GTM

In your Server Container:

  1. Use the HTTP Request Client
  2. Match incoming event where event_name = refund
  3. Extract transaction_id, items, value, currency, and event_id


πŸ“€ Step 4: Send Refund Event to GA4 via Server GTM

Create a GA4 Event Tag in ssGTM:

Parameter Value
Event Name refund
Transaction ID {{transaction_id}}
Value {{value}}
Currency {{currency}}
Items {{items}}
Event ID {{event_id}}

βœ… This removes refunded purchase value from revenue reports in GA4.


🎯 Step 5: Send Refund to Google Ads

In ssGTM:

  1. Create a Google Ads Conversion Tag
  2. Configure:
    • Conversion ID / Label
    • transaction_id: {{transaction_id}}
    • value: {{value}} (should be negative)
    • currency: {{currency}}
    • event_id: {{event_id}} (deduplication)

βœ… This tells Google Ads the conversion has been reversed.


πŸ“˜ Step 6: Send Refund Event to Meta Conversions API

Use the Meta HTTP tag or a Custom Request:

POST https://graph.facebook.com/v18.0/<PIXEL_ID>/events?access_token=<TOKEN>

{
"data": [{
"event_name": "Refund",
"event_time": {{ timestamp }},
"event_id": "{{event_id}}",
"action_source": "website",
"custom_data": {
"transaction_id": "{{transaction_id}}",
"currency": "{{currency}}",
"value": -99.00
}
}]
}

βœ… Meta will treat it as a revenue correction.


πŸ›‘οΈ Step 7: Consent-Safe Processing

If you’re passing PII (e.g., email for Enhanced Conversions), add this logic:

if ($user_consent_given) {
$payload['email'] = hash('sha256', strtolower($order_info['email']));
}

Or handle consent_granted logic in ssGTM to block tags if needed.


πŸ§ͺ Step 8: QA Your Refund Events

Tool What to Check
ssGTM Preview Inspect payload structure
GA4 DebugView Look for real-time refund event
Google Ads Conversion Tag Diagnostics Check negative conversion
Meta Events Manager β†’ Test Events View refund test data


πŸ“Š Sample Refund Data Sent

{
"event_name": "refund",
"event_id": "refund_664c49d1a3bc9",
"transaction_id": "ORD12842",
"value": -99.99,
"currency": "USD",
"items": [{
"item_id": "PROD001",
"item_name": "Winter Jacket",
"price": 99.99,
"quantity": 1
}]
}


βœ… Benefits Summary

Feature Benefit
True ROAS Calculation Filter out refunded revenue
Attribution Accuracy GA4 funnel metrics adjusted
Ads Optimization Less bias in Smart Bidding or CAPI
First-Party Secure No exposure of refund logic in frontend
Works with All Platforms Google Ads, Meta, GA4, and others


Leave a Reply

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