Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(R2.1f) Google tag enhanced conversion #2258

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
672800e
Update src/Google/GlobalSiteTag.php
ankitrox Feb 16, 2024
e3cf005
E2E: Enhanced conversion user data test
ankitrox Feb 16, 2024
7523ea1
Merge branch 'update/52310672-google-tag-enhanced-conversion' of http…
ankitrox Feb 16, 2024
93f9c4b
Conflict resolve: develop
ankitrox Feb 26, 2024
9ccbba8
Resolve conflict: merge feature/2239-enhanced-conversions-panel
ankitrox Feb 26, 2024
62fb97e
Merge branch 'feature/2239-enhanced-conversions-panel' into update/52…
ankitrox Feb 28, 2024
85e005a
Conditionally add enhanced conversion event
ankitrox Mar 4, 2024
b43997b
Normalize and hash data
ankitrox Mar 4, 2024
a4e289a
Merge branch 'feature/2239-enhanced-conversions-panel' into update/52…
ankitrox Mar 4, 2024
365139c
Fix: algo name
ankitrox Mar 4, 2024
1c57792
Fix: key name for hashed data
ankitrox Mar 7, 2024
ea8cd23
Resolve conflicts: merge develop
ankitrox Mar 13, 2024
e93b854
Add EC data prior to firing events
joemcgill Mar 15, 2024
c0da595
Update e2e tests
joemcgill Mar 15, 2024
1afe1d5
Remove E2E tests for now
joemcgill Mar 15, 2024
ce4441e
Add e2e tests
joemcgill Mar 15, 2024
84343ae
Fix JSDoc errors
joemcgill Mar 16, 2024
8ee59b6
Fix tests
joemcgill Mar 16, 2024
72a2f2a
Enable EC for e2e test
joemcgill Mar 17, 2024
aaa28be
Make disabled test more robust
joemcgill Mar 17, 2024
7d60a3f
Add normalization for email address
ankitrox Mar 20, 2024
447f12a
Resolve conflicts during pull
ankitrox Mar 20, 2024
c2d73c2
Remove hashing from non PII fields and fix merge
joemcgill Mar 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 134 additions & 4 deletions src/Google/GlobalSiteTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\WP;
use Automattic\WooCommerce\GoogleListingsAndAds\Value\BuiltScriptDependencyArray;
use WC_Product;
use WC_Customer;

defined( 'ABSPATH' ) || exit;

Expand Down Expand Up @@ -224,12 +225,13 @@
);
} else {
// Legacy code to support Google Analytics for WooCommerce version < 2.0.0.
$config = wp_json_encode( $this->get_config_object() );

Check warning on line 228 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L228

Added line #L228 was not covered by tests
add_filter(
'woocommerce_gtag_snippet',
function ( $gtag_snippet ) use ( $ads_conversion_id ) {
function ( $gtag_snippet ) use ( $ads_conversion_id, $config ) {

Check warning on line 231 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L231

Added line #L231 was not covered by tests
return preg_replace(
'~(\s)</script>~',
"\tgtag('config', '" . $ads_conversion_id . "', { 'groups': 'GLA', 'send_page_view': false });\n$1</script>",
"\tgtag('config', '" . $ads_conversion_id . "', $config);\n$1</script>",

Check warning on line 234 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L234

Added line #L234 was not covered by tests
$gtag_snippet
);
}
Expand Down Expand Up @@ -277,9 +279,11 @@
* @param string $ads_conversion_id Google Ads account conversion ID.
*/
protected function get_gtag_config( string $ads_conversion_id ) {
$config = $this->get_config_object();

Check warning on line 282 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L282

Added line #L282 was not covered by tests
return sprintf(
'gtag("config", "%1$s", { "groups": "GLA", "send_page_view": false });',
esc_js( $ads_conversion_id )
'gtag("config", "%1$s", %2$s);',
esc_js( $ads_conversion_id ),
wp_json_encode( $config )

Check warning on line 286 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L284-L286

Added lines #L284 - L286 were not covered by tests
);
}

Expand Down Expand Up @@ -344,6 +348,73 @@
$order->update_meta_data( self::ORDER_CONVERSION_META_KEY, 1 );
$order->save_meta_data();

// Prepare and enqueue the enhanced conversion data, if enabled.
if ( $this->is_enhanced_conversion_enabled() ) {

Check warning on line 352 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L352

Added line #L352 was not covered by tests
// Enhanced conversion data.
$ec_data = [];
$email = $order->get_billing_email();
$fname = $order->get_billing_first_name();
$lname = $order->get_billing_last_name();
$phone = $order->get_billing_phone();
$billing_address = $order->get_billing_address_1();
$postcode = $order->get_billing_postcode();
$city = $order->get_billing_city();
$region = $order->get_billing_state();
$country = $order->get_billing_country();

Check warning on line 363 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L354-L363

Added lines #L354 - L363 were not covered by tests

// Add email in EC data.
if ( ! empty( $email ) ) {
$normalized_email = strtolower( $email );
$email_parts = explode( '@', $normalized_email );

Check warning on line 368 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L366-L368

Added lines #L366 - L368 were not covered by tests

if ( count( $email_parts ) > 1 && preg_match( '/^(gmail|googlemail)\.com\s*/', $email_parts[1] ) ) {
$email_parts[0] = str_replace( '.', '', $email_parts[0] );
$normalized_email = sprintf( '%s@%s', $email_parts[0], $email_parts[1] );

Check warning on line 372 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L370-L372

Added lines #L370 - L372 were not covered by tests
}

$ec_data['sha256_email_address'] = $this->normalize_and_hash( $normalized_email );

Check warning on line 375 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L375

Added line #L375 was not covered by tests
}

// Format phone number in IE64.
$phone = preg_replace( '/[^0-9]/', '', $phone );
$phone_length = strlen( $phone );
if ( $phone_length > 9 && $phone_length < 14 ) {
$phone = sprintf( '%s%d', '+', $phone );
$ec_data['sha256_phone_number'] = $this->normalize_and_hash( $phone );

Check warning on line 383 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L379-L383

Added lines #L379 - L383 were not covered by tests
}

// Check for required address fields.
if ( ! empty( $fname ) && ! empty( $lname ) && ! empty( $postcode ) && ! empty( $country ) ) {
$ec_data['address']['sha256_first_name'] = $this->normalize_and_hash( $fname );
$ec_data['address']['sha256_last_name'] = $this->normalize_and_hash( $lname );
$ec_data['address']['postal_code'] = $postcode;
$ec_data['address']['country'] = $country;

Check warning on line 391 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L387-L391

Added lines #L387 - L391 were not covered by tests

/**
* Add additional data, if present.
*/

// Add street address.
if ( ! empty( $billing_address ) ) {
$ec_data['address']['street'] = $billing_address;

Check warning on line 399 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L398-L399

Added lines #L398 - L399 were not covered by tests
}

// Add city.
if ( ! empty( $city ) ) {
$ec_data['address']['city'] = $city;

Check warning on line 404 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L403-L404

Added lines #L403 - L404 were not covered by tests
}

// Add region.
if ( ! empty( $region ) ) {
$ec_data['address']['region'] = $region;

Check warning on line 409 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L408-L409

Added lines #L408 - L409 were not covered by tests
}
}

$purchase_user_data_gtag = sprintf( 'gtag("set", "user_data", %s)', wp_json_encode( $ec_data ) );

Check warning on line 413 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L413

Added line #L413 was not covered by tests

$this->add_inline_event_script( $purchase_user_data_gtag );

Check warning on line 415 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L415

Added line #L415 was not covered by tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is working, as in it's adding the snippet to the page:

gtag( "set", "user_data", {
	"sha256_email_address": "2eb9d564b813d572e4ed03bb5a58459a93caa71b7b227338b682fd038b291a3d",
	"sha256_phone_number": "422ce82c6fc1724ac878042f7d055653ab5e983d186e616826a72d4384b68af8",
	"address": {
		"sha256_first_name": "96d9632f363564cc3032521409cf22a852f2032eec099ed5967c0d000cec607a",
		"sha256_last_name": "799ef92a11af918e3fb741df42934f3b568ed2d93ac1df74f1b8d41a27932a6f",
		"sha256_postal_code": "ea4ffb75adc0903ab5ff284da63b2abe9f3591415e97226a77751b93acfd5d3e",
		"sha256_country": "292c1980ba2805512acfef5d0cf8f43fba5c7b9b73a5a7afad1c37cfacad3c98",
		"sha256_street": "6008c26f4452392acb19374bc12a5ec0c360ae17356bce8b786fb128c8720951",
		"sha256_city": "11a62c23412b77477a71481aa2dc7323bcc61d076c8449076c4c58a8356c1bb1",
		"sha256_region": "18ac3e7343f016890c510e93f935261169d9e3f565436429830faf0934f4f8e4"
	}
} )

But when I look at the tracking data sent to Google I'm not seeing the valid data being sent. I'm following the instructions: Validate your implementation using Chrome Developer Tools

I can see the regular data about the event:

data: event=conversion

But this part I'm not seeing:

Look for a parameter “em” with a hashed string as the value. The value should start with “tv.1~em” followed by a long string of characters. If you see the "em" parameter, this means that the enhanced conversions tag is picking up and hashing the enhanced_conversion_data object.

How can I confirm it is actually including the data? It seems the E2E tests are also just checking for the user_data being sent in the page, and not for the data being sent in the tracking request. That would make it closer to a unit test if it confirms the data is output in the page, seeing expected data appear in the tracking request like is done for the other tests would be more of an E2E test.

The other way to validate the data is to check the Ads account, but it says I have to wait 48hours for that, so I'll have to get back to you if it ever appears.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I can't see the em property as mentioned in the document, but I think if its possible we should wait to check if it is being reflected in ads account after 48 hours as we did all the steps according to the documentation.

}

$conversion_gtag_info =
sprintf(
'gtag("event", "conversion", {
Expand Down Expand Up @@ -417,6 +488,7 @@
esc_js( $language ),
join( ',', $item_info ),
);

$this->add_inline_event_script( $purchase_page_gtag );
}

Expand Down Expand Up @@ -583,4 +655,62 @@
false
);
}

/**
* Get the config object for Google tag.
*
* @return array
*/
private function get_config_object(): array {

Check warning on line 664 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L664

Added line #L664 was not covered by tests
// Standard config.
$config = [
'groups' => 'GLA',
'send_page_view' => false,
];

Check warning on line 669 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L666-L669

Added lines #L666 - L669 were not covered by tests

// Check if enhanced conversion is enabled.
if ( $this->is_enhanced_conversion_enabled() ) {
$config['allow_enhanced_conversions'] = true;

Check warning on line 673 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L672-L673

Added lines #L672 - L673 were not covered by tests
}

return $config;

Check warning on line 676 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L676

Added line #L676 was not covered by tests
}

/**
* Checks if enhanced conversion is enabled.
*
* @return bool
*/
private function is_enhanced_conversion_enabled(): bool {

Check warning on line 684 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L684

Added line #L684 was not covered by tests
// Check if enhanced conversion is enabled.
$enhanced_conversion_status = $this->options->get( OptionsInterface::ADS_ENHANCED_CONVERSION_STATUS, null );
return ( 'enabled' === $enhanced_conversion_status );

Check warning on line 687 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L686-L687

Added lines #L686 - L687 were not covered by tests
}

/**
* Return the hashed data to be sent to Google Ads for enhanced conversion.
*
* @param string $value Data that needs to be hashed.
* @param string $algo Algorithm for hashing.
* @param bool $trim_intermediate_spaces Whether to trim intermediate spaces in values. Default true.
*
* @return string
*/
private function normalize_and_hash( string $value, $algo = 'sha256', bool $trim_intermediate_spaces = true ): string {
if ( empty( $value ) ) {
return '';

Check warning on line 701 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L699-L701

Added lines #L699 - L701 were not covered by tests
}

// Convert case to lowercase.
$normalized_value = strtolower( $value );

Check warning on line 705 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L705

Added line #L705 was not covered by tests

if ( $trim_intermediate_spaces ) {
$normalized_value = str_replace( ' ', '', $normalized_value );

Check warning on line 708 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L707-L708

Added lines #L707 - L708 were not covered by tests
} else {
// Remove leading and trailing whitespaces.
$normalized_value = trim( $normalized_value );

Check warning on line 711 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L711

Added line #L711 was not covered by tests
}

return hash( $algo, strtolower( trim( $normalized_value ) ) );

Check warning on line 714 in src/Google/GlobalSiteTag.php

View check run for this annotation

Codecov / codecov/patch

src/Google/GlobalSiteTag.php#L714

Added line #L714 was not covered by tests
}
}
48 changes: 47 additions & 1 deletion tests/e2e/specs/gtag-events/gtag-events.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
createSimpleProduct,
setConversionID,
clearConversionID,
enableEnhancedConversions,
disableEnhancedConversions,
} from '../../utils/api';
import {
blockProductAddToCart,
Expand All @@ -18,7 +20,11 @@ import {
singleProductAddToCart,
} from '../../utils/customer';
import { createBlockShopPage } from '../../utils/block-page';
import { getEventData, trackGtagEvent } from '../../utils/track-event';
import {
getEventData,
trackGtagEvent,
getDataLayerValue,
} from '../../utils/track-event';

const config = require( '../../config/default' );
const productPrice = config.products.simple.regularPrice;
Expand All @@ -28,6 +34,7 @@ let simpleProductID;
test.describe( 'GTag events', () => {
test.beforeAll( async () => {
await setConversionID();
await enableEnhancedConversions();
simpleProductID = await createSimpleProduct();
} );

Expand Down Expand Up @@ -192,4 +199,43 @@ test.describe( 'GTag events', () => {
expect( data.country ).toEqual( 'US' );
} );
} );

test( 'User data for enhanced conversions are not sent when not enabled', async ( {
page,
} ) => {
await disableEnhancedConversions();
await singleProductAddToCart( page, simpleProductID );

await checkout( page );

const dataConfig = await getDataLayerValue( page, {
type: 'config',
key: 'AW-123456',
} );

expect( dataConfig ).toBeDefined();
expect( dataConfig.allow_enhanced_conversions ).toBeUndefined();
} );

test( 'User data for enhanced conversions is sent when enabled', async ( {
page,
} ) => {
await enableEnhancedConversions();
await singleProductAddToCart( page, simpleProductID );

await checkout( page );

const dataConfig = await getDataLayerValue( page, {
type: 'config',
key: 'AW-123456',
} );

const dataUserData = await getDataLayerValue( page, {
type: 'set',
key: 'user_data',
} );

expect( dataConfig.allow_enhanced_conversions ).toBeTruthy();
expect( dataUserData.sha256_email_address ).toBeDefined();
} );
} );
40 changes: 40 additions & 0 deletions tests/e2e/test-data/test-data.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ function register_routes() {
],
],
);
register_rest_route(
'wc/v3',
'gla-test/enhanced-conversions',
[
[
'methods' => 'POST',
'callback' => __NAMESPACE__ . '\enable_enhanced_conversions',
'permission_callback' => __NAMESPACE__ . '\permissions',
],
[
'methods' => 'DELETE',
'callback' => __NAMESPACE__ . '\disable_enhanced_conversions',
'permission_callback' => __NAMESPACE__ . '\permissions',
],
],
);
}

/**
Expand Down Expand Up @@ -107,6 +123,30 @@ function clear_conversion_id() {
$options->delete( OptionsInterface::ADS_CONVERSION_ACTION );
}

/**
* Enable enhanced conversions.
*/
function enable_enhanced_conversions() {
/** @var OptionsInterface $options */
$options = woogle_get_container()->get( OptionsInterface::class );
$options->update(
OptionsInterface::ADS_ENHANCED_CONVERSION_STATUS,
'enabled'
);
}

/**
* Disable enhanced conversions.
*/
function disable_enhanced_conversions() {
/** @var OptionsInterface $options */
$options = woogle_get_container()->get( OptionsInterface::class );
$options->update(
OptionsInterface::ADS_ENHANCED_CONVERSION_STATUS,
'disabled'
);
}

/**
* Check permissions for API requests.
*/
Expand Down
14 changes: 14 additions & 0 deletions tests/e2e/utils/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,17 @@ export async function setOnboardedMerchant() {
export async function clearOnboardedMerchant() {
await api().delete( 'gla-test/onboarded-merchant' );
}

/**
* Enable Enhanced Conversions.
*/
export async function enableEnhancedConversions() {
await api().post( 'gla-test/enhanced-conversions' );
}

/**
* Disable Enhanced Conversions.
*/
export async function disableEnhancedConversions() {
await api().delete( 'gla-test/enhanced-conversions' );
}
16 changes: 16 additions & 0 deletions tests/e2e/utils/track-event.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,19 @@ export function getEventData( request ) {

return data;
}

/**
* Find a value in the dataLayer
*
* @param {Page} page
* @param {Object} args The type and key to search for.
*/
export function getDataLayerValue( page, args ) {
return page.evaluate( ( { type, key } ) => {
return (
window.dataLayer.find(
( item ) => item[ 0 ] === type && item[ 1 ] === key
)[ 2 ] ?? null
);
}, args );
}
Loading