diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 00000000..56986528
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,78 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Overview
+
+This is the **Tagging_GTM** Magento 2 module - an AdPage/Tagging integration for Google Tag Manager. The module pushes e-commerce events and data to the dataLayer for GTM consumption.
+
+- **Package**: `tagginggroup/gtm`
+- **Module name**: `Tagging_GTM`
+- **PHP namespace**: `Tagging\GTM`
+- **Requirements**: Magento 2.3.7+ or 2.4.1+, PHP 7.4 or 8.1+
+
+## Common Commands
+
+```bash
+# Install module
+composer require tagginggroup/gtm
+bin/magento module:enable Tagging_GTM
+bin/magento setup:upgrade
+bin/magento cache:clean
+
+# Run unit tests (requires Magento test framework)
+./vendor/bin/phpunit -c phpunit.xml
+
+# Logs location
+var/log/Tagging_GTM.log
+```
+
+## Architecture
+
+### Core Concepts
+
+The module uses a **Tag-based DataLayer architecture** where data is assembled from Tag, Event, and Processor objects:
+
+1. **Tags** (`Api/Data/TagInterface`): Single-value data providers that implement `get()` - return scalar values or arrays
+2. **Events** (`Api/Data/EventInterface`): Complete event objects (purchase, add_to_cart, etc.) that implement `get(): array`
+3. **Processors** (`Api/Data/ProcessorInterface`): Post-processors that modify the assembled data via `process(array $data): array`
+4. **MergeTagInterface**: Tags that merge their data into the parent array rather than as a nested key
+
+### Data Flow
+
+```
+Layout XML → DataLayer Block → TagParser → dataLayer push
+```
+
+1. Layout XML files configure `data_layer` and `data_layer_events` arrays on the `Tagging_GTM.data-layer` block
+2. `ViewModel/DataLayer.php` retrieves and processes this data
+3. `DataLayer/TagParser.php` recursively resolves Tag/Event objects to their values
+4. Processors are applied last for data modification
+
+### Key Directories
+
+- **DataLayer/Tag/**: Individual tag implementations (PageTitle, CartItems, CurrentProduct, etc.)
+- **DataLayer/Event/**: E-commerce events (Purchase, AddToCart, BeginCheckout, Login, etc.)
+- **DataLayer/Processor/**: Page-specific processors (SuccessPage, Checkout, Cart, Category, Product)
+- **DataLayer/Mapper/**: Data mappers for products, categories, customers
+- **Observer/**: Event observers triggering dataLayer events (AddToCart, Login, Logout, SignUp, etc.)
+- **Plugin/**: Magento plugins for checkout events (shipping info, payment info, search results)
+
+### Configuration
+
+- XML config: `etc/data_layer.xml` - extend default dataLayer values and event data via XML
+- Admin config: **Stores > Configuration > AdPage > AdPage GTM**
+- DI configuration: `etc/di.xml` and `etc/frontend/di.xml`
+
+### Hyva Theme Support
+
+The module includes Hyva theme compatibility via:
+- `view/frontend/layout/hyva_default.xml`
+- `view/frontend/layout/hyva_checkout_index_index.xml`
+- `MageWire/` components for Hyva checkout
+
+### Testing
+
+- Unit tests: `Test/Unit/`
+- Integration tests: `Test/Integration/`
+- Functional tests: `Test/Functional/`
diff --git a/DataLayer/Event/Purchase.php b/DataLayer/Event/Purchase.php
index 078aee32..d7e60cca 100644
--- a/DataLayer/Event/Purchase.php
+++ b/DataLayer/Event/Purchase.php
@@ -1,4 +1,6 @@
- $this->priceFormatter->format((float)$order->getShippingInclTax()),
'coupon' => $order->getCouponCode(),
'items' => $this->orderItems->setOrder($order)->get()
- ]
+ ],
+ 'user_data' => $this->getUserData($order)
+ ];
+ }
+
+ /**
+ * @param OrderInterface $order
+ * @return array
+ */
+ private function getUserData(OrderInterface $order): array
+ {
+ $billingAddress = $order->getBillingAddress();
+ $shippingAddress = $order->getShippingAddress();
+
+ return [
+ 'customer_id' => $order->getCustomerId() ?? '',
+ 'billing_first_name' => $billingAddress ? $billingAddress->getFirstname() ?? '' : '',
+ 'billing_last_name' => $billingAddress ? $billingAddress->getLastname() ?? '' : '',
+ 'billing_address' => $billingAddress && $billingAddress->getStreet() ? $billingAddress->getStreet()[0] ?? '' : '',
+ 'billing_postcode' => $billingAddress ? $billingAddress->getPostcode() ?? '' : '',
+ 'billing_country' => $billingAddress ? $billingAddress->getCountryId() ?? '' : '',
+ 'billing_state' => $billingAddress ? $billingAddress->getRegion() ?? '' : '',
+ 'billing_city' => $billingAddress ? $billingAddress->getCity() ?? '' : '',
+ 'billing_email' => $billingAddress ? $billingAddress->getEmail() ?? '' : '',
+ 'billing_phone' => $billingAddress ? $billingAddress->getTelephone() ?? '' : '',
+ 'shipping_first_name' => $shippingAddress ? $shippingAddress->getFirstname() ?? '' : '',
+ 'shipping_last_name' => $shippingAddress ? $shippingAddress->getLastname() ?? '' : '',
+ 'shipping_company' => $shippingAddress ? $shippingAddress->getCompany() ?? '' : '',
+ 'shipping_address' => $shippingAddress && $shippingAddress->getStreet() ? $shippingAddress->getStreet()[0] ?? '' : '',
+ 'shipping_postcode' => $shippingAddress ? $shippingAddress->getPostcode() ?? '' : '',
+ 'shipping_country' => $shippingAddress ? $shippingAddress->getCountryId() ?? '' : '',
+ 'shipping_state' => $shippingAddress ? $shippingAddress->getRegion() ?? '' : '',
+ 'shipping_city' => $shippingAddress ? $shippingAddress->getCity() ?? '' : '',
+ 'shipping_phone' => $shippingAddress ? $shippingAddress->getTelephone() ?? '' : '',
+ 'email' => $order->getCustomerEmail() ?? '',
+ 'first_name' => $order->getCustomerFirstname() ?? '',
+ 'last_name' => $order->getCustomerLastname() ?? '',
+ 'new_customer' => $order->getCustomerIsGuest() ? 'true' : 'false'
];
}
diff --git a/DataLayer/Tag/Order/UserData.php b/DataLayer/Tag/Order/UserData.php
new file mode 100644
index 00000000..9f624336
--- /dev/null
+++ b/DataLayer/Tag/Order/UserData.php
@@ -0,0 +1,68 @@
+checkoutSession = $checkoutSession;
+ $this->orderRepository = $orderRepository;
+ }
+
+ /**
+ * @return array
+ */
+ public function get(): array
+ {
+ $order = $this->getOrder();
+ $billingAddress = $order->getBillingAddress();
+ $shippingAddress = $order->getShippingAddress();
+
+ return [
+ 'customer_id' => $order->getCustomerId() ?? '',
+ 'billing_first_name' => $billingAddress ? $billingAddress->getFirstname() ?? '' : '',
+ 'billing_last_name' => $billingAddress ? $billingAddress->getLastname() ?? '' : '',
+ 'billing_address' => $billingAddress && $billingAddress->getStreet() ? $billingAddress->getStreet()[0] ?? '' : '',
+ 'billing_postcode' => $billingAddress ? $billingAddress->getPostcode() ?? '' : '',
+ 'billing_country' => $billingAddress ? $billingAddress->getCountryId() ?? '' : '',
+ 'billing_state' => $billingAddress ? $billingAddress->getRegion() ?? '' : '',
+ 'billing_city' => $billingAddress ? $billingAddress->getCity() ?? '' : '',
+ 'billing_email' => $billingAddress ? $billingAddress->getEmail() ?? '' : '',
+ 'billing_phone' => $billingAddress ? $billingAddress->getTelephone() ?? '' : '',
+ 'shipping_first_name' => $shippingAddress ? $shippingAddress->getFirstname() ?? '' : '',
+ 'shipping_last_name' => $shippingAddress ? $shippingAddress->getLastname() ?? '' : '',
+ 'shipping_company' => $shippingAddress ? $shippingAddress->getCompany() ?? '' : '',
+ 'shipping_address' => $shippingAddress && $shippingAddress->getStreet() ? $shippingAddress->getStreet()[0] ?? '' : '',
+ 'shipping_postcode' => $shippingAddress ? $shippingAddress->getPostcode() ?? '' : '',
+ 'shipping_country' => $shippingAddress ? $shippingAddress->getCountryId() ?? '' : '',
+ 'shipping_state' => $shippingAddress ? $shippingAddress->getRegion() ?? '' : '',
+ 'shipping_city' => $shippingAddress ? $shippingAddress->getCity() ?? '' : '',
+ 'shipping_phone' => $shippingAddress ? $shippingAddress->getTelephone() ?? '' : '',
+ 'email' => $order->getCustomerEmail() ?? '',
+ 'first_name' => $order->getCustomerFirstname() ?? '',
+ 'last_name' => $order->getCustomerLastname() ?? '',
+ 'new_customer' => $order->getCustomerIsGuest() ? 'true' : 'false'
+ ];
+ }
+
+ /**
+ * @return OrderInterface
+ */
+ private function getOrder(): OrderInterface
+ {
+ return $this->orderRepository->get($this->checkoutSession->getLastRealOrder()->getId());
+ }
+}
diff --git a/composer.json b/composer.json
index c8fc5186..fa57c5cf 100644
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,6 @@
{
"name": "tagginggroup/gtm",
- "version": "1.5.4",
+ "version": "1.5.5",
"license": "OSL-3.0",
"type": "magento2-module",
"description": "AdPage tagging integration for Magento 2",
diff --git a/view/frontend/layout/checkout_onepage_success.xml b/view/frontend/layout/checkout_onepage_success.xml
index 9536bbcb..d12697fb 100644
--- a/view/frontend/layout/checkout_onepage_success.xml
+++ b/view/frontend/layout/checkout_onepage_success.xml
@@ -24,6 +24,7 @@
- Tagging\GTM\DataLayer\Tag\Order\Order
- Tagging\GTM\DataLayer\Tag\Order\OrderItems
+ - Tagging\GTM\DataLayer\Tag\Order\UserData
diff --git a/view/frontend/templates/hyva/script-additions.phtml b/view/frontend/templates/hyva/script-additions.phtml
index 27634ac9..6b864d6c 100644
--- a/view/frontend/templates/hyva/script-additions.phtml
+++ b/view/frontend/templates/hyva/script-additions.phtml
@@ -38,7 +38,7 @@ $commons = $block->getCommonsViewModel();
}
getSectionNames().forEach(function(sectionName) {
- if (!event.detail.data[sectionName].gtm) {
+ if (!event.detail.data[sectionName] || !event.detail.data[sectionName].gtm) {
return;
}
@@ -52,7 +52,7 @@ $commons = $block->getCommonsViewModel();
let attributes = {};
getSectionNames().forEach(function(sectionName) {
- if (!event.detail.data[sectionName].gtm_events) {
+ if (!event.detail.data[sectionName] || !event.detail.data[sectionName].gtm_events) {
return;
}