Axo: Add PayPal Insights to the Block Checkout and fix the existing Classic Checkout integration

This commit is contained in:
Daniel Dudzic 2024-10-29 11:04:41 +01:00
parent c89da49e8f
commit 1204edb1ca
No known key found for this signature in database
GPG key ID: 31B40D33E3465483
12 changed files with 570 additions and 119 deletions

View file

@ -53,7 +53,7 @@ class AxoManager {
cardView = null;
constructor( namespace, axoConfig, ppcpConfig ) {
this.namespace = namespace;
this.namespace = namespace;
this.axoConfig = axoConfig;
this.ppcpConfig = ppcpConfig;
@ -116,7 +116,7 @@ class AxoManager {
this.axoConfig?.insights?.session_id
) {
PayPalInsights.config( this.axoConfig?.insights?.client_id, {
debug: true,
debug: axoConfig?.wp_debug === '1',
} );
PayPalInsights.setSessionId( this.axoConfig?.insights?.session_id );
PayPalInsights.trackJsLoad();
@ -159,19 +159,25 @@ class AxoManager {
}
registerEventHandlers() {
// Payment method change tracking with duplicate prevention
let lastSelectedPaymentMethod = document.querySelector(
'input[name=payment_method]:checked'
)?.value;
this.$( document ).on(
'change',
'input[name=payment_method]',
( ev ) => {
const map = {
'ppcp-axo-gateway': 'card',
'ppcp-gateway': 'paypal',
};
PayPalInsights.trackSelectPaymentMethod( {
payment_method_selected: map[ ev.target.value ] || 'other',
page_type: 'checkout',
} );
if ( lastSelectedPaymentMethod !== ev.target.value ) {
PayPalInsights.trackSelectPaymentMethod( {
payment_method_selected:
this.axoConfig?.insights
?.payment_method_selected_map[
ev.target.value
] || 'other',
page_type: 'checkout',
} );
lastSelectedPaymentMethod = ev.target.value;
}
}
);
@ -1155,16 +1161,6 @@ class AxoManager {
this.el.axoNonceInput.get().value = nonce;
PayPalInsights.trackEndCheckout( {
amount: this.axoConfig?.insights?.amount,
page_type: 'checkout',
payment_method_selected: 'card',
user_data: {
country: 'US',
is_store_member: false,
},
} );
if ( data ) {
// Ryan flow.
const form = document.querySelector( 'form.woocommerce-checkout' );

View file

@ -0,0 +1,99 @@
import PayPalInsights from '../../../../ppcp-axo/resources/js/Insights/PayPalInsights';
class EndCheckoutTracker {
constructor() {
this.initialize();
}
async initialize() {
const axoConfig = window.wc_ppcp_axo_insights_data || {};
if (
axoConfig?.enabled === '1' &&
axoConfig?.client_id &&
axoConfig?.session_id &&
axoConfig?.orderTotal &&
axoConfig?.orderCurrency
) {
try {
await this.waitForPayPalInsight();
PayPalInsights.config( axoConfig?.client_id, {
debug: axoConfig?.wp_debug === '1',
} );
PayPalInsights.setSessionId( axoConfig.session_id );
PayPalInsights.trackJsLoad();
const trackingData = {
amount: {
currency_code: axoConfig?.orderCurrency,
value: axoConfig?.orderTotal,
},
page_type: 'checkout',
payment_method_selected:
axoConfig?.payment_method_selected_map[
axoConfig?.paymentMethod
] || 'other',
user_data: {
country: 'US',
is_store_member: false,
},
order_id: axoConfig?.orderId,
order_key: axoConfig?.orderKey,
};
PayPalInsights.trackEndCheckout( trackingData );
} catch ( error ) {
console.error(
'EndCheckoutTracker: Error during tracking:',
error
);
console.error( 'PayPalInsights object:', window.paypalInsight );
}
} else {
console.warn(
'EndCheckoutTracker: Missing required configuration',
{
enabled: axoConfig?.enabled,
hasClientId: !! axoConfig?.client_id,
hasSessionId: !! axoConfig?.session_id,
hasOrderTotal: !! axoConfig?.orderTotal,
hasOrderCurrency: !! axoConfig?.orderCurrency,
}
);
}
}
waitForPayPalInsight() {
return new Promise( ( resolve, reject ) => {
// If already loaded, resolve immediately
if ( window.paypalInsight ) {
resolve( window.paypalInsight );
return;
}
const timeoutId = setTimeout( () => {
observer.disconnect();
reject( new Error( 'PayPal Insights script load timeout' ) );
}, 10000 );
// Create MutationObserver to watch for script initialization
const observer = new MutationObserver( () => {
if ( window.paypalInsight ) {
observer.disconnect();
clearTimeout( timeoutId );
resolve( window.paypalInsight );
}
} );
observer.observe( document, {
childList: true,
subtree: true,
} );
} );
}
}
document.addEventListener( 'DOMContentLoaded', () => {
new EndCheckoutTracker();
} );

View file

@ -18,6 +18,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;
use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter;
return array(
@ -64,6 +65,7 @@ return array(
$container->get( 'session.handler' ),
$container->get( 'wcgateway.settings' ),
$container->get( 'onboarding.environment' ),
$container->get( 'axo.insights' ),
$container->get( 'wcgateway.settings.status' ),
$container->get( 'api.shop.currency.getter' ),
$container->get( 'woocommerce.logger.woocommerce' ),
@ -89,6 +91,52 @@ return array(
);
},
// Data needed for the PayPal Insights.
'axo.insights' => static function ( ContainerInterface $container ): array {
$settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
$currency = $container->get( 'api.shop.currency.getter' );
assert( $currency instanceof CurrencyGetter );
return array(
'enabled' => defined( 'WP_DEBUG' ) && WP_DEBUG,
'client_id' => ( $settings->has( 'client_id' ) ? $settings->get( 'client_id' ) : null ),
'session_id' => substr(
method_exists( WC()->session, 'get_customer_unique_id' ) ?
md5( WC()->session->get_customer_unique_id() ) :
'',
0,
16
),
'amount' => array(
'currency_code' => $currency->get(),
),
'payment_method_selected_map' => $container->get( 'axo.payment_method_selected_map' ),
'wp_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG,
);
},
// The mapping of payment methods to the PayPal Insights 'payment_method_selected' types.
'axo.payment_method_selected_map' => static function ( ContainerInterface $container ): array {
return array(
'ppcp-axo-gateway' => 'card',
'ppcp-credit-card-gateway' => 'card',
'ppcp-gateway' => 'paypal',
'ppcp-googlepay' => 'google_pay',
'ppcp-applepay' => 'apple_pay',
'ppcp-multibanco' => 'other',
'ppcp-trustly' => 'other',
'ppcp-p24' => 'other',
'ppcp-mybank' => 'other',
'ppcp-ideal' => 'other',
'ppcp-eps' => 'other',
'ppcp-blik' => 'other',
'ppcp-bancontact' => 'other',
'ppcp-card-button-gateway' => 'card',
);
},
/**
* The matrix which countries and currency combinations can be used for AXO.
*/

View file

@ -52,6 +52,13 @@ class AxoManager {
*/
private $environment;
/**
* Data needed for the PayPal Insights.
*
* @var array
*/
private array $insights_data;
/**
* The Settings status helper.
*
@ -95,6 +102,7 @@ class AxoManager {
* @param SessionHandler $session_handler The Session handler.
* @param Settings $settings The Settings.
* @param Environment $environment The environment object.
* @param array $insights_data Data needed for the PayPal Insights.
* @param SettingsStatus $settings_status The Settings status helper.
* @param CurrencyGetter $currency The getter of the 3-letter currency code of the shop.
* @param LoggerInterface $logger The logger.
@ -106,6 +114,7 @@ class AxoManager {
SessionHandler $session_handler,
Settings $settings,
Environment $environment,
array $insights_data,
SettingsStatus $settings_status,
CurrencyGetter $currency,
LoggerInterface $logger,
@ -117,6 +126,7 @@ class AxoManager {
$this->session_handler = $session_handler;
$this->settings = $settings;
$this->environment = $environment;
$this->insights_data = $insights_data;
$this->settings_status = $settings_status;
$this->currency = $currency;
$this->logger = $logger;
@ -161,7 +171,7 @@ class AxoManager {
*
* @return array
*/
private function script_data() {
private function script_data(): array {
return array(
'environment' => array(
'is_sandbox' => $this->environment->current_environment() === 'sandbox',
@ -169,20 +179,10 @@ class AxoManager {
'widgets' => array(
'email' => 'render',
),
'insights' => array(
'enabled' => defined( 'WP_DEBUG' ) && WP_DEBUG,
'client_id' => ( $this->settings->has( 'client_id' ) ? $this->settings->get( 'client_id' ) : null ),
'session_id' =>
substr(
method_exists( WC()->session, 'get_customer_unique_id' ) ? md5( WC()->session->get_customer_unique_id() ) : '',
0,
16
),
'amount' => array(
'currency_code' => $this->currency->get(),
'value' => WC()->cart->get_total( 'numeric' ),
),
),
// The amount is not available when setting the insights data, so we need to merge it here.
'insights' => ( function( array $data ): array {
$data['amount']['value'] = WC()->cart->get_total( 'numeric' );
return $data; } )( $this->insights_data ),
'style_options' => array(
'root' => array(
'backgroundColor' => $this->settings->has( 'axo_style_root_bg_color' ) ? $this->settings->get( 'axo_style_root_bg_color' ) : '',

View file

@ -318,6 +318,15 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
$endpoint->handle_request();
}
);
// Enqueue the PayPal Insights script.
add_action(
'wp_enqueue_scripts',
function () use ( $c ) {
$this->enqueue_paypal_insights_script_on_order_received( $c );
}
);
return true;
}
@ -429,8 +438,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
* @return bool
*/
private function is_excluded_endpoint(): bool {
// Exclude the Order Pay endpoint.
return is_wc_endpoint_url( 'order-pay' );
// Exclude the Order Pay and Order Received endpoints.
return is_wc_endpoint_url( 'order-pay' ) || is_wc_endpoint_url( 'order-received' );
}
/**
@ -451,4 +460,57 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
$axo_enabled ? 'enabled' : 'disabled'
);
}
/**
* Enqueues PayPal Insights on the Order Received endpoint.
*
* @param ContainerInterface $c The service container.
* @return void
*/
private function enqueue_paypal_insights_script_on_order_received( ContainerInterface $c ): void {
global $wp;
if ( ! isset( $wp->query_vars['order-received'] ) ) {
return;
}
$order_id = absint( $wp->query_vars['order-received'] );
if ( ! $order_id ) {
return;
}
$order = wc_get_order( $order_id );
if ( ! $order || ! $order instanceof \WC_Order ) {
return;
}
$module_url = $c->get( 'axo.url' );
$asset_version = $c->get( 'ppcp.asset-version' );
$insights_data = $c->get( 'axo.insights' );
wp_register_script(
'wc-ppcp-paypal-insights-end-checkout',
untrailingslashit( $module_url ) . '/assets/js/TrackEndCheckout.js',
array( 'wp-plugins', 'wp-data', 'wp-element', 'wc-blocks-registry' ),
$asset_version,
true
);
wp_localize_script(
'wc-ppcp-paypal-insights-end-checkout',
'wc_ppcp_axo_insights_data',
array_merge(
$insights_data,
array(
'orderId' => $order_id,
'orderTotal' => (string) $order->get_total(),
'orderCurrency' => (string) $order->get_currency(),
'paymentMethod' => (string) $order->get_payment_method(),
'orderKey' => (string) $order->get_order_key(),
)
)
);
wp_enqueue_script( 'wc-ppcp-paypal-insights-end-checkout' );
}
}

View file

@ -1,39 +1,44 @@
const path = require('path');
const path = require( 'path' );
const isProduction = process.env.NODE_ENV === 'production';
const DependencyExtractionWebpackPlugin = require( '@woocommerce/dependency-extraction-webpack-plugin' );
module.exports = {
devtool: isProduction ? 'source-map' : 'eval-source-map',
mode: isProduction ? 'production' : 'development',
target: 'web',
plugins: [ new DependencyExtractionWebpackPlugin() ],
entry: {
'boot': path.resolve('./resources/js/boot.js'),
'styles': path.resolve('./resources/css/styles.scss')
},
output: {
path: path.resolve(__dirname, 'assets/'),
filename: 'js/[name].js',
},
module: {
rules: [{
test: /\.js?$/,
exclude: /node_modules/,
loader: 'babel-loader',
},
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
{
loader: 'file-loader',
options: {
name: 'css/[name].css',
}
},
{loader:'sass-loader'}
]
}]
}
devtool: isProduction ? 'source-map' : 'eval-source-map',
mode: isProduction ? 'production' : 'development',
target: 'web',
plugins: [ new DependencyExtractionWebpackPlugin() ],
entry: {
boot: path.resolve( './resources/js/boot.js' ),
styles: path.resolve( './resources/css/styles.scss' ),
TrackEndCheckout: path.resolve(
'./resources/js/Insights/TrackEndCheckout.js'
),
},
output: {
path: path.resolve( __dirname, 'assets/' ),
filename: 'js/[name].js',
},
module: {
rules: [
{
test: /\.js?$/,
exclude: /node_modules/,
loader: 'babel-loader',
},
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
{
loader: 'file-loader',
options: {
name: 'css/[name].css',
},
},
{ loader: 'sass-loader' },
],
},
],
},
};