diff --git a/modules/ppcp-axo-block/resources/js/components/Card/Card.js b/modules/ppcp-axo-block/resources/js/components/Card/Card.js
index 51dfa15ae..3bf39b10b 100644
--- a/modules/ppcp-axo-block/resources/js/components/Card/Card.js
+++ b/modules/ppcp-axo-block/resources/js/components/Card/Card.js
@@ -5,7 +5,7 @@ import { STORE_NAME } from '../../stores/axoStore';
const cardIcons = {
VISA: 'visa-light.svg',
- MASTER_CARD: 'mastercard-light.svg',
+ MASTERCARD: 'mastercard-light.svg',
AMEX: 'amex-light.svg',
DISCOVER: 'discover-light.svg',
DINERS: 'dinersclub-light.svg',
diff --git a/modules/ppcp-axo-block/resources/js/components/Card/CardChangeButton.js b/modules/ppcp-axo-block/resources/js/components/Card/CardChangeButton.js
index 1779d67b6..c2a4eaa65 100644
--- a/modules/ppcp-axo-block/resources/js/components/Card/CardChangeButton.js
+++ b/modules/ppcp-axo-block/resources/js/components/Card/CardChangeButton.js
@@ -1,6 +1,13 @@
import { createElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
+/**
+ * Renders a button to change the selected card in the checkout process.
+ *
+ * @param {Object} props
+ * @param {Function} props.onChangeButtonClick - Callback function to handle the click event.
+ * @return {JSX.Element} The rendered button as an anchor tag.
+ */
const CardChangeButton = ( { onChangeButtonClick } ) =>
createElement(
'a',
@@ -9,7 +16,9 @@ const CardChangeButton = ( { onChangeButtonClick } ) =>
'wc-block-checkout-axo-block-card__edit wc-block-axo-change-link',
role: 'button',
onClick: ( event ) => {
+ // Prevent default anchor behavior
event.preventDefault();
+ // Call the provided click handler
onChangeButtonClick();
},
},
diff --git a/modules/ppcp-axo-block/resources/js/components/Card/CardChangeButtonManager.js b/modules/ppcp-axo-block/resources/js/components/Card/CardChangeButtonManager.js
index c38e6c0b6..2b6deba4f 100644
--- a/modules/ppcp-axo-block/resources/js/components/Card/CardChangeButtonManager.js
+++ b/modules/ppcp-axo-block/resources/js/components/Card/CardChangeButtonManager.js
@@ -1,6 +1,13 @@
import { createElement, createRoot, useEffect } from '@wordpress/element';
import CardChangeButton from './CardChangeButton';
+/**
+ * Manages the insertion and removal of the CardChangeButton in the DOM.
+ *
+ * @param {Object} props
+ * @param {Function} props.onChangeButtonClick - Callback function for when the card change button is clicked.
+ * @return {null} This component doesn't render any visible elements directly.
+ */
const CardChangeButtonManager = ( { onChangeButtonClick } ) => {
useEffect( () => {
const radioLabelElement = document.getElementById(
@@ -8,14 +15,17 @@ const CardChangeButtonManager = ( { onChangeButtonClick } ) => {
);
if ( radioLabelElement ) {
+ // Check if the change button doesn't already exist
if (
! radioLabelElement.querySelector(
'.wc-block-checkout-axo-block-card__edit'
)
) {
+ // Create a new container for the button
const buttonContainer = document.createElement( 'div' );
radioLabelElement.appendChild( buttonContainer );
+ // Create a React root and render the CardChangeButton
const root = createRoot( buttonContainer );
root.render(
createElement( CardChangeButton, { onChangeButtonClick } )
@@ -23,6 +33,7 @@ const CardChangeButtonManager = ( { onChangeButtonClick } ) => {
}
}
+ // Cleanup function to remove the button when the component unmounts
return () => {
const button = document.querySelector(
'.wc-block-checkout-axo-block-card__edit'
@@ -33,6 +44,7 @@ const CardChangeButtonManager = ( { onChangeButtonClick } ) => {
};
}, [ onChangeButtonClick ] );
+ // This component doesn't render anything directly
return null;
};
diff --git a/modules/ppcp-axo-block/resources/js/components/Card/utils.js b/modules/ppcp-axo-block/resources/js/components/Card/utils.js
index 93f903142..915511885 100644
--- a/modules/ppcp-axo-block/resources/js/components/Card/utils.js
+++ b/modules/ppcp-axo-block/resources/js/components/Card/utils.js
@@ -1,18 +1,31 @@
import { createElement, createRoot } from '@wordpress/element';
import CardChangeButtonManager from './CardChangeButtonManager';
+/**
+ * Injects a card change button into the DOM.
+ *
+ * @param {Function} onChangeButtonClick - Callback function for when the card change button is clicked.
+ */
export const injectCardChangeButton = ( onChangeButtonClick ) => {
+ // Create a container for the button
const container = document.createElement( 'div' );
document.body.appendChild( container );
+
+ // Render the CardChangeButtonManager in the new container
createRoot( container ).render(
createElement( CardChangeButtonManager, { onChangeButtonClick } )
);
};
+/**
+ * Removes the card change button from the DOM if it exists.
+ */
export const removeCardChangeButton = () => {
const button = document.querySelector(
'.wc-block-checkout-axo-block-card__edit'
);
+
+ // Remove the button's parent node if it exists
if ( button && button.parentNode ) {
button.parentNode.remove();
}
diff --git a/modules/ppcp-axo-block/resources/js/components/EmailButton/EmailButton.js b/modules/ppcp-axo-block/resources/js/components/EmailButton/EmailButton.js
index ab41f067e..a7fcbb86d 100644
--- a/modules/ppcp-axo-block/resources/js/components/EmailButton/EmailButton.js
+++ b/modules/ppcp-axo-block/resources/js/components/EmailButton/EmailButton.js
@@ -2,7 +2,15 @@ import { STORE_NAME } from '../../stores/axoStore';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
+/**
+ * Renders a submit button for email input in the AXO checkout process.
+ *
+ * @param {Object} props
+ * @param {Function} props.handleSubmit - Function to handle button click/submit.
+ * @return {JSX.Element|null} The rendered button or null if conditions are not met.
+ */
const EmailButton = ( { handleSubmit } ) => {
+ // Select relevant states from the AXO store
const { isGuest, isAxoActive, isEmailSubmitted } = useSelect(
( select ) => ( {
isGuest: select( STORE_NAME ).getIsGuest(),
@@ -11,6 +19,7 @@ const EmailButton = ( { handleSubmit } ) => {
} )
);
+ // Only render the button for guests when AXO is active
if ( ! isGuest || ! isAxoActive ) {
return null;
}
@@ -24,6 +33,7 @@ const EmailButton = ( { handleSubmit } ) => {
}` }
disabled={ isEmailSubmitted }
>
+ { /* Button text */ }
{
>
{ __( 'Continue', 'woocommerce-paypal-payments' ) }
+ { /* Loading spinner */ }
{ isEmailSubmitted && (
{
if ( ! emailInput ) {
emailInput = document.getElementById( 'email' );
@@ -18,6 +24,11 @@ const getEmailInput = () => {
return emailInput;
};
+/**
+ * Sets up email functionality for AXO checkout.
+ *
+ * @param {Function} onEmailSubmit - Callback function to handle email submission.
+ */
export const setupEmailFunctionality = ( onEmailSubmit ) => {
const input = getEmailInput();
if ( ! input ) {
@@ -28,6 +39,7 @@ export const setupEmailFunctionality = ( onEmailSubmit ) => {
return;
}
+ // Handler for email submission
const handleEmailSubmit = async () => {
const isEmailSubmitted = wp.data
.select( STORE_NAME )
@@ -50,6 +62,7 @@ export const setupEmailFunctionality = ( onEmailSubmit ) => {
}
};
+ // Set up keydown handler for Enter key
keydownHandler = ( event ) => {
const isAxoActive = wp.data.select( STORE_NAME ).getIsAxoActive();
if ( event.key === 'Enter' && isAxoActive ) {
@@ -78,6 +91,7 @@ export const setupEmailFunctionality = ( onEmailSubmit ) => {
);
}
+ // Function to render the EmailButton
const renderButton = () => {
if ( submitButtonReference.root ) {
submitButtonReference.root.render(
@@ -90,12 +104,15 @@ export const setupEmailFunctionality = ( onEmailSubmit ) => {
renderButton();
- // Subscribe to state changes
+ // Subscribe to state changes and re-render button
submitButtonReference.unsubscribe = wp.data.subscribe( () => {
renderButton();
} );
};
+/**
+ * Removes email functionality and cleans up event listeners and DOM elements.
+ */
export const removeEmailFunctionality = () => {
const input = getEmailInput();
if ( input && keydownHandler ) {
@@ -120,6 +137,11 @@ export const removeEmailFunctionality = () => {
keydownHandler = null;
};
+/**
+ * Checks if email functionality is currently set up.
+ *
+ * @return {boolean} True if email functionality is set up, false otherwise.
+ */
export const isEmailFunctionalitySetup = () => {
return !! submitButtonReference.root;
};
diff --git a/modules/ppcp-axo-block/resources/js/components/Payment/Payment.js b/modules/ppcp-axo-block/resources/js/components/Payment/Payment.js
index d6c96c51c..e1191c8ce 100644
--- a/modules/ppcp-axo-block/resources/js/components/Payment/Payment.js
+++ b/modules/ppcp-axo-block/resources/js/components/Payment/Payment.js
@@ -4,8 +4,18 @@ import { __ } from '@wordpress/i18n';
import { Card } from '../Card';
import { STORE_NAME } from '../../stores/axoStore';
+/**
+ * Renders the payment component based on the user's state (guest or authenticated).
+ *
+ * @param {Object} props
+ * @param {Object} props.fastlaneSdk - The Fastlane SDK instance.
+ * @param {Function} props.onPaymentLoad - Callback function when payment component is loaded.
+ * @return {JSX.Element} The rendered payment component.
+ */
export const Payment = ( { fastlaneSdk, onPaymentLoad } ) => {
const [ isCardElementReady, setIsCardElementReady ] = useState( false );
+
+ // Select relevant states from the AXO store
const { isGuest, isEmailLookupCompleted } = useSelect(
( select ) => ( {
isGuest: select( STORE_NAME ).getIsGuest(),
@@ -31,16 +41,22 @@ export const Payment = ( { fastlaneSdk, onPaymentLoad } ) => {
onPaymentLoad,
] );
+ // Set card element ready when guest email lookup is completed
useEffect( () => {
if ( isGuest && isEmailLookupCompleted ) {
setIsCardElementReady( true );
}
}, [ isGuest, isEmailLookupCompleted ] );
+ // Load payment component when dependencies change
useEffect( () => {
loadPaymentComponent();
}, [ loadPaymentComponent ] );
+ // Conditional rendering based on user state:
+ // 1. If authenticated: Render the Card component
+ // 2. If guest with completed email lookup: Render the card fields
+ // 3. If guest without completed email lookup: Render a message to enter email
if ( isGuest ) {
if ( isEmailLookupCompleted ) {
return ;
diff --git a/modules/ppcp-axo-block/resources/js/components/Shipping/ShippingChangeButton.js b/modules/ppcp-axo-block/resources/js/components/Shipping/ShippingChangeButton.js
index b7d55d508..a68de9789 100644
--- a/modules/ppcp-axo-block/resources/js/components/Shipping/ShippingChangeButton.js
+++ b/modules/ppcp-axo-block/resources/js/components/Shipping/ShippingChangeButton.js
@@ -1,11 +1,20 @@
import { __ } from '@wordpress/i18n';
+/**
+ * Renders a button to change the shipping address.
+ *
+ * @param {Object} props
+ * @param {Function} props.onChangeShippingAddressClick - Callback function to handle the click event.
+ * @return {JSX.Element} The rendered button as an anchor tag.
+ */
const ShippingChangeButton = ( { onChangeShippingAddressClick } ) => (
{
+ // Prevent default anchor behavior
event.preventDefault();
+ // Call the provided click handler
onChangeShippingAddressClick();
} }
>
diff --git a/modules/ppcp-axo-block/resources/js/components/Shipping/ShippingChangeButtonManager.js b/modules/ppcp-axo-block/resources/js/components/Shipping/ShippingChangeButtonManager.js
index e072f0193..abbd7e4da 100644
--- a/modules/ppcp-axo-block/resources/js/components/Shipping/ShippingChangeButtonManager.js
+++ b/modules/ppcp-axo-block/resources/js/components/Shipping/ShippingChangeButtonManager.js
@@ -1,22 +1,32 @@
import { useEffect, createRoot } from '@wordpress/element';
import ShippingChangeButton from './ShippingChangeButton';
+/**
+ * Manages the insertion and removal of the ShippingChangeButton in the DOM.
+ *
+ * @param {Object} props
+ * @param {Function} props.onChangeShippingAddressClick - Callback function for when the shipping change button is clicked.
+ * @return {null} This component doesn't render any visible elements directly.
+ */
const ShippingChangeButtonManager = ( { onChangeShippingAddressClick } ) => {
useEffect( () => {
const shippingHeading = document.querySelector(
'#shipping-fields .wc-block-components-checkout-step__heading'
);
+ // Check if the shipping heading exists and doesn't already have a change button
if (
shippingHeading &&
! shippingHeading.querySelector(
'.wc-block-checkout-axo-block-card__edit'
)
) {
+ // Create a new span element to contain the ShippingChangeButton
const spanElement = document.createElement( 'span' );
spanElement.className = 'wc-block-checkout-axo-block-card__edit';
shippingHeading.appendChild( spanElement );
+ // Create a React root and render the ShippingChangeButton
const root = createRoot( spanElement );
root.render(
{
/>
);
+ // Cleanup function to remove the button when the component unmounts
return () => {
root.unmount();
spanElement.remove();
@@ -33,6 +44,7 @@ const ShippingChangeButtonManager = ( { onChangeShippingAddressClick } ) => {
}
}, [ onChangeShippingAddressClick ] );
+ // This component doesn't render anything directly
return null;
};
diff --git a/modules/ppcp-axo-block/resources/js/components/Shipping/utils.js b/modules/ppcp-axo-block/resources/js/components/Shipping/utils.js
index 1bce1ea3f..a131da555 100644
--- a/modules/ppcp-axo-block/resources/js/components/Shipping/utils.js
+++ b/modules/ppcp-axo-block/resources/js/components/Shipping/utils.js
@@ -1,14 +1,23 @@
import { createRoot } from '@wordpress/element';
import ShippingChangeButtonManager from './ShippingChangeButtonManager';
+/**
+ * Injects a shipping change button into the DOM if it doesn't already exist.
+ *
+ * @param {Function} onChangeShippingAddressClick - Callback function for when the shipping change button is clicked.
+ */
export const injectShippingChangeButton = ( onChangeShippingAddressClick ) => {
+ // Check if the button already exists
const existingButton = document.querySelector(
'#shipping-fields .wc-block-checkout-axo-block-card__edit'
);
if ( ! existingButton ) {
+ // Create a new container for the button
const container = document.createElement( 'div' );
document.body.appendChild( container );
+
+ // Render the ShippingChangeButtonManager in the new container
createRoot( container ).render(
{
}
};
+/**
+ * Removes the shipping change button from the DOM if it exists.
+ */
export const removeShippingChangeButton = () => {
const span = document.querySelector(
'#shipping-fields .wc-block-checkout-axo-block-card__edit'
diff --git a/modules/ppcp-axo-block/resources/js/components/Watermark/Watermark.js b/modules/ppcp-axo-block/resources/js/components/Watermark/Watermark.js
index fcc098768..2ec89de90 100644
--- a/modules/ppcp-axo-block/resources/js/components/Watermark/Watermark.js
+++ b/modules/ppcp-axo-block/resources/js/components/Watermark/Watermark.js
@@ -1,6 +1,15 @@
import { useEffect, useRef } from '@wordpress/element';
import { log } from '../../../../../ppcp-axo/resources/js/Helper/Debug';
+/**
+ * Watermark component for displaying AXO watermark.
+ *
+ * @param {Object} props
+ * @param {Object} props.fastlaneSdk - The Fastlane SDK instance.
+ * @param {string} [props.name='fastlane-watermark-container'] - ID for the watermark container.
+ * @param {boolean} [props.includeAdditionalInfo=true] - Whether to include additional info in the watermark.
+ * @return {JSX.Element} The watermark container element.
+ */
const Watermark = ( {
fastlaneSdk,
name = 'fastlane-watermark-container',
@@ -10,15 +19,19 @@ const Watermark = ( {
const watermarkRef = useRef( null );
useEffect( () => {
+ /**
+ * Renders the Fastlane watermark.
+ */
const renderWatermark = async () => {
if ( ! containerRef.current ) {
return;
}
- // Clear the container
+ // Clear the container before rendering
containerRef.current.innerHTML = '';
try {
+ // Create and render the Fastlane watermark
const watermark = await fastlaneSdk.FastlaneWatermarkComponent(
{
includeAdditionalInfo,
@@ -34,6 +47,7 @@ const Watermark = ( {
renderWatermark();
+ // Cleanup function to clear the container on unmount
return () => {
if ( containerRef.current ) {
containerRef.current.innerHTML = '';
@@ -41,6 +55,7 @@ const Watermark = ( {
};
}, [ fastlaneSdk, name, includeAdditionalInfo ] );
+ // Render the container for the watermark
return ;
};
diff --git a/modules/ppcp-axo-block/resources/js/components/Watermark/WatermarkManager.js b/modules/ppcp-axo-block/resources/js/components/Watermark/WatermarkManager.js
index 41c078585..ef175a98f 100644
--- a/modules/ppcp-axo-block/resources/js/components/Watermark/WatermarkManager.js
+++ b/modules/ppcp-axo-block/resources/js/components/Watermark/WatermarkManager.js
@@ -7,7 +7,15 @@ import {
updateWatermarkContent,
} from './utils';
+/**
+ * Manages the lifecycle and content of the AXO watermark.
+ *
+ * @param {Object} props
+ * @param {Object} props.fastlaneSdk - The Fastlane SDK instance.
+ * @return {null} This component doesn't render any visible elements.
+ */
const WatermarkManager = ( { fastlaneSdk } ) => {
+ // Select relevant states from the AXO store
const isGuest = useSelect( ( select ) =>
select( STORE_NAME ).getIsGuest()
);
@@ -20,6 +28,7 @@ const WatermarkManager = ( { fastlaneSdk } ) => {
useEffect( () => {
if ( isAxoActive || ( ! isAxoActive && ! isAxoScriptLoaded ) ) {
+ // Create watermark container and update content when AXO is active or loading
createWatermarkContainer();
updateWatermarkContent( {
isAxoActive,
@@ -28,12 +37,15 @@ const WatermarkManager = ( { fastlaneSdk } ) => {
isGuest,
} );
} else {
+ // Remove watermark when AXO is inactive and not loading
removeWatermark();
}
+ // Cleanup function to remove watermark on unmount
return removeWatermark;
}, [ fastlaneSdk, isGuest, isAxoActive, isAxoScriptLoaded ] );
+ // This component doesn't render anything directly
return null;
};
diff --git a/modules/ppcp-axo-block/resources/js/components/Watermark/utils.js b/modules/ppcp-axo-block/resources/js/components/Watermark/utils.js
index 38f4a7582..638bffe61 100644
--- a/modules/ppcp-axo-block/resources/js/components/Watermark/utils.js
+++ b/modules/ppcp-axo-block/resources/js/components/Watermark/utils.js
@@ -1,11 +1,15 @@
import { createElement, createRoot } from '@wordpress/element';
import { Watermark, WatermarkManager } from '../Watermark';
+// Object to store references to the watermark container and root
const watermarkReference = {
container: null,
root: null,
};
+/**
+ * Creates a container for the watermark in the checkout contact information block.
+ */
export const createWatermarkContainer = () => {
const textInputContainer = document.querySelector(
'.wp-block-woocommerce-checkout-contact-information-block .wc-block-components-text-input'
@@ -16,6 +20,7 @@ export const createWatermarkContainer = () => {
textInputContainer.querySelector( 'input[id="email"]' );
if ( emailInput ) {
+ // Create watermark container
watermarkReference.container = document.createElement( 'div' );
watermarkReference.container.setAttribute(
'class',
@@ -26,7 +31,7 @@ export const createWatermarkContainer = () => {
'.wc-block-axo-email-submit-button-container'
);
- // If possible, insert the watermark after the "Continue" button.
+ // Insert the watermark after the "Continue" button or email input
const insertAfterElement = emailButton || emailInput;
insertAfterElement.parentNode.insertBefore(
@@ -34,6 +39,7 @@ export const createWatermarkContainer = () => {
insertAfterElement.nextSibling
);
+ // Create a root for the watermark
watermarkReference.root = createRoot(
watermarkReference.container
);
@@ -41,12 +47,19 @@ export const createWatermarkContainer = () => {
}
};
+/**
+ * Sets up the watermark manager component.
+ *
+ * @param {Object} fastlaneSdk - The Fastlane SDK instance.
+ * @return {Function} Cleanup function to remove the watermark.
+ */
export const setupWatermark = ( fastlaneSdk ) => {
const container = document.createElement( 'div' );
document.body.appendChild( container );
const root = createRoot( container );
root.render( createElement( WatermarkManager, { fastlaneSdk } ) );
+ // Return cleanup function
return () => {
root.unmount();
if ( container && container.parentNode ) {
@@ -55,6 +68,9 @@ export const setupWatermark = ( fastlaneSdk ) => {
};
};
+/**
+ * Removes the watermark from the DOM and resets the reference.
+ */
export const removeWatermark = () => {
if ( watermarkReference.root ) {
watermarkReference.root.unmount();
@@ -65,6 +81,7 @@ export const removeWatermark = () => {
watermarkReference.container
);
} else {
+ // Fallback removal if parent node is not available
const detachedContainer = document.querySelector(
'.wc-block-checkout-axo-block-watermark-container'
);
@@ -73,15 +90,30 @@ export const removeWatermark = () => {
}
}
}
+ // Reset watermark reference
Object.assign( watermarkReference, { container: null, root: null } );
};
+/**
+ * Renders content in the watermark container.
+ *
+ * @param {ReactElement} content - The content to render.
+ */
export const renderWatermarkContent = ( content ) => {
if ( watermarkReference.root ) {
watermarkReference.root.render( content );
}
};
+/**
+ * Updates the watermark content based on the current state.
+ *
+ * @param {Object} params - State parameters.
+ * @param {boolean} params.isAxoActive - Whether AXO is active.
+ * @param {boolean} params.isAxoScriptLoaded - Whether AXO script is loaded.
+ * @param {Object} params.fastlaneSdk - The Fastlane SDK instance.
+ * @param {boolean} params.isGuest - Whether the user is a guest.
+ */
export const updateWatermarkContent = ( {
isAxoActive,
isAxoScriptLoaded,
@@ -89,6 +121,7 @@ export const updateWatermarkContent = ( {
isGuest,
} ) => {
if ( ! isAxoActive && ! isAxoScriptLoaded ) {
+ // Show loading spinner
renderWatermarkContent(
createElement( 'span', {
className: 'wc-block-components-spinner',
@@ -96,6 +129,7 @@ export const updateWatermarkContent = ( {
} )
);
} else if ( isAxoActive ) {
+ // Show Fastlane watermark
renderWatermarkContent(
createElement( Watermark, {
fastlaneSdk,
@@ -104,6 +138,7 @@ export const updateWatermarkContent = ( {
} )
);
} else {
+ // Clear watermark content
renderWatermarkContent( null );
}
};
diff --git a/modules/ppcp-axo-block/resources/js/events/emailLookupManager.js b/modules/ppcp-axo-block/resources/js/events/emailLookupManager.js
index 85efa0234..563f9510d 100644
--- a/modules/ppcp-axo-block/resources/js/events/emailLookupManager.js
+++ b/modules/ppcp-axo-block/resources/js/events/emailLookupManager.js
@@ -4,6 +4,21 @@ import { injectShippingChangeButton } from '../components/Shipping';
import { injectCardChangeButton } from '../components/Card';
import { setIsGuest, setIsEmailLookupCompleted } from '../stores/axoStore';
+/**
+ * Creates an email lookup handler function for AXO checkout.
+ *
+ * @param {Object} fastlaneSdk - The Fastlane SDK instance.
+ * @param {Function} setShippingAddress - Function to set shipping address in the store.
+ * @param {Function} setCardDetails - Function to set card details in the store.
+ * @param {Function} snapshotFields - Function to save current field values.
+ * @param {Object} wooShippingAddress - Current WooCommerce shipping address.
+ * @param {Object} wooBillingAddress - Current WooCommerce billing address.
+ * @param {Function} setWooShippingAddress - Function to update WooCommerce shipping address.
+ * @param {Function} setWooBillingAddress - Function to update WooCommerce billing address.
+ * @param {Function} onChangeShippingAddressClick - Handler for shipping address change.
+ * @param {Function} onChangeCardButtonClick - Handler for card change.
+ * @return {Function} The email lookup handler function.
+ */
export const createEmailLookupHandler = (
fastlaneSdk,
setShippingAddress,
@@ -20,6 +35,7 @@ export const createEmailLookupHandler = (
try {
log( `Email value being looked up: ${ email }` );
+ // Validate Fastlane SDK initialization
if ( ! fastlaneSdk ) {
throw new Error( 'FastlaneSDK is not initialized' );
}
@@ -30,12 +46,13 @@ export const createEmailLookupHandler = (
);
}
+ // Perform email lookup
const lookup =
await fastlaneSdk.identity.lookupCustomerByEmail( email );
log( `Lookup response: ${ JSON.stringify( lookup ) }` );
- // Gary flow
+ // Handle Gary flow (new user)
if ( lookup && lookup.customerContextId === '' ) {
setIsEmailLookupCompleted( true );
}
@@ -45,6 +62,7 @@ export const createEmailLookupHandler = (
return;
}
+ // Trigger authentication flow
const authResponse =
await fastlaneSdk.identity.triggerAuthenticationFlow(
lookup.customerContextId
@@ -56,15 +74,18 @@ export const createEmailLookupHandler = (
const { authenticationState, profileData } = authResponse;
- // OTP success/fail/cancel flow
+ // Mark email lookup as completed for OTP flow
if ( authResponse ) {
setIsEmailLookupCompleted( true );
}
+ // Handle successful authentication
if ( authenticationState === 'succeeded' ) {
+ // Save current field values
snapshotFields( wooShippingAddress, wooBillingAddress );
setIsGuest( false );
+ // Update store with profile data
if ( profileData && profileData.shippingAddress ) {
setShippingAddress( profileData.shippingAddress );
}
@@ -74,12 +95,14 @@ export const createEmailLookupHandler = (
log( `Profile Data: ${ JSON.stringify( profileData ) }` );
+ // Populate WooCommerce fields with profile data
populateWooFields(
profileData,
setWooShippingAddress,
setWooBillingAddress
);
+ // Inject change buttons for shipping and card
injectShippingChangeButton( onChangeShippingAddressClick );
injectCardChangeButton( onChangeCardButtonClick );
} else {
diff --git a/modules/ppcp-axo-block/resources/js/helpers/classnamesManager.js b/modules/ppcp-axo-block/resources/js/helpers/classnamesManager.js
index 2af96aea9..411f815cb 100644
--- a/modules/ppcp-axo-block/resources/js/helpers/classnamesManager.js
+++ b/modules/ppcp-axo-block/resources/js/helpers/classnamesManager.js
@@ -41,6 +41,10 @@ export const setupAuthenticationClassToggle = () => {
return unsubscribe;
};
+/**
+ * Sets up a class toggle based on the isEmailLookupCompleted state for the checkout fields block.
+ * @return {Function} Unsubscribe function for cleanup.
+ */
export const setupEmailLookupCompletedClassToggle = () => {
const targetSelector = '.wp-block-woocommerce-checkout-fields-block';
const emailLookupCompletedClass = 'wc-block-axo-email-lookup-completed';
@@ -77,7 +81,7 @@ export const setupEmailLookupCompletedClassToggle = () => {
};
/**
- * Sets up class toggles for the contact information block based on isAxoActive and isGuest states.
+ * Sets up class toggles for the contact information block based on isAxoActive, isGuest, and isEmailLookupCompleted states.
* @return {Function} Unsubscribe function for cleanup.
*/
export const setupCheckoutBlockClassToggles = () => {
@@ -133,7 +137,7 @@ export const setupCheckoutBlockClassToggles = () => {
/**
* Initializes all class toggles.
- * @return {Function} Cleanup function.
+ * @return {Function} Cleanup function to unsubscribe all listeners.
*/
export const initializeClassToggles = () => {
const unsubscribeAuth = setupAuthenticationClassToggle();
@@ -141,6 +145,7 @@ export const initializeClassToggles = () => {
setupEmailLookupCompletedClassToggle();
const unsubscribeContactInfo = setupCheckoutBlockClassToggles();
+ // Return a cleanup function that unsubscribes all listeners
return () => {
if ( unsubscribeAuth ) {
unsubscribeAuth();
diff --git a/modules/ppcp-axo-block/resources/js/helpers/fieldHelpers.js b/modules/ppcp-axo-block/resources/js/helpers/fieldHelpers.js
index 9c66d762d..c04b62a24 100644
--- a/modules/ppcp-axo-block/resources/js/helpers/fieldHelpers.js
+++ b/modules/ppcp-axo-block/resources/js/helpers/fieldHelpers.js
@@ -1,6 +1,12 @@
import { dispatch } from '@wordpress/data';
import { log } from '../../../../ppcp-axo/resources/js/Helper/Debug';
+/**
+ * Saves the current shipping and billing address to localStorage.
+ *
+ * @param {Object} shippingAddress - The current shipping address.
+ * @param {Object} billingAddress - The current billing address.
+ */
export const snapshotFields = ( shippingAddress, billingAddress ) => {
if ( ! shippingAddress || ! billingAddress ) {
log(
@@ -15,6 +21,7 @@ export const snapshotFields = ( shippingAddress, billingAddress ) => {
const originalData = { shippingAddress, billingAddress };
log( `Snapshot data: ${ JSON.stringify( originalData ) }` );
try {
+ // Save the original data to localStorage
localStorage.setItem(
'axoOriginalCheckoutFields',
JSON.stringify( originalData )
@@ -24,6 +31,12 @@ export const snapshotFields = ( shippingAddress, billingAddress ) => {
}
};
+/**
+ * Restores the original shipping and billing addresses from localStorage.
+ *
+ * @param {Function} updateShippingAddress - Function to update the shipping address.
+ * @param {Function} updateBillingAddress - Function to update the billing address.
+ */
export const restoreOriginalFields = (
updateShippingAddress,
updateBillingAddress
@@ -31,6 +44,7 @@ export const restoreOriginalFields = (
log( 'Attempting to restore original fields' );
let savedData;
try {
+ // Retrieve saved data from localStorage
savedData = localStorage.getItem( 'axoOriginalCheckoutFields' );
log(
`Data retrieved from localStorage: ${ JSON.stringify( savedData ) }`
@@ -42,11 +56,13 @@ export const restoreOriginalFields = (
if ( savedData ) {
try {
const parsedData = JSON.parse( savedData );
+ // Restore shipping address if available
if ( parsedData.shippingAddress ) {
updateShippingAddress( parsedData.shippingAddress );
} else {
log( `No shipping address found in saved data`, 'warn' );
}
+ // Restore billing address if available
if ( parsedData.billingAddress ) {
log(
`Restoring billing address:
@@ -67,6 +83,13 @@ export const restoreOriginalFields = (
}
};
+/**
+ * Populates WooCommerce fields with profile data from AXO.
+ *
+ * @param {Object} profileData - The profile data from AXO.
+ * @param {Function} setWooShippingAddress - Function to set WooCommerce shipping address.
+ * @param {Function} setWooBillingAddress - Function to set WooCommerce billing address.
+ */
export const populateWooFields = (
profileData,
setWooShippingAddress,
@@ -82,14 +105,14 @@ export const populateWooFields = (
const checkoutDispatch = dispatch( CHECKOUT_STORE_KEY );
- // Uncheck the 'Use same address for billing' checkbox if the method exists.
+ // Uncheck the 'Use same address for billing' checkbox if the method exists
if (
typeof checkoutDispatch.__internalSetUseShippingAsBilling === 'function'
) {
checkoutDispatch.__internalSetUseShippingAsBilling( false );
}
- // Save shipping address.
+ // Prepare and set shipping address
const { address, name, phoneNumber } = profileData.shippingAddress;
const shippingAddress = {
@@ -111,7 +134,7 @@ export const populateWooFields = (
);
setWooShippingAddress( shippingAddress );
- // Save billing address.
+ // Prepare and set billing address
const billingData = profileData.card.paymentSource.card.billingAddress;
const billingAddress = {
@@ -132,12 +155,12 @@ export const populateWooFields = (
);
setWooBillingAddress( billingAddress );
- // Collapse shipping address input fields into the card view.
+ // Collapse shipping address input fields into the card view
if ( typeof checkoutDispatch.setEditingShippingAddress === 'function' ) {
checkoutDispatch.setEditingShippingAddress( false );
}
- // Collapse billing address input fields into the card view.
+ // Collapse billing address input fields into the card view
if ( typeof checkoutDispatch.setEditingBillingAddress === 'function' ) {
checkoutDispatch.setEditingBillingAddress( false );
}
diff --git a/modules/ppcp-axo-block/resources/js/hooks/useAddressEditing.js b/modules/ppcp-axo-block/resources/js/hooks/useAddressEditing.js
index 59fc32769..f1e57c943 100644
--- a/modules/ppcp-axo-block/resources/js/hooks/useAddressEditing.js
+++ b/modules/ppcp-axo-block/resources/js/hooks/useAddressEditing.js
@@ -3,11 +3,21 @@ import { useDispatch, useSelect } from '@wordpress/data';
const CHECKOUT_STORE_KEY = 'wc/store/checkout';
+/**
+ * Custom hook to manage address editing states in the checkout process.
+ *
+ * When set to true (default), the shipping and billing address forms are displayed.
+ * When set to false, the address forms are hidden and the user can only view the address details (card view).
+ *
+ * @return {Object} An object containing address editing states and setter functions.
+ */
export const useAddressEditing = () => {
+ // Select address editing states from the checkout store
const { isEditingShippingAddress, isEditingBillingAddress } = useSelect(
( select ) => {
const store = select( CHECKOUT_STORE_KEY );
return {
+ // Default to true if the getter function doesn't exist
isEditingShippingAddress: store.getEditingShippingAddress
? store.getEditingShippingAddress()
: true,
@@ -19,9 +29,11 @@ export const useAddressEditing = () => {
[]
);
+ // Get dispatch functions to update address editing states
const { setEditingShippingAddress, setEditingBillingAddress } =
useDispatch( CHECKOUT_STORE_KEY );
+ // Memoized function to update shipping address editing state
const setShippingAddressEditing = useCallback(
( isEditing ) => {
if ( typeof setEditingShippingAddress === 'function' ) {
@@ -31,6 +43,7 @@ export const useAddressEditing = () => {
[ setEditingShippingAddress ]
);
+ // Memoized function to update billing address editing state
const setBillingAddressEditing = useCallback(
( isEditing ) => {
if ( typeof setEditingBillingAddress === 'function' ) {
@@ -40,6 +53,7 @@ export const useAddressEditing = () => {
[ setEditingBillingAddress ]
);
+ // Return an object with address editing states and setter functions
return {
isEditingShippingAddress,
isEditingBillingAddress,
diff --git a/modules/ppcp-axo-block/resources/js/hooks/useAxoCleanup.js b/modules/ppcp-axo-block/resources/js/hooks/useAxoCleanup.js
index 23175e31f..871197722 100644
--- a/modules/ppcp-axo-block/resources/js/hooks/useAxoCleanup.js
+++ b/modules/ppcp-axo-block/resources/js/hooks/useAxoCleanup.js
@@ -12,13 +12,21 @@ import {
import { restoreOriginalFields } from '../helpers/fieldHelpers';
import useCustomerData from './useCustomerData';
+/**
+ * Custom hook to handle cleanup of AXO functionality.
+ * This hook ensures that all AXO-related changes are reverted when the component unmounts (a different payment method gets selected).
+ */
const useAxoCleanup = () => {
+ // Get dispatch functions from the AXO store
const { setIsAxoActive, setIsGuest } = useDispatch( STORE_NAME );
+
+ // Get functions to update WooCommerce shipping and billing addresses
const {
setShippingAddress: updateWooShippingAddress,
setBillingAddress: updateWooBillingAddress,
} = useCustomerData();
+ // Effect to restore original WooCommerce fields on unmount
useEffect( () => {
return () => {
log( 'Cleaning up: Restoring WooCommerce fields' );
@@ -29,14 +37,21 @@ const useAxoCleanup = () => {
};
}, [ updateWooShippingAddress, updateWooBillingAddress ] );
+ // Effect to clean up AXO-specific functionality on unmount
useEffect( () => {
return () => {
log( 'Cleaning up Axo component' );
+
+ // Reset AXO state
setIsAxoActive( false );
setIsGuest( true );
+
+ // Remove AXO UI elements
removeShippingChangeButton();
removeCardChangeButton();
removeWatermark();
+
+ // Remove email functionality if it was set up
if ( isEmailFunctionalitySetup() ) {
log( 'Removing email functionality' );
removeEmailFunctionality();
diff --git a/modules/ppcp-axo-block/resources/js/hooks/useAxoSetup.js b/modules/ppcp-axo-block/resources/js/hooks/useAxoSetup.js
index bdc615461..37ac4898b 100644
--- a/modules/ppcp-axo-block/resources/js/hooks/useAxoSetup.js
+++ b/modules/ppcp-axo-block/resources/js/hooks/useAxoSetup.js
@@ -12,20 +12,34 @@ import useCustomerData from './useCustomerData';
import useShippingAddressChange from './useShippingAddressChange';
import useCardChange from './useCardChange';
+/**
+ * Custom hook to set up AXO functionality.
+ *
+ * @param {Object} ppcpConfig - PayPal Checkout configuration.
+ * @param {Object} fastlaneSdk - Fastlane SDK instance.
+ * @param {Object} paymentComponent - Payment component instance.
+ * @return {boolean} Whether PayPal script has loaded.
+ */
const useAxoSetup = ( ppcpConfig, fastlaneSdk, paymentComponent ) => {
+ // Get dispatch functions from the AXO store
const {
setIsAxoActive,
setIsAxoScriptLoaded,
setShippingAddress,
setCardDetails,
} = useDispatch( STORE_NAME );
+
+ // Check if PayPal script has loaded
const paypalLoaded = usePayPalScript( ppcpConfig );
+
+ // Set up card and shipping address change handlers
const onChangeCardButtonClick = useCardChange( fastlaneSdk );
const onChangeShippingAddressClick = useShippingAddressChange(
fastlaneSdk,
setShippingAddress
);
+ // Get customer data and setter functions
const {
shippingAddress: wooShippingAddress,
billingAddress: wooBillingAddress,
@@ -33,17 +47,22 @@ const useAxoSetup = ( ppcpConfig, fastlaneSdk, paymentComponent ) => {
setBillingAddress: setWooBillingAddress,
} = useCustomerData();
+ // Set up phone sync handler
usePhoneSyncHandler( paymentComponent );
+ // Initialize class toggles on mount
useEffect( () => {
initializeClassToggles();
}, [] );
+ // Set up AXO functionality when PayPal and Fastlane are loaded
useEffect( () => {
setupWatermark( fastlaneSdk );
if ( paypalLoaded && fastlaneSdk ) {
setIsAxoScriptLoaded( true );
setIsAxoActive( true );
+
+ // Create and set up email lookup handler
const emailLookupHandler = createEmailLookupHandler(
fastlaneSdk,
setShippingAddress,
diff --git a/modules/ppcp-axo-block/resources/js/hooks/useCardChange.js b/modules/ppcp-axo-block/resources/js/hooks/useCardChange.js
index 6d2482416..29ab2f42c 100644
--- a/modules/ppcp-axo-block/resources/js/hooks/useCardChange.js
+++ b/modules/ppcp-axo-block/resources/js/hooks/useCardChange.js
@@ -5,22 +5,29 @@ import { useAddressEditing } from './useAddressEditing';
import useCustomerData from './useCustomerData';
import { STORE_NAME } from '../stores/axoStore';
+/**
+ * Custom hook to handle the 'Choose a different card' selection.
+ *
+ * @param {Object} fastlaneSdk - The Fastlane SDK instance.
+ * @return {Function} Callback function to trigger card selection and update related data.
+ */
export const useCardChange = ( fastlaneSdk ) => {
const { setBillingAddressEditing } = useAddressEditing();
const { setBillingAddress: setWooBillingAddress } = useCustomerData();
- const { setCardDetails, setShippingAddress } = useDispatch( STORE_NAME );
+ const { setCardDetails } = useDispatch( STORE_NAME );
return useCallback( async () => {
if ( fastlaneSdk ) {
+ // Show card selector and get the user's selection
const { selectionChanged, selectedCard } =
await fastlaneSdk.profile.showCardSelector();
if ( selectionChanged && selectedCard?.paymentSource?.card ) {
- // Use the fallback logic for cardholder's name.
+ // Extract cardholder and billing information from the selected card
const { name, billingAddress } =
selectedCard.paymentSource.card;
- // If name is missing, use billing details as a fallback for the name.
+ // Parse cardholder's name, using billing details as a fallback if missing
let firstName = '';
let lastName = '';
@@ -30,6 +37,7 @@ export const useCardChange = ( fastlaneSdk ) => {
lastName = nameParts.slice( 1 ).join( ' ' );
}
+ // Transform the billing address into WooCommerce format
const newBillingAddress = {
first_name: firstName,
last_name: lastName,
@@ -41,20 +49,19 @@ export const useCardChange = ( fastlaneSdk ) => {
country: billingAddress?.countryCode || '',
};
- // Batch state updates.
+ // Batch update states
await Promise.all( [
+ // Update the selected card details in the custom store
new Promise( ( resolve ) => {
setCardDetails( selectedCard );
resolve();
} ),
+ // Update the WooCommerce billing address in the WooCommerce store
new Promise( ( resolve ) => {
setWooBillingAddress( newBillingAddress );
resolve();
} ),
- new Promise( ( resolve ) => {
- setShippingAddress( newBillingAddress );
- resolve();
- } ),
+ // Trigger the Address Card view by setting the billing address editing state to false
new Promise( ( resolve ) => {
setBillingAddressEditing( false );
resolve();
@@ -68,7 +75,6 @@ export const useCardChange = ( fastlaneSdk ) => {
fastlaneSdk,
setCardDetails,
setWooBillingAddress,
- setShippingAddress,
setBillingAddressEditing,
] );
};
diff --git a/modules/ppcp-axo-block/resources/js/hooks/useCustomerData.js b/modules/ppcp-axo-block/resources/js/hooks/useCustomerData.js
index 6f7ffe537..b839e3199 100644
--- a/modules/ppcp-axo-block/resources/js/hooks/useCustomerData.js
+++ b/modules/ppcp-axo-block/resources/js/hooks/useCustomerData.js
@@ -1,16 +1,24 @@
import { useCallback, useMemo } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
+/**
+ * Custom hook to manage customer data in the WooCommerce store.
+ *
+ * @return {Object} An object containing customer addresses and setter functions.
+ */
export const useCustomerData = () => {
+ // Fetch customer data from the WooCommerce store
const customerData = useSelect( ( select ) =>
select( 'wc/store/cart' ).getCustomerData()
);
+ // Get dispatch functions to update shipping and billing addresses
const {
setShippingAddress: setShippingAddressDispatch,
setBillingAddress: setBillingAddressDispatch,
} = useDispatch( 'wc/store/cart' );
+ // Memoized function to update shipping address
const setShippingAddress = useCallback(
( address ) => {
setShippingAddressDispatch( address );
@@ -18,6 +26,7 @@ export const useCustomerData = () => {
[ setShippingAddressDispatch ]
);
+ // Memoized function to update billing address
const setBillingAddress = useCallback(
( address ) => {
setBillingAddressDispatch( address );
@@ -25,6 +34,7 @@ export const useCustomerData = () => {
[ setBillingAddressDispatch ]
);
+ // Return memoized object with customer data and setter functions
return useMemo(
() => ( {
shippingAddress: customerData.shippingAddress,
diff --git a/modules/ppcp-axo-block/resources/js/hooks/useDeleteEmptyKeys.js b/modules/ppcp-axo-block/resources/js/hooks/useDeleteEmptyKeys.js
index 04043e0a9..63eaffe14 100644
--- a/modules/ppcp-axo-block/resources/js/hooks/useDeleteEmptyKeys.js
+++ b/modules/ppcp-axo-block/resources/js/hooks/useDeleteEmptyKeys.js
@@ -3,17 +3,30 @@ import { useCallback } from '@wordpress/element';
const isObject = ( value ) => typeof value === 'object' && value !== null;
const isNonEmptyString = ( value ) => value !== '';
+/**
+ * Recursively removes empty values from an object.
+ * Empty values are considered to be:
+ * - Empty strings
+ * - Empty objects
+ * - Null or undefined values
+ *
+ * @param {Object} obj - The object to clean.
+ * @return {Object} A new object with empty values removed.
+ */
const removeEmptyValues = ( obj ) => {
+ // If not an object, return the value as is
if ( ! isObject( obj ) ) {
return obj;
}
return Object.fromEntries(
Object.entries( obj )
+ // Recursively apply removeEmptyValues to nested objects
.map( ( [ key, value ] ) => [
key,
isObject( value ) ? removeEmptyValues( value ) : value,
] )
+ // Filter out empty values
.filter( ( [ _, value ] ) =>
isObject( value )
? Object.keys( value ).length > 0
@@ -22,6 +35,11 @@ const removeEmptyValues = ( obj ) => {
);
};
+/**
+ * Custom hook that returns a memoized function to remove empty values from an object.
+ *
+ * @return {Function} A memoized function that removes empty values from an object.
+ */
export const useDeleteEmptyKeys = () => {
return useCallback( removeEmptyValues, [] );
};
diff --git a/modules/ppcp-axo-block/resources/js/hooks/useFastlaneSdk.js b/modules/ppcp-axo-block/resources/js/hooks/useFastlaneSdk.js
index 9bd9db8d5..bf1d4c791 100644
--- a/modules/ppcp-axo-block/resources/js/hooks/useFastlaneSdk.js
+++ b/modules/ppcp-axo-block/resources/js/hooks/useFastlaneSdk.js
@@ -3,16 +3,27 @@ import Fastlane from '../../../../ppcp-axo/resources/js/Connection/Fastlane';
import { log } from '../../../../ppcp-axo/resources/js/Helper/Debug';
import { useDeleteEmptyKeys } from './useDeleteEmptyKeys';
+/**
+ * Custom hook to initialize and manage the Fastlane SDK.
+ *
+ * @param {Object} axoConfig - Configuration for AXO.
+ * @param {Object} ppcpConfig - Configuration for PPCP.
+ * @return {Object|null} The initialized Fastlane SDK instance or null.
+ */
const useFastlaneSdk = ( axoConfig, ppcpConfig ) => {
const [ fastlaneSdk, setFastlaneSdk ] = useState( null );
+ // Ref to prevent multiple simultaneous initializations
const initializingRef = useRef( false );
+ // Ref to hold the latest config values
const configRef = useRef( { axoConfig, ppcpConfig } );
+ // Custom hook to remove empty keys from an object
const deleteEmptyKeys = useDeleteEmptyKeys();
const styleOptions = useMemo( () => {
return deleteEmptyKeys( configRef.current.axoConfig.style_options );
}, [ deleteEmptyKeys ] );
+ // Effect to initialize Fastlane SDK
useEffect( () => {
const initFastlane = async () => {
if ( initializingRef.current || fastlaneSdk ) {
@@ -25,15 +36,18 @@ const useFastlaneSdk = ( axoConfig, ppcpConfig ) => {
try {
const fastlane = new Fastlane();
+ // Set sandbox environment if configured
if ( configRef.current.axoConfig.environment.is_sandbox ) {
window.localStorage.setItem( 'axoEnv', 'sandbox' );
}
+ // Connect to Fastlane with locale and style options
await fastlane.connect( {
locale: configRef.current.ppcpConfig.locale,
styles: styleOptions,
} );
+ // Set locale (hardcoded to 'en_us' for now)
fastlane.setLocale( 'en_us' );
setFastlaneSdk( fastlane );
@@ -47,6 +61,7 @@ const useFastlaneSdk = ( axoConfig, ppcpConfig ) => {
initFastlane();
}, [ fastlaneSdk, styleOptions ] );
+ // Effect to update the config ref when configs change
useEffect( () => {
configRef.current = { axoConfig, ppcpConfig };
}, [ axoConfig, ppcpConfig ] );
diff --git a/modules/ppcp-axo-block/resources/js/hooks/useHandlePaymentSetup.js b/modules/ppcp-axo-block/resources/js/hooks/useHandlePaymentSetup.js
index ca74c887b..86975f78c 100644
--- a/modules/ppcp-axo-block/resources/js/hooks/useHandlePaymentSetup.js
+++ b/modules/ppcp-axo-block/resources/js/hooks/useHandlePaymentSetup.js
@@ -2,29 +2,40 @@ import { useCallback } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { STORE_NAME } from '../stores/axoStore';
+/**
+ * Custom hook to handle payment setup in the checkout process.
+ *
+ * @param {Object} emitResponse - Object containing response types.
+ * @param {Object} paymentComponent - The payment component instance.
+ * @param {Object} tokenizedCustomerData - Tokenized customer data for payment.
+ * @return {Function} Callback function to handle payment setup.
+ */
const useHandlePaymentSetup = (
emitResponse,
paymentComponent,
tokenizedCustomerData
) => {
+ // Select card details from the store
const { cardDetails } = useSelect(
( select ) => ( {
- shippingAddress: select( STORE_NAME ).getShippingAddress(),
cardDetails: select( STORE_NAME ).getCardDetails(),
} ),
[]
);
return useCallback( async () => {
+ // Determine if it's a Ryan flow (saved card) based on the presence of card ID
const isRyanFlow = !! cardDetails?.id;
let cardToken = cardDetails?.id;
+ // If no card token and payment component exists, get a new token
if ( ! cardToken && paymentComponent ) {
cardToken = await paymentComponent
.getPaymentToken( tokenizedCustomerData )
.then( ( response ) => response.id );
}
+ // Handle error cases when card token is not available
if ( ! cardToken ) {
let reason = 'tokenization error';
diff --git a/modules/ppcp-axo-block/resources/js/hooks/usePayPalScript.js b/modules/ppcp-axo-block/resources/js/hooks/usePayPalScript.js
index 8fc85829e..223525285 100644
--- a/modules/ppcp-axo-block/resources/js/hooks/usePayPalScript.js
+++ b/modules/ppcp-axo-block/resources/js/hooks/usePayPalScript.js
@@ -2,12 +2,20 @@ import { useState, useEffect } from '@wordpress/element';
import { log } from '../../../../ppcp-axo/resources/js/Helper/Debug';
import { loadPaypalScript } from '../../../../ppcp-button/resources/js/modules/Helper/ScriptLoading';
+/**
+ * Custom hook to load the PayPal script.
+ *
+ * @param {Object} ppcpConfig - Configuration object for PayPal script.
+ * @return {boolean} True if the PayPal script has loaded, false otherwise.
+ */
const usePayPalScript = ( ppcpConfig ) => {
const [ isLoaded, setIsLoaded ] = useState( false );
useEffect( () => {
if ( ! isLoaded ) {
log( 'Loading PayPal script' );
+
+ // Load the PayPal script using the provided configuration
loadPaypalScript( ppcpConfig, () => {
log( 'PayPal script loaded' );
setIsLoaded( true );
diff --git a/modules/ppcp-axo-block/resources/js/hooks/usePaymentSetupEffect.js b/modules/ppcp-axo-block/resources/js/hooks/usePaymentSetupEffect.js
index 087fc807e..8e82b5011 100644
--- a/modules/ppcp-axo-block/resources/js/hooks/usePaymentSetupEffect.js
+++ b/modules/ppcp-axo-block/resources/js/hooks/usePaymentSetupEffect.js
@@ -1,5 +1,13 @@
import { useEffect, useCallback } from '@wordpress/element';
+/**
+ * Custom hook to handle payment setup effects in the checkout flow.
+ *
+ * @param {Function} onPaymentSetup - Function to subscribe to payment setup events.
+ * @param {Function} handlePaymentSetup - Callback to process payment setup.
+ * @param {Function} setPaymentComponent - Function to update the payment component state.
+ * @return {Object} Object containing the handlePaymentLoad function.
+ */
const usePaymentSetupEffect = (
onPaymentSetup,
handlePaymentSetup,
@@ -17,6 +25,11 @@ const usePaymentSetupEffect = (
};
}, [ onPaymentSetup, handlePaymentSetup ] );
+ /**
+ * Callback function to handle payment component loading.
+ *
+ * @param {Object} component - The loaded payment component.
+ */
const handlePaymentLoad = useCallback(
( component ) => {
setPaymentComponent( component );
diff --git a/modules/ppcp-axo-block/resources/js/hooks/useShippingAddressChange.js b/modules/ppcp-axo-block/resources/js/hooks/useShippingAddressChange.js
index 6d8e7b4a2..2c45b8930 100644
--- a/modules/ppcp-axo-block/resources/js/hooks/useShippingAddressChange.js
+++ b/modules/ppcp-axo-block/resources/js/hooks/useShippingAddressChange.js
@@ -2,19 +2,30 @@ import { useCallback } from '@wordpress/element';
import { useAddressEditing } from './useAddressEditing';
import useCustomerData from './useCustomerData';
+/**
+ * Custom hook to handle the 'Choose a different shipping address' selection.
+ *
+ * @param {Object} fastlaneSdk - The Fastlane SDK instance.
+ * @param {Function} setShippingAddress - Function to update the shipping address state.
+ * @return {Function} Callback function to trigger shipping address selection and update.
+ */
export const useShippingAddressChange = ( fastlaneSdk, setShippingAddress ) => {
const { setShippingAddressEditing } = useAddressEditing();
const { setShippingAddress: setWooShippingAddress } = useCustomerData();
return useCallback( async () => {
if ( fastlaneSdk ) {
+ // Show shipping address selector and get the user's selection
const { selectionChanged, selectedAddress } =
await fastlaneSdk.profile.showShippingAddressSelector();
+
if ( selectionChanged ) {
+ // Update the shipping address in the custom store with the selected address
setShippingAddress( selectedAddress );
const { address, name, phoneNumber } = selectedAddress;
+ // Transform the selected address into WooCommerce format
const newShippingAddress = {
first_name: name.firstName,
last_name: name.lastName,
@@ -27,11 +38,13 @@ export const useShippingAddressChange = ( fastlaneSdk, setShippingAddress ) => {
phone: phoneNumber.nationalNumber,
};
+ // Update the WooCommerce shipping address in the WooCommerce store
await new Promise( ( resolve ) => {
setWooShippingAddress( newShippingAddress );
resolve();
} );
+ // Trigger the Address Card view by setting the shipping address editing state to false
await new Promise( ( resolve ) => {
setShippingAddressEditing( false );
resolve();
diff --git a/modules/ppcp-axo-block/resources/js/hooks/useTokenizeCustomerData.js b/modules/ppcp-axo-block/resources/js/hooks/useTokenizeCustomerData.js
index 868ddcb85..c0a7dcf97 100644
--- a/modules/ppcp-axo-block/resources/js/hooks/useTokenizeCustomerData.js
+++ b/modules/ppcp-axo-block/resources/js/hooks/useTokenizeCustomerData.js
@@ -1,18 +1,27 @@
import { useMemo } from '@wordpress/element';
-import { useSelect } from '@wordpress/data';
+import useCustomerData from './useCustomerData';
+/**
+ * Custom hook to prepare customer data for tokenization.
+ *
+ * @return {Object} Formatted customer data for tokenization.
+ */
export const useTokenizeCustomerData = () => {
- const customerData = useSelect( ( select ) =>
- select( 'wc/store/cart' ).getCustomerData()
- );
+ const { billingAddress, shippingAddress } = useCustomerData();
+ /**
+ * Validates if an address contains the minimum required data.
+ *
+ * @param {Object} address - The address object to validate.
+ * @return {boolean} True if the address is valid, false otherwise.
+ */
const isValidAddress = ( address ) => {
- // At least one name must be present.
+ // At least one name must be present
if ( ! address.first_name && ! address.last_name ) {
return false;
}
- // Street, city, postcode, country are mandatory; state is optional.
+ // Street, city, postcode, country are mandatory; state is optional
return (
address.address_1 &&
address.city &&
@@ -21,15 +30,14 @@ export const useTokenizeCustomerData = () => {
);
};
- // Memoize the customer data to avoid unnecessary re-renders (and potential infinite loops).
+ // Memoize the customer data to avoid unnecessary re-renders (and potential infinite loops)
return useMemo( () => {
- const { billingAddress, shippingAddress } = customerData;
-
- // Prefer billing address, but fallback to shipping address if billing address is not valid.
+ // Determine the main address, preferring billing address if valid
const mainAddress = isValidAddress( billingAddress )
? billingAddress
: shippingAddress;
+ // Format the customer data for tokenization
return {
cardholderName: {
fullName: `${ mainAddress.first_name } ${ mainAddress.last_name }`,
@@ -43,7 +51,7 @@ export const useTokenizeCustomerData = () => {
countryCode: mainAddress.country,
},
};
- }, [ customerData ] );
+ }, [ billingAddress, shippingAddress ] );
};
export default useTokenizeCustomerData;
diff --git a/modules/ppcp-axo-block/resources/js/stores/axoStore.js b/modules/ppcp-axo-block/resources/js/stores/axoStore.js
index 04323c6e1..da92d0e6f 100644
--- a/modules/ppcp-axo-block/resources/js/stores/axoStore.js
+++ b/modules/ppcp-axo-block/resources/js/stores/axoStore.js
@@ -2,7 +2,6 @@ import { createReduxStore, register, dispatch } from '@wordpress/data';
export const STORE_NAME = 'woocommerce-paypal-payments/axo-block';
-// Initial state
const DEFAULT_STATE = {
isGuest: true,
isAxoActive: false,
@@ -14,7 +13,7 @@ const DEFAULT_STATE = {
phoneNumber: '',
};
-// Actions
+// Action creators for updating the store state
const actions = {
setIsGuest: ( isGuest ) => ( {
type: 'SET_IS_GUEST',
@@ -50,7 +49,13 @@ const actions = {
} ),
};
-// Reducer
+/**
+ * Reducer function to handle state updates based on dispatched actions.
+ *
+ * @param {Object} state - Current state of the store.
+ * @param {Object} action - Dispatched action object.
+ * @return {Object} New state after applying the action.
+ */
const reducer = ( state = DEFAULT_STATE, action ) => {
switch ( action.type ) {
case 'SET_IS_GUEST':
@@ -74,7 +79,7 @@ const reducer = ( state = DEFAULT_STATE, action ) => {
}
};
-// Selectors
+// Selector functions to retrieve specific pieces of state
const selectors = {
getIsGuest: ( state ) => state.isGuest,
getIsAxoActive: ( state ) => state.isAxoActive,
@@ -86,7 +91,7 @@ const selectors = {
getPhoneNumber: ( state ) => state.phoneNumber,
};
-// Create and register the store
+// Create and register the Redux store for the AXO block
const store = createReduxStore( STORE_NAME, {
reducer,
actions,
@@ -96,22 +101,48 @@ const store = createReduxStore( STORE_NAME, {
register( store );
// Action dispatchers
+
+/**
+ * Action dispatcher to update the guest status in the store.
+ *
+ * @param {boolean} isGuest - Whether the user is a guest or not.
+ */
export const setIsGuest = ( isGuest ) => {
dispatch( STORE_NAME ).setIsGuest( isGuest );
};
+/**
+ * Action dispatcher to update the email lookup completion status in the store.
+ *
+ * @param {boolean} isEmailLookupCompleted - Whether the email lookup is completed.
+ */
export const setIsEmailLookupCompleted = ( isEmailLookupCompleted ) => {
dispatch( STORE_NAME ).setIsEmailLookupCompleted( isEmailLookupCompleted );
};
+/**
+ * Action dispatcher to update the shipping address in the store.
+ *
+ * @param {Object} shippingAddress - The user's shipping address.
+ */
export const setShippingAddress = ( shippingAddress ) => {
dispatch( STORE_NAME ).setShippingAddress( shippingAddress );
};
+/**
+ * Action dispatcher to update the card details in the store.
+ *
+ * @param {Object} cardDetails - The user's card details.
+ */
export const setCardDetails = ( cardDetails ) => {
dispatch( STORE_NAME ).setCardDetails( cardDetails );
};
+/**
+ * Action dispatcher to update the phone number in the store.
+ *
+ * @param {string} phoneNumber - The user's phone number.
+ */
export const setPhoneNumber = ( phoneNumber ) => {
dispatch( STORE_NAME ).setPhoneNumber( phoneNumber );
};