mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-05 08:59:14 +08:00
Add support for card icons to Axo Block and move the Change Card Link handling to the store. Also migrate the Change Card Link markup management logic to React
This commit is contained in:
parent
53af77897f
commit
42805c4fb5
17 changed files with 172 additions and 120 deletions
|
@ -17,10 +17,19 @@ $fast-transition-duration: 0.5s;
|
|||
}
|
||||
|
||||
// 1. AXO Block Radio Label
|
||||
#ppcp-axo-block-radio-label {
|
||||
@include flex-space-between;
|
||||
.wc-block-checkout__payment-method label[for="radio-control-wc-payment-method-options-ppcp-axo-gateway"] {
|
||||
padding-right: .875em;
|
||||
}
|
||||
|
||||
#radio-control-wc-payment-method-options-ppcp-axo-gateway__label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding-right: 1em;
|
||||
|
||||
.wc-block-components-payment-method-icons {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. AXO Block Card
|
||||
|
@ -70,15 +79,16 @@ $fast-transition-duration: 0.5s;
|
|||
}
|
||||
|
||||
&__edit {
|
||||
background-color: transparent;
|
||||
flex-grow: 1;
|
||||
margin-left: auto;
|
||||
text-align: right;
|
||||
border: 0;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-family: inherit;
|
||||
margin: 0 0 0 auto;
|
||||
font-size: 0.875em;
|
||||
font-weight: normal;
|
||||
color: inherit;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
|
|
|
@ -1,15 +1,28 @@
|
|||
import { createElement } from '@wordpress/element';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { STORE_NAME } from '../../stores/axoStore';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return {JSX.Element|null} The rendered button as an anchor tag, or null if conditions aren't met.
|
||||
*/
|
||||
const CardChangeButton = ( { onChangeButtonClick } ) =>
|
||||
createElement(
|
||||
const CardChangeButton = () => {
|
||||
const { isGuest, cardDetails, cardChangeHandler } = useSelect(
|
||||
( select ) => ( {
|
||||
isGuest: select( STORE_NAME ).getIsGuest(),
|
||||
cardDetails: select( STORE_NAME ).getCardDetails(),
|
||||
cardChangeHandler: select( STORE_NAME ).getCardChangeHandler(),
|
||||
} ),
|
||||
[]
|
||||
);
|
||||
|
||||
if ( isGuest || ! cardDetails || ! cardChangeHandler ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createElement(
|
||||
'a',
|
||||
{
|
||||
className:
|
||||
|
@ -19,10 +32,11 @@ const CardChangeButton = ( { onChangeButtonClick } ) =>
|
|||
// Prevent default anchor behavior
|
||||
event.preventDefault();
|
||||
// Call the provided click handler
|
||||
onChangeButtonClick();
|
||||
cardChangeHandler();
|
||||
},
|
||||
},
|
||||
__( 'Choose a different card', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
};
|
||||
|
||||
export default CardChangeButton;
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
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(
|
||||
'ppcp-axo-block-radio-label'
|
||||
);
|
||||
|
||||
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 } )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup function to remove the button when the component unmounts
|
||||
return () => {
|
||||
const button = document.querySelector(
|
||||
'.wc-block-checkout-axo-block-card__edit'
|
||||
);
|
||||
if ( button && button.parentNode ) {
|
||||
button.parentNode.remove();
|
||||
}
|
||||
};
|
||||
}, [ onChangeButtonClick ] );
|
||||
|
||||
// This component doesn't render anything directly
|
||||
return null;
|
||||
};
|
||||
|
||||
export default CardChangeButtonManager;
|
|
@ -1,4 +1,2 @@
|
|||
export { default as Card } from './Card';
|
||||
export { default as CardChangeButton } from './CardChangeButton';
|
||||
export { default as CardChangeButtonManager } from './CardChangeButtonManager';
|
||||
export { injectCardChangeButton, removeCardChangeButton } from './utils';
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
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();
|
||||
}
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
import CardChangeButton from './../Card/CardChangeButton';
|
||||
|
||||
/**
|
||||
* TitleLabel component for displaying a payment method title with icons and a change card button.
|
||||
*
|
||||
* @param {Object} props - Component props
|
||||
* @param {Object} props.components - Object containing WooCommerce components
|
||||
* @param {Object} props.config - Configuration object for the payment method
|
||||
* @return {JSX.Element} WordPress element
|
||||
*/
|
||||
const TitleLabel = ( { components, config } ) => {
|
||||
const axoConfig = window.wc_ppcp_axo;
|
||||
const { PaymentMethodIcons } = components;
|
||||
|
||||
return (
|
||||
<>
|
||||
<span dangerouslySetInnerHTML={ { __html: config.title } } />
|
||||
<PaymentMethodIcons icons={ axoConfig?.card_icons } />
|
||||
<CardChangeButton />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TitleLabel;
|
|
@ -0,0 +1 @@
|
|||
export { default as TitleLabel } from './TitleLabel';
|
|
@ -1,7 +1,6 @@
|
|||
import { log } from '../../../../ppcp-axo/resources/js/Helper/Debug';
|
||||
import { populateWooFields } from '../helpers/fieldHelpers';
|
||||
import { injectShippingChangeButton } from '../components/Shipping';
|
||||
import { injectCardChangeButton } from '../components/Card';
|
||||
import { setIsGuest, setIsEmailLookupCompleted } from '../stores/axoStore';
|
||||
|
||||
/**
|
||||
|
@ -16,7 +15,6 @@ import { setIsGuest, setIsEmailLookupCompleted } from '../stores/axoStore';
|
|||
* @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 = (
|
||||
|
@ -28,8 +26,7 @@ export const createEmailLookupHandler = (
|
|||
wooBillingAddress,
|
||||
setWooShippingAddress,
|
||||
setWooBillingAddress,
|
||||
onChangeShippingAddressClick,
|
||||
onChangeCardButtonClick
|
||||
onChangeShippingAddressClick
|
||||
) => {
|
||||
return async ( email ) => {
|
||||
try {
|
||||
|
@ -102,9 +99,8 @@ export const createEmailLookupHandler = (
|
|||
setWooBillingAddress
|
||||
);
|
||||
|
||||
// Inject change buttons for shipping and card
|
||||
// Inject the change button for shipping
|
||||
injectShippingChangeButton( onChangeShippingAddressClick );
|
||||
injectCardChangeButton( onChangeCardButtonClick );
|
||||
} else {
|
||||
log( 'Authentication failed or did not succeed', 'warn' );
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import { useDispatch } from '@wordpress/data';
|
|||
import { log } from '../../../../ppcp-axo/resources/js/Helper/Debug';
|
||||
import { STORE_NAME } from '../stores/axoStore';
|
||||
import { removeShippingChangeButton } from '../components/Shipping';
|
||||
import { removeCardChangeButton } from '../components/Card';
|
||||
import { removeWatermark } from '../components/Watermark';
|
||||
import {
|
||||
removeEmailFunctionality,
|
||||
|
@ -50,7 +49,6 @@ const useAxoCleanup = () => {
|
|||
|
||||
// Remove AXO UI elements
|
||||
removeShippingChangeButton();
|
||||
removeCardChangeButton();
|
||||
removeWatermark();
|
||||
|
||||
// Remove email functionality if it was set up
|
||||
|
|
|
@ -35,6 +35,7 @@ const useAxoSetup = (
|
|||
setIsAxoScriptLoaded,
|
||||
setShippingAddress,
|
||||
setCardDetails,
|
||||
setCardChangeHandler,
|
||||
} = useDispatch( STORE_NAME );
|
||||
|
||||
// Check if PayPal script has loaded
|
||||
|
@ -73,6 +74,7 @@ const useAxoSetup = (
|
|||
if ( paypalLoaded && fastlaneSdk ) {
|
||||
setIsAxoScriptLoaded( true );
|
||||
setIsAxoActive( true );
|
||||
setCardChangeHandler( onChangeCardButtonClick );
|
||||
|
||||
// Create and set up email lookup handler
|
||||
const emailLookupHandler = createEmailLookupHandler(
|
||||
|
@ -84,8 +86,7 @@ const useAxoSetup = (
|
|||
wooBillingAddress,
|
||||
setWooShippingAddress,
|
||||
setWooBillingAddress,
|
||||
onChangeShippingAddressClick,
|
||||
onChangeCardButtonClick
|
||||
onChangeShippingAddressClick
|
||||
);
|
||||
setupEmailFunctionality( emailLookupHandler );
|
||||
}
|
||||
|
|
36
modules/ppcp-axo-block/resources/js/hooks/useCardOptions.js
Normal file
36
modules/ppcp-axo-block/resources/js/hooks/useCardOptions.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { useMemo } from '@wordpress/element';
|
||||
|
||||
const DEFAULT_ALLOWED_CARDS = [ 'VISA', 'MASTERCARD', 'AMEX', 'DISCOVER' ];
|
||||
|
||||
/**
|
||||
* Custom hook to determine the allowed card options based on configuration.
|
||||
*
|
||||
* @param {Object} axoConfig - The AXO configuration object.
|
||||
* @return {Array} The final list of allowed card options.
|
||||
*/
|
||||
const useCardOptions = ( axoConfig ) => {
|
||||
const merchantCountry = axoConfig.merchant_country || 'US';
|
||||
|
||||
return useMemo( () => {
|
||||
const allowedCards = new Set(
|
||||
axoConfig.allowed_cards?.[ merchantCountry ] ||
|
||||
DEFAULT_ALLOWED_CARDS
|
||||
);
|
||||
|
||||
// Create a Set of disabled cards, converting each to uppercase
|
||||
const disabledCards = new Set(
|
||||
( axoConfig.disable_cards || [] ).map( ( card ) =>
|
||||
card.toUpperCase()
|
||||
)
|
||||
);
|
||||
|
||||
// Filter out disabled cards from the allowed cards
|
||||
const finalCardOptions = [ ...allowedCards ].filter(
|
||||
( card ) => ! disabledCards.has( card )
|
||||
);
|
||||
|
||||
return finalCardOptions;
|
||||
}, [ axoConfig.allowed_cards, axoConfig.disable_cards, merchantCountry ] );
|
||||
};
|
||||
|
||||
export default useCardOptions;
|
|
@ -3,6 +3,7 @@ import { useSelect } from '@wordpress/data';
|
|||
import Fastlane from '../../../../ppcp-axo/resources/js/Connection/Fastlane';
|
||||
import { log } from '../../../../ppcp-axo/resources/js/Helper/Debug';
|
||||
import { useDeleteEmptyKeys } from './useDeleteEmptyKeys';
|
||||
import useCardOptions from './useCardOptions';
|
||||
import { STORE_NAME } from '../stores/axoStore';
|
||||
|
||||
/**
|
||||
|
@ -26,6 +27,8 @@ const useFastlaneSdk = ( namespace, axoConfig, ppcpConfig ) => {
|
|||
[]
|
||||
);
|
||||
|
||||
const cardOptions = useCardOptions( axoConfig );
|
||||
|
||||
const styleOptions = useMemo( () => {
|
||||
return deleteEmptyKeys( configRef.current.axoConfig.style_options );
|
||||
}, [ deleteEmptyKeys ] );
|
||||
|
@ -48,10 +51,13 @@ const useFastlaneSdk = ( namespace, axoConfig, ppcpConfig ) => {
|
|||
window.localStorage.setItem( 'axoEnv', 'sandbox' );
|
||||
}
|
||||
|
||||
// Connect to Fastlane with locale and style options
|
||||
// Connect to Fastlane with locale, style options, and allowed card brands
|
||||
await fastlane.connect( {
|
||||
locale: configRef.current.ppcpConfig.locale,
|
||||
styles: styleOptions,
|
||||
cardOptions: {
|
||||
allowedBrands: cardOptions,
|
||||
},
|
||||
} );
|
||||
|
||||
// Set locale (hardcoded to 'en_us' for now)
|
||||
|
@ -66,7 +72,7 @@ const useFastlaneSdk = ( namespace, axoConfig, ppcpConfig ) => {
|
|||
};
|
||||
|
||||
initFastlane();
|
||||
}, [ fastlaneSdk, styleOptions, isPayPalLoaded, namespace ] );
|
||||
}, [ fastlaneSdk, styleOptions, isPayPalLoaded, namespace, cardOptions ] );
|
||||
|
||||
// Effect to update the config ref when configs change
|
||||
useEffect( () => {
|
||||
|
|
|
@ -13,6 +13,7 @@ import usePayPalCommerceGateway from './hooks/usePayPalCommerceGateway';
|
|||
|
||||
// Components
|
||||
import { Payment } from './components/Payment/Payment';
|
||||
import { TitleLabel } from './components/TitleLabel';
|
||||
|
||||
const gatewayHandle = 'ppcp-axo-gateway';
|
||||
const namespace = 'ppcpBlocksPaypalAxo';
|
||||
|
@ -89,12 +90,7 @@ const Axo = ( props ) => {
|
|||
|
||||
registerPaymentMethod( {
|
||||
name: initialConfig.id,
|
||||
label: (
|
||||
<div
|
||||
id="ppcp-axo-block-radio-label"
|
||||
dangerouslySetInnerHTML={ { __html: initialConfig.title } }
|
||||
/>
|
||||
),
|
||||
label: <TitleLabel config={ initialConfig } />,
|
||||
content: <Axo />,
|
||||
edit: createElement( initialConfig.title ),
|
||||
ariaLabel: initialConfig.title,
|
||||
|
|
|
@ -12,6 +12,7 @@ const DEFAULT_STATE = {
|
|||
shippingAddress: null,
|
||||
cardDetails: null,
|
||||
phoneNumber: '',
|
||||
cardChangeHandler: null,
|
||||
};
|
||||
|
||||
// Action creators for updating the store state
|
||||
|
@ -52,6 +53,10 @@ const actions = {
|
|||
type: 'SET_PHONE_NUMBER',
|
||||
payload: phoneNumber,
|
||||
} ),
|
||||
setCardChangeHandler: ( cardChangeHandler ) => ( {
|
||||
type: 'SET_CARD_CHANGE_HANDLER',
|
||||
payload: cardChangeHandler,
|
||||
} ),
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -81,6 +86,8 @@ const reducer = ( state = DEFAULT_STATE, action ) => {
|
|||
return { ...state, cardDetails: action.payload };
|
||||
case 'SET_PHONE_NUMBER':
|
||||
return { ...state, phoneNumber: action.payload };
|
||||
case 'SET_CARD_CHANGE_HANDLER':
|
||||
return { ...state, cardChangeHandler: action.payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -97,6 +104,7 @@ const selectors = {
|
|||
getShippingAddress: ( state ) => state.shippingAddress,
|
||||
getCardDetails: ( state ) => state.cardDetails,
|
||||
getPhoneNumber: ( state ) => state.phoneNumber,
|
||||
getCardChangeHandler: ( state ) => state.cardChangeHandler,
|
||||
};
|
||||
|
||||
// Create and register the Redux store for the AXO block
|
||||
|
@ -163,3 +171,12 @@ export const setCardDetails = ( cardDetails ) => {
|
|||
export const setPhoneNumber = ( phoneNumber ) => {
|
||||
dispatch( STORE_NAME ).setPhoneNumber( phoneNumber );
|
||||
};
|
||||
|
||||
/**
|
||||
* Action dispatcher to update the card change handler in the store.
|
||||
*
|
||||
* @param {Function} cardChangeHandler - The card change handler function.
|
||||
*/
|
||||
export const setCardChangeHandler = ( cardChangeHandler ) => {
|
||||
dispatch( STORE_NAME ).setCardChangeHandler( cardChangeHandler );
|
||||
};
|
||||
|
|
|
@ -37,7 +37,8 @@ return array(
|
|||
$container->get( 'wcgateway.settings' ),
|
||||
$container->get( 'wcgateway.configuration.dcc' ),
|
||||
$container->get( 'onboarding.environment' ),
|
||||
$container->get( 'wcgateway.url' )
|
||||
$container->get( 'wcgateway.url' ),
|
||||
$container->get( 'axo.supported-country-card-type-matrix' ),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -79,6 +79,13 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
|
|||
*/
|
||||
private $wcgateway_module_url;
|
||||
|
||||
/**
|
||||
* The supported country card type matrix.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $supported_country_card_type_matrix;
|
||||
|
||||
/**
|
||||
* AdvancedCardPaymentMethod constructor.
|
||||
*
|
||||
|
@ -91,6 +98,7 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
|
|||
* @param DCCGatewayConfiguration $dcc_configuration The DCC gateway settings.
|
||||
* @param Environment $environment The environment object.
|
||||
* @param string $wcgateway_module_url The WcGateway module URL.
|
||||
* @param array $supported_country_card_type_matrix The supported country card type matrix for Axo.
|
||||
*/
|
||||
public function __construct(
|
||||
string $module_url,
|
||||
|
@ -100,7 +108,8 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
|
|||
Settings $settings,
|
||||
DCCGatewayConfiguration $dcc_configuration,
|
||||
Environment $environment,
|
||||
string $wcgateway_module_url
|
||||
string $wcgateway_module_url,
|
||||
array $supported_country_card_type_matrix
|
||||
) {
|
||||
$this->name = AxoGateway::ID;
|
||||
$this->module_url = $module_url;
|
||||
|
@ -111,7 +120,7 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
|
|||
$this->dcc_configuration = $dcc_configuration;
|
||||
$this->environment = $environment;
|
||||
$this->wcgateway_module_url = $wcgateway_module_url;
|
||||
|
||||
$this->supported_country_card_type_matrix = $supported_country_card_type_matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -207,6 +216,8 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
|
|||
: null, // Set to null if WC()->cart is null or get_total doesn't exist.
|
||||
),
|
||||
),
|
||||
'allowed_cards' => $this->supported_country_card_type_matrix,
|
||||
'disable_cards' => $this->settings->has( 'disable_cards' ) ? (array) $this->settings->get( 'disable_cards' ) : array(),
|
||||
'style_options' => array(
|
||||
'root' => array(
|
||||
'backgroundColor' => $this->settings->has( 'axo_style_root_bg_color' ) ? $this->settings->get( 'axo_style_root_bg_color' ) : '',
|
||||
|
@ -243,6 +254,8 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
|
|||
),
|
||||
'logging_enabled' => $this->settings->has( 'logging_enabled' ) ? $this->settings->get( 'logging_enabled' ) : '',
|
||||
'wp_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG,
|
||||
'card_icons' => $this->settings->has( 'card_icons' ) ? (array) $this->settings->get( 'card_icons' ) : array(),
|
||||
'merchant_country' => WC()->countries->get_base_country(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue