mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-05 08:59:14 +08:00
Axo Block: Improve code comments and fix minor misc bugs
This commit is contained in:
parent
1f3034136e
commit
2436ceb487
29 changed files with 449 additions and 39 deletions
|
@ -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',
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 */ }
|
||||
<span
|
||||
className="wc-block-components-button__text"
|
||||
style={ {
|
||||
|
@ -32,6 +42,7 @@ const EmailButton = ( { handleSubmit } ) => {
|
|||
>
|
||||
{ __( 'Continue', 'woocommerce-paypal-payments' ) }
|
||||
</span>
|
||||
{ /* Loading spinner */ }
|
||||
{ isEmailSubmitted && (
|
||||
<span
|
||||
className="wc-block-components-spinner"
|
||||
|
|
|
@ -3,6 +3,7 @@ import { log } from '../../../../../ppcp-axo/resources/js/Helper/Debug';
|
|||
import { STORE_NAME } from '../../stores/axoStore';
|
||||
import EmailButton from './EmailButton';
|
||||
|
||||
// Cache for DOM elements and references
|
||||
let emailInput = null;
|
||||
let submitButtonReference = {
|
||||
container: null,
|
||||
|
@ -11,6 +12,11 @@ let submitButtonReference = {
|
|||
};
|
||||
let keydownHandler = null;
|
||||
|
||||
/**
|
||||
* Retrieves or caches the email input element.
|
||||
*
|
||||
* @return {HTMLElement|null} The email input element or null if not found.
|
||||
*/
|
||||
const getEmailInput = () => {
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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 <div id="fastlane-card" key="fastlane-card" />;
|
||||
|
|
|
@ -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 } ) => (
|
||||
<a
|
||||
className="wc-block-axo-change-link"
|
||||
role="button"
|
||||
onClick={ ( event ) => {
|
||||
// Prevent default anchor behavior
|
||||
event.preventDefault();
|
||||
// Call the provided click handler
|
||||
onChangeShippingAddressClick();
|
||||
} }
|
||||
>
|
||||
|
|
|
@ -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(
|
||||
<ShippingChangeButton
|
||||
|
@ -26,6 +36,7 @@ const ShippingChangeButtonManager = ( { onChangeShippingAddressClick } ) => {
|
|||
/>
|
||||
);
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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(
|
||||
<ShippingChangeButtonManager
|
||||
onChangeShippingAddressClick={ onChangeShippingAddressClick }
|
||||
|
@ -17,6 +26,9 @@ export const injectShippingChangeButton = ( onChangeShippingAddressClick ) => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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'
|
||||
|
|
|
@ -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 <div id={ name } ref={ containerRef } />;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
] );
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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, [] );
|
||||
};
|
||||
|
|
|
@ -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 ] );
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue