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

Axo: Add PayPal Insights integration to the Block Checkout (3770) #2737

Merged
259 changes: 259 additions & 0 deletions modules/ppcp-axo-block/resources/js/plugins/PayPalInsightsLoader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
import { registerPlugin } from '@wordpress/plugins';
import { useEffect, useCallback, useState, useRef } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { PAYMENT_STORE_KEY } from '@woocommerce/block-data';
import PayPalInsights from '../../../../ppcp-axo/resources/js/Insights/PayPalInsights';
import { STORE_NAME } from '../stores/axoStore';
import usePayPalCommerceGateway from '../hooks/usePayPalCommerceGateway';

const GATEWAY_HANDLE = 'ppcp-axo-gateway';

const useEventTracking = () => {
const [ triggeredEvents, setTriggeredEvents ] = useState( {
initialized: false,
jsLoaded: false,
beginCheckout: false,
emailSubmitted: false,
} );

const currentPaymentMethod = useRef( null );

const setEventTriggered = useCallback( ( eventName, value = true ) => {
setTriggeredEvents( ( prev ) => ( {
...prev,
[ eventName ]: value,
} ) );
}, [] );

const isEventTriggered = useCallback(
( eventName ) => triggeredEvents[ eventName ],
[ triggeredEvents ]
);

const setCurrentPaymentMethod = useCallback( ( methodName ) => {
currentPaymentMethod.current = methodName;
}, [] );

const getCurrentPaymentMethod = useCallback(
() => currentPaymentMethod.current,
[]
);

return {
setEventTriggered,
isEventTriggered,
setCurrentPaymentMethod,
getCurrentPaymentMethod,
};
};

const waitForPayPalInsight = () => {
return new Promise( ( resolve, reject ) => {
// If already loaded, resolve immediately
if ( window.paypalInsight ) {
resolve( window.paypalInsight );
return;
}

// Set a reasonable timeout
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 );
}
} );

// Start observing
observer.observe( document, {
childList: true,
subtree: true,
} );
} );
};

const usePayPalInsightsInit = ( axoConfig, ppcpConfig, eventTracking ) => {
const { setEventTriggered, isEventTriggered } = eventTracking;
const initialized = useRef( false );

useEffect( () => {
if (
! axoConfig?.insights?.enabled ||
! axoConfig?.insights?.client_id ||
! axoConfig?.insights?.session_id ||
initialized.current ||
isEventTriggered( 'initialized' )
) {
return;
}

const initializePayPalInsights = async () => {
try {
await waitForPayPalInsight();

if ( initialized.current ) {
return;
}

// Track JS load first
PayPalInsights.trackJsLoad();
setEventTriggered( 'jsLoaded' );

PayPalInsights.config( axoConfig.insights.client_id, {
debug: axoConfig?.wp_debug === '1',
} );

PayPalInsights.setSessionId( axoConfig.insights.session_id );
initialized.current = true;
setEventTriggered( 'initialized' );

if (
isEventTriggered( 'jsLoaded' ) &&
! isEventTriggered( 'beginCheckout' )
) {
PayPalInsights.trackBeginCheckout( {
amount: axoConfig.insights.amount,
page_type: 'checkout',
user_data: {
country: 'US',
is_store_member: false,
},
} );
setEventTriggered( 'beginCheckout' );
}
} catch ( error ) {
console.error(
'PayPal Insights initialization failed:',
error
);
}
};

initializePayPalInsights();

return () => {
initialized.current = false;
};
}, [ axoConfig, ppcpConfig, setEventTriggered, isEventTriggered ] );
};

const usePaymentMethodTracking = ( axoConfig, eventTracking ) => {
const { setCurrentPaymentMethod } = eventTracking;
const lastPaymentMethod = useRef( null );
const isInitialMount = useRef( true );

const activePaymentMethod = useSelect( ( select ) => {
return select( PAYMENT_STORE_KEY )?.getActivePaymentMethod();
}, [] );

const handlePaymentMethodChange = useCallback(
async ( paymentMethod ) => {
// Skip if no payment method or same as last one
if (
! paymentMethod ||
paymentMethod === lastPaymentMethod.current
) {
return;
}

try {
await waitForPayPalInsight();

// Only track if it's not the initial mount, and we have a previous payment method
if ( ! isInitialMount.current && lastPaymentMethod.current ) {
PayPalInsights.trackSelectPaymentMethod( {
payment_method_selected:
axoConfig?.insights?.payment_method_selected_map[
paymentMethod
] || 'other',
page_type: 'checkout',
} );
}

lastPaymentMethod.current = paymentMethod;
setCurrentPaymentMethod( paymentMethod );
} catch ( error ) {
console.error( 'Failed to track payment method:', error );
}
},
[
axoConfig?.insights?.payment_method_selected_map,
setCurrentPaymentMethod,
]
);

useEffect( () => {
if ( activePaymentMethod ) {
if ( isInitialMount.current ) {
// Just set the initial payment method without tracking
lastPaymentMethod.current = activePaymentMethod;
setCurrentPaymentMethod( activePaymentMethod );
isInitialMount.current = false;
} else {
handlePaymentMethodChange( activePaymentMethod );
}
}
}, [
activePaymentMethod,
handlePaymentMethodChange,
setCurrentPaymentMethod,
] );

useEffect( () => {
return () => {
lastPaymentMethod.current = null;
isInitialMount.current = true;
};
}, [] );
};

const PayPalInsightsLoader = () => {
const eventTracking = useEventTracking();
const { setEventTriggered, isEventTriggered } = eventTracking;

const initialConfig =
window?.wc?.wcSettings?.getSetting( `${ GATEWAY_HANDLE }_data` ) || {};

const { ppcpConfig } = usePayPalCommerceGateway( initialConfig );
const axoConfig = window?.wc_ppcp_axo;

const { isEmailSubmitted } = useSelect( ( select ) => {
const storeSelect = select( STORE_NAME );
return {
isEmailSubmitted: storeSelect?.getIsEmailSubmitted?.() ?? false,
};
}, [] );

usePayPalInsightsInit( axoConfig, ppcpConfig, eventTracking );
usePaymentMethodTracking( axoConfig, eventTracking );

useEffect( () => {
const trackEmail = async () => {
if ( isEmailSubmitted && ! isEventTriggered( 'emailSubmitted' ) ) {
try {
await waitForPayPalInsight();
PayPalInsights.trackSubmitCheckoutEmail();
setEventTriggered( 'emailSubmitted' );
} catch ( error ) {
console.error( 'Failed to track email submission:', error );
}
}
};
trackEmail();
}, [ isEmailSubmitted, setEventTriggered, isEventTriggered ] );

return null;
};

registerPlugin( 'wc-ppcp-paypal-insights', {
render: PayPalInsightsLoader,
scope: 'woocommerce-checkout',
} );

export default PayPalInsightsLoader;
16 changes: 9 additions & 7 deletions modules/ppcp-axo-block/resources/js/stores/axoStore.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createReduxStore, register, dispatch } from '@wordpress/data';
import { createReduxStore, register, dispatch, select } from '@wordpress/data';

export const STORE_NAME = 'woocommerce-paypal-payments/axo-block';

Expand Down Expand Up @@ -100,13 +100,15 @@ const selectors = {
};

// Create and register the Redux store for the AXO block
const store = createReduxStore( STORE_NAME, {
reducer,
actions,
selectors,
} );
if ( ! select( STORE_NAME ) ) {
const store = createReduxStore( STORE_NAME, {
reducer,
actions,
selectors,
} );

register( store );
register( store );
}

// Action dispatchers

Expand Down
1 change: 1 addition & 0 deletions modules/ppcp-axo-block/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
$container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.configuration.dcc' ),
$container->get( 'onboarding.environment' ),
$container->get( 'axo.payment_method_selected_map' ),
$container->get( 'wcgateway.url' )
);
},
Expand Down
42 changes: 42 additions & 0 deletions modules/ppcp-axo-block/src/AxoBlockModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,15 @@ static function () use ( $c ) {
wp_enqueue_style( 'wc-ppcp-axo-block' );
}
);

// Enqueue the PayPal Insights script.
add_action(
'wp_enqueue_scripts',
function () use ( $c ) {
$this->enqueue_paypal_insights_script( $c );
}
);

return true;
}

Expand Down Expand Up @@ -166,4 +175,37 @@ private function add_sdk_client_token_to_script_data(

return $localized_script_data;
}

/**
* Enqueues PayPal Insights analytics script for the Checkout block.
*
* @param ContainerInterface $c The service container.
* @return void
*/
private function enqueue_paypal_insights_script( ContainerInterface $c ): void {
if ( ! has_block( 'woocommerce/checkout' ) || WC()->cart->is_empty() ) {
return;
}

$module_url = $c->get( 'axoblock.url' );
$asset_version = $c->get( 'ppcp.asset-version' );

wp_register_script(
'wc-ppcp-paypal-insights',
untrailingslashit( $module_url ) . '/assets/js/PayPalInsightsLoader.js',
array( 'wp-plugins', 'wp-data', 'wp-element', 'wc-blocks-registry' ),
$asset_version,
true
);

wp_localize_script(
'wc-ppcp-paypal-insights',
'ppcpPayPalInsightsData',
array(
'isAxoEnabled' => true,
)
);

wp_enqueue_script( 'wc-ppcp-paypal-insights' );
}
}
Loading
Loading