🇲🇽 Add Mexico-specific logic for the new UI

This commit is contained in:
Daniel Dudzic 2025-05-21 12:45:15 +02:00
parent 82af3c07c9
commit 6738f80efc
No known key found for this signature in database
GPG key ID: 31B40D33E3465483
22 changed files with 456 additions and 87 deletions

View file

@ -61,6 +61,7 @@ return array(
'LT',
'LU',
'MT',
'MX',
'NL',
'PL',
'PT',

View file

@ -73,7 +73,8 @@ return array(
$container->get( 'wcgateway.settings' ),
$container->get( 'api.endpoint.partners' ),
$container->get( 'settings.flag.is-connected' ),
$container->get( 'api.helper.failure-registry' )
$container->get( 'api.helper.failure-registry' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'ppcp-local-apms.bancontact.wc-gateway' => static function ( ContainerInterface $container ): BancontactGateway {

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="30px" height="30px" viewBox="0 0 42 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>OXXO</title>
<defs/>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="SPB_&amp;_AltPay_NewAssets" transform="translate(-100.000000, -159.000000)">
<g id="logo-OXXO" transform="translate(100.000000, 159.000000)">
<path d="M0.142456528,1.48437917 C0.142456528,0.77043992 0.728159303,0.186243119 1.44446761,0.186243119 L40.6503931,0.186243119 C41.3667014,0.186243119 41.9524042,0.77043992 41.9524042,1.48437917 L41.9524042,18.1011373 C41.9524042,18.8150765 41.3667014,19.3990362 40.6503931,19.3990362 L1.44446761,19.3990362 C0.728159303,19.3990362 0.142456528,18.8150765 0.142456528,18.1011373 L0.142456528,1.48437917 Z" id="Fill-2" fill="#EDA42D"/>
<polygon id="Fill-4" fill="#FEFEFE" points="0.142480318 17.5124813 41.952428 17.5124813 41.952428 2.07265562 0.142480318 2.07265562"/>
<path d="M35.5752619,6.08262231 C33.662331,6.08262231 32.1029152,7.63763417 32.1029152,9.54463469 C32.1029152,11.4511608 33.662331,13.0064099 35.5752619,13.0064099 C37.4877171,13.0064099 39.0471329,11.4511608 39.0471329,9.54463469 C39.0471329,7.63763417 37.4877171,6.08262231 35.5752619,6.08262231" id="Fill-6" fill="#EC1D24"/>
<path d="M6.95585459,6.08262231 C5.04268574,6.08262231 3.48326994,7.63763417 3.48326994,9.54463469 C3.48326994,11.4511608 5.04268574,13.0064099 6.95585459,13.0064099 C8.86807185,13.0064099 10.4277255,11.4511608 10.4277255,9.54463469 C10.4277255,7.63763417 8.86807185,6.08262231 6.95585459,6.08262231" id="Fill-7" fill="#EC1D24"/>
<path d="M35.5752619,15.0141446 C32.5537303,15.0141446 30.0893537,12.5573397 30.0893537,9.54480072 C30.0893537,6.53155015 32.5537303,4.07521964 35.5752619,4.07521964 C38.5970315,4.07521964 41.0609322,6.53155015 41.0609322,9.54480072 C41.0609322,12.5573397 38.5970315,15.0141446 35.5752619,15.0141446 Z M12.4411918,9.54480072 C12.4411918,12.5573397 9.97729109,15.0141446 6.95575943,15.0141446 C3.93351408,15.0141446 1.46985124,12.5573397 1.46985124,9.54480072 C1.46985124,6.53155015 3.93351408,4.07521964 6.95575943,4.07521964 C9.97729109,4.07521964 12.4411918,6.53155015 12.4411918,9.54480072 Z M35.3028697,3.03585692 C32.0884035,2.9620911 30.5772808,5.01709763 28.384107,7.55170056 L26.3151155,9.94232969 L29.591435,13.8526295 C30.3719756,15.0542296 28.8822636,16.2465793 27.9580332,15.1472077 L24.9288888,11.5447794 L21.9772989,14.9562705 C21.0373673,16.0421223 19.5645461,14.8288999 20.3617394,13.6386849 L23.5659761,9.92382894 L21.4667717,7.42693908 L22.8173138,5.75949957 L24.9522028,8.31639828 L26.7923372,6.18217058 C27.6953948,5.13569219 28.6162946,3.74884741 29.8098246,3.03585692 L0.142385159,3.03585692 L0.142385159,16.549707 L7.07875226,16.549707 C10.2934564,16.549707 11.7529554,14.6332189 13.8866549,12.0492806 L15.8999784,9.61097649 L12.5334959,5.77752594 C11.726073,4.59418943 13.1874752,3.36815887 14.1371606,4.44594623 L17.2483795,7.9779294 L20.1209875,4.49931378 C21.0354641,3.39164059 22.5356435,4.57118208 21.7662842,5.77942346 L18.6486421,9.56757088 L20.8051797,12.0153626 L19.4463112,13.6197098 L17.2997653,11.2058361 L15.5095892,13.3813347 C14.6310351,14.4484486 13.7415376,15.8094397 12.5646605,16.549707 L41.9523328,16.549707 L41.9523328,3.03585692 L35.3028697,3.03585692 Z" id="Fill-8" fill="#EC1D24"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -42,7 +42,8 @@ const PaymentFlow = ( {
/>
);
}
const description = useAcdc ? optionalDescription : '';
const description =
useAcdc && 'MX' !== storeCountry ? optionalDescription : '';
return (
<div className="ppcp-r-welcome-docs__wrapper">
<DefaultMethodsSection

View file

@ -1,20 +1,29 @@
import { __ } from '@wordpress/i18n';
import { useWooSettings } from '../../../../../data/common/hooks';
import PricingTitleBadge from '../../../../ReusableComponents/PricingTitleBadge';
import BadgeBox from '../../../../ReusableComponents/BadgeBox';
const AlternativePaymentMethods = ( { learnMore = '' } ) => {
const { storeCountry } = useWooSettings();
// Determine which icons to display based on the country code.
const imageBadges =
storeCountry === 'MX'
? [ 'icon-button-oxxo.svg' ]
: [
// 'icon-button-sepa.svg', // Enable this when the SEPA-Gateway is ready.
'icon-button-ideal.svg',
'icon-button-blik.svg',
'icon-button-bancontact.svg',
];
return (
<BadgeBox
title={ __(
'Alternative Payment Methods',
'woocommerce-paypal-payments'
) }
imageBadge={ [
// 'icon-button-sepa.svg', // Enable this when the SEPA-Gateway is ready.
'icon-button-ideal.svg',
'icon-button-blik.svg',
'icon-button-bancontact.svg',
] }
imageBadge={ imageBadges }
textBadge={ <PricingTitleBadge item="apm" /> }
description={ __(
'Seamless payments for customers across the globe using their preferred payment methods.',

View file

@ -10,13 +10,13 @@ import PaymentFlow from '../Components/PaymentFlow';
const StepPaymentMethods = () => {
const { optionalMethods, setOptionalMethods } =
OnboardingHooks.useOptionalPaymentMethods();
const { ownBrandOnly } = CommonHooks.useWooSettings();
const { ownBrandOnly, storeCountry } = CommonHooks.useWooSettings();
const { isCasualSeller } = OnboardingHooks.useBusiness();
const { canUseCardPayments } = OnboardingHooks.useFlags();
const optionalMethodTitle = useMemo( () => {
// The BCDC flow does not show a title. !acdc does not show a title.
if ( isCasualSeller || ! canUseCardPayments ) {
// The BCDC flow does not show a title. !acdc does not show a title. Mexico does not show a title.
if ( isCasualSeller || ! canUseCardPayments || 'MX' === storeCountry ) {
return null;
}
@ -24,7 +24,7 @@ const StepPaymentMethods = () => {
'Available with additional application',
'woocommerce-paypal-payments'
);
}, [ isCasualSeller, canUseCardPayments ] );
}, [ isCasualSeller, canUseCardPayments, storeCountry ] );
const methodChoices = [
{
@ -34,7 +34,7 @@ const StepPaymentMethods = () => {
},
{
title:
ownBrandOnly || ! canUseCardPayments
ownBrandOnly || ! canUseCardPayments || 'MX' === storeCountry
? __(
'No thanks, I prefer to use a different provider for local payment methods',
'woocommerce-paypal-payments'
@ -87,7 +87,9 @@ const OptionalMethodDescription = () => {
return (
<PaymentFlow
onlyOptional={ true }
useAcdc={ ! isCasualSeller && canUseCardPayments }
useAcdc={
! isCasualSeller && canUseCardPayments && 'MX' !== storeCountry
}
isFastlane={ true }
isPayLater={ true }
ownBrandOnly={ ownBrandOnly }

View file

@ -14,6 +14,7 @@ import { usePaymentConfig } from '../hooks/usePaymentConfig';
const StepWelcome = ( { setStep, currentStep } ) => {
const { storeCountry, ownBrandOnly } = CommonHooks.useWooSettings();
const { canUseCardPayments, canUseFastlane } = OnboardingHooks.useFlags();
const { isCasualSeller } = OnboardingHooks.useBusiness();
const { icons } = usePaymentConfig(
storeCountry,
@ -23,7 +24,7 @@ const StepWelcome = ( { setStep, currentStep } ) => {
);
const onboardingHeaderDescription =
canUseCardPayments && ! ownBrandOnly
canUseCardPayments && ! ownBrandOnly && 'MX' !== storeCountry
? __(
'Your all-in-one integration for PayPal checkout solutions that enable buyers to pay via PayPal, Pay Later, all major credit/debit cards, Apple Pay, Google Pay, and more.',
'woocommerce-paypal-payments'

View file

@ -15,6 +15,8 @@ import {
CreditDebitCards,
} from '../Components/PaymentOptions';
import { useWooSettings } from '../../../../data/common/hooks';
// List of all payment icons and which requirements they have.
const PAYMENT_ICONS = [
{ name: 'paypal', always: true },
@ -28,6 +30,7 @@ const PAYMENT_ICONS = [
{ name: 'blik', isOwnBrand: true, onlyAcdc: true },
{ name: 'ideal', isOwnBrand: true, onlyAcdc: true },
{ name: 'bancontact', isOwnBrand: true, onlyAcdc: true },
{ name: 'oxxo', isOwnBrand: true, onlyAcdc: false, countries: [ 'MX' ] },
];
// Default configuration, used for all countries, unless they override individual attributes below.
@ -211,6 +214,11 @@ const getRelevantIcons = ( country, includeAcdc, onlyBranded ) =>
return true;
}
// If we're in Mexico, only show OXXO from the APMs.
if ( country === 'MX' && onlyAcdc ) {
return false;
}
if ( onlyBranded && ! isOwnBrand ) {
return false;
}
@ -251,6 +259,7 @@ export const usePaymentConfig = (
hasFastlane,
ownBrandOnly
) => {
const { countryCode } = useWooSettings();
return useMemo( () => {
// eslint-disable-next-line no-console
console.log( '[Payment Config]', {
@ -277,8 +286,11 @@ export const usePaymentConfig = (
const availableOptionalMethods = filterMethods(
config.extendedMethods,
[
// Either include Acdc or non-Acdc methods.
( method ) => method.isAcdc === canUseCardPayments,
// Either include Acdc or non-Acdc methods except for Mexico.
( method ) =>
countryCode === 'MX'
? ! method.isAcdc || canUseCardPayments
: method.isAcdc === canUseCardPayments,
// Only include own-brand methods when ownBrandOnly is true.
( method ) => ! ownBrandOnly || method.isOwnBrand === true,
// Only include Fastlane when hasFastlane is true.

View file

@ -6,7 +6,10 @@ import FeatureItem from './FeatureItem';
import FeatureDescription from './FeatureDescription';
import { ContentWrapper } from '../../../../../ReusableComponents/Elements';
import SettingsCard from '../../../../../ReusableComponents/SettingsCard';
import { useMerchantInfo } from '../../../../../../data/common/hooks';
import {
useMerchantInfo,
useWooSettings,
} from '../../../../../../data/common/hooks';
import { STORE_NAME as COMMON_STORE_NAME } from '../../../../../../data/common';
import {
NOTIFICATION_ERROR,
@ -17,6 +20,7 @@ import { useFeatures } from '../../../../../../data/features/hooks';
const Features = () => {
const [ isRefreshing, setIsRefreshing ] = useState( false );
const { merchant } = useMerchantInfo();
const { storeCountry } = useWooSettings();
const { features, fetchFeatures } = useFeatures();
const { refreshFeatureStatuses } = useDispatch( COMMON_STORE_NAME );
const { createSuccessNotice, createErrorNotice } =
@ -26,6 +30,13 @@ const Features = () => {
return null;
}
// Filter out ACDC for Mexico (when disabled).
const filteredFeatures = features.filter(
( feature ) =>
feature.id !== 'advanced_credit_and_debit_cards' ||
storeCountry !== 'MX'
);
const refreshHandler = async () => {
setIsRefreshing( true );
try {
@ -86,7 +97,7 @@ const Features = () => {
aria-busy={ isRefreshing }
>
<ContentWrapper>
{ features.map( ( { id, enabled, ...feature } ) => (
{ filteredFeatures.map( ( { id, enabled, ...feature } ) => (
<FeatureItem
key={ id }
isBusy={ isRefreshing }

View file

@ -4,9 +4,8 @@ import { PaymentMethodsBlock } from '../../../../ReusableComponents/SettingsBloc
import usePaymentDependencyState from '../../../../../hooks/usePaymentDependencyState';
import useSettingDependencyState from '../../../../../hooks/useSettingDependencyState';
import usePaymentMethodsToggle from '../../../../../hooks/usePaymentMethodsToggle';
import useDependencyMessages from '../../../../../hooks/useDependencyMessages';
import BulkPaymentToggle from './BulkPaymentToggle';
import PaymentDependencyMessage from './PaymentDependencyMessage';
import SettingDependencyMessage from './SettingDependencyMessage';
import SpinnerOverlay from '../../../../ReusableComponents/SpinnerOverlay';
import {
PaymentHooks,
@ -60,6 +59,13 @@ const PaymentMethodCard = ( {
const settingDependencies = useSettingDependencyState( methods );
const dependencyMessagesMap = useDependencyMessages(
methods,
paymentDependencies,
settingDependencies,
isDisabled
);
// Initialize the bulk toggle functionality.
const { allEnabled, toggleAllMethods, methodCount } =
usePaymentMethodsToggle( {
@ -86,37 +92,17 @@ const PaymentMethodCard = ( {
return <SpinnerOverlay asModal={ true } />;
}
// Process methods with dependencies.
// Process methods with dependencies from the pre-computed map.
const processedMethods = methods.map( ( method ) => {
const paymentDependency = paymentDependencies?.[ method.id ];
const settingDependency = settingDependencies?.[ method.id ];
let dependencyMessage = null;
let isMethodDisabled = method.isDisabled || isDisabled;
if ( paymentDependency ) {
dependencyMessage = (
<PaymentDependencyMessage
parentId={ paymentDependency.parentId }
parentName={ paymentDependency.parentName }
/>
);
isMethodDisabled = true;
} else if ( settingDependency?.isDisabled ) {
dependencyMessage = (
<SettingDependencyMessage
settingId={ settingDependency.settingId }
requiredValue={ settingDependency.requiredValue }
methodId={ method.id }
/>
);
isMethodDisabled = true;
}
const dependencyInfo = dependencyMessagesMap[ method.id ] || {};
return {
...method,
isDisabled: isMethodDisabled,
disabledMessage: dependencyMessage,
isDisabled:
dependencyInfo.isMethodDisabled ||
method.isDisabled ||
isDisabled,
disabledMessage: dependencyInfo.dependencyMessage,
};
} );

View file

@ -0,0 +1,49 @@
import { createInterpolateElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { scrollAndHighlight } from '../../../../../utils/scrollAndHighlight';
/**
* Component to display a payment method value dependency message
*
* @param {Object} props - Component props
* @param {string} props.dependentMethodId - ID of the dependent payment method
* @param {string} props.dependentMethodName - Display name of the dependent payment method
* @param {boolean} props.requiredValue - Required value (enabled/disabled state) for the dependent method
* @return {JSX.Element} The formatted message with link
*/
const PaymentMethodValueDependencyMessage = ( {
dependentMethodId,
dependentMethodName,
requiredValue,
} ) => {
const displayName = dependentMethodName || dependentMethodId;
// Determine appropriate message template based on the required value
const template = requiredValue
? __(
'Enable <methodLink /> to use this method.',
'woocommerce-paypal-payments'
)
: __(
'Disable <methodLink /> to use this method.',
'woocommerce-paypal-payments'
);
return createInterpolateElement( template, {
methodLink: (
<strong>
<a
href="#"
onClick={ ( e ) => {
e.preventDefault();
scrollAndHighlight( dependentMethodId );
} }
>
{ displayName }
</a>
</strong>
),
} );
};
export default PaymentMethodValueDependencyMessage;

View file

@ -2,15 +2,17 @@ import { __ } from '@wordpress/i18n';
import { useCallback } from '@wordpress/element';
import { CommonHooks, OnboardingHooks, PaymentHooks } from '../../../../data';
import { useActiveModal } from '../../../../data/common/hooks';
import { useActiveModal, useWooSettings } from '../../../../data/common/hooks';
import Modal from '../Components/Payment/Modal';
import PaymentMethodCard from '../Components/Payment/PaymentMethodCard';
import { useFeatures } from '../../../../data/features/hooks';
const TabPaymentMethods = () => {
const methods = PaymentHooks.usePaymentMethods();
const store = PaymentHooks.useStore();
const { setPersistent, changePaymentSettings } = store;
const { activeModal, setActiveModal } = useActiveModal();
const { features } = useFeatures();
// Get all methods as a map for dependency checking
const methodsMap = {};
@ -52,12 +54,31 @@ const TabPaymentMethods = () => {
);
const merchant = CommonHooks.useMerchant();
const { storeCountry } = useWooSettings();
const { canUseCardPayments } = OnboardingHooks.useFlags();
const showCardPayments =
methods.cardPayment.length > 0 &&
merchant.isBusinessSeller &&
canUseCardPayments;
canUseCardPayments &&
// Show ACDC if the merchant has the feature enabled in PayPal account.
features.some(
( feature ) =>
feature.id === 'advanced_credit_and_debit_cards' &&
feature.enabled
);
// Hide BCDC for all countries except Mexico when ACDC is turned on.
const filteredPayPalMethods = methods.paypal.filter(
( method ) =>
method.id !== 'ppcp-card-button-gateway' ||
storeCountry === 'MX' ||
! features.some(
( feature ) =>
feature.id === 'advanced_credit_and_debit_cards' &&
feature.enabled === true
)
);
const showApms = methods.apm.length > 0 && merchant.isBusinessSeller;
return (
@ -70,7 +91,7 @@ const TabPaymentMethods = () => {
'woocommerce-paypal-payments'
) }
icon="icon-checkout-standard.svg"
methods={ methods.paypal }
methods={ filteredPayPalMethods }
onTriggerModal={ setActiveModal }
methodsMap={ methodsMap }
/>

View file

@ -162,14 +162,15 @@ export const useNavigationState = () => {
};
};
export const useDetermineProducts = ( ownBrandOnly ) => {
export const useDetermineProducts = ( ownBrandOnly, storeCountry ) => {
return useSelect(
( select ) => {
return select( STORE_NAME ).determineProductsAndCaps(
ownBrandOnly
ownBrandOnly,
storeCountry
);
},
[ ownBrandOnly ]
[ ownBrandOnly, storeCountry ]
);
};

View file

@ -35,9 +35,14 @@ export const flags = ( state ) => {
*
* @param {{}} state
* @param {boolean} ownBrandOnly
* @param {string} storeCountry
* @return {{products:string[], options:{}}} The ISU products, based on choices made in the onboarding wizard.
*/
export const determineProductsAndCaps = ( state, ownBrandOnly ) => {
export const determineProductsAndCaps = (
state,
ownBrandOnly,
storeCountry
) => {
/**
* An array of product-names that are used to build an onboarding URL via the
* PartnerReferrals API. To avoid confusion with the "products" property from the
@ -80,7 +85,7 @@ export const determineProductsAndCaps = ( state, ownBrandOnly ) => {
if ( canUseVaulting ) {
apiModules.push( PAYPAL_PRODUCTS.VAULTING );
}
} else if ( isCasualSeller ) {
} else if ( isCasualSeller || 'MX' === storeCountry ) {
/**
* Branch 2: Merchant has no business.
* The store uses the Express-checkout product.

View file

@ -0,0 +1,83 @@
// hooks/useDependencyMessages.js
import { useMemo } from '@wordpress/element';
import PaymentDependencyMessage from '../Components/Screens/Settings/Components/Payment/PaymentDependencyMessage';
import PaymentMethodValueDependencyMessage from '../Components/Screens/Settings/Components/Payment/PaymentMethodValueDependencyMessage';
import SettingDependencyMessage from '../Components/Screens/Settings/Components/Payment/SettingDependencyMessage';
/**
* Hook to process dependency messages for all methods
*
* @param {Array} methods - List of payment methods
* @param {Object} paymentDependencies - Payment method dependencies
* @param {Object} settingDependencies - Setting dependencies
* @param {boolean} isDisabled - Whether methods are globally disabled
* @return {Object} Map of method IDs to their dependency messages and disabled states
*/
const useDependencyMessages = (
methods,
paymentDependencies,
settingDependencies,
isDisabled = false
) => {
return useMemo( () => {
const result = {};
if ( ! methods || ! methods.length ) {
return result;
}
// Process each method once to create their dependency messages.
methods.forEach( ( method ) => {
if ( ! method || ! method.id ) {
return;
}
let dependencyMessage = null;
let isMethodDisabled = method.isDisabled || isDisabled;
// Check payment dependencies
const dependency = paymentDependencies?.[ method.id ];
if ( dependency ) {
if ( dependency.type === 'parent' ) {
dependencyMessage = (
<PaymentDependencyMessage
parentId={ dependency.parentId }
parentName={ dependency.parentName }
/>
);
} else if ( dependency.type === 'value' ) {
dependencyMessage = (
<PaymentMethodValueDependencyMessage
dependentMethodId={ dependency.dependentId }
dependentMethodName={ dependency.dependentName }
requiredValue={ dependency.requiredValue }
/>
);
}
isMethodDisabled = true;
}
// Check setting dependencies
else if ( settingDependencies?.[ method.id ]?.isDisabled ) {
const settingDependency = settingDependencies[ method.id ];
dependencyMessage = (
<SettingDependencyMessage
settingId={ settingDependency.settingId }
requiredValue={ settingDependency.requiredValue }
methodId={ method.id }
/>
);
isMethodDisabled = true;
}
// Store the results for this method
result[ method.id ] = {
dependencyMessage,
isMethodDisabled,
};
} );
return result;
}, [ methods, paymentDependencies, settingDependencies, isDisabled ] );
};
export default useDependencyMessages;

View file

@ -31,9 +31,11 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
const { onboardingUrl } = isSandbox
? CommonHooks.useSandbox()
: CommonHooks.useProduction();
const { ownBrandOnly } = CommonHooks.useWooSettings();
const { products, options } =
OnboardingHooks.useDetermineProducts( ownBrandOnly );
const { ownBrandOnly, storeCountry } = CommonHooks.useWooSettings();
const { products, options } = OnboardingHooks.useDetermineProducts(
ownBrandOnly,
storeCountry
);
const { startActivity } = CommonHooks.useBusyState();
const { authenticateWithOAuth } = CommonHooks.useAuthentication();
const [ onboardingUrlState, setOnboardingUrl ] = useState( '' );

View file

@ -6,15 +6,13 @@ import { useSelect } from '@wordpress/data';
/**
* Gets the display name for a parent payment method
*
* @param {string} parentId - ID of the parent payment method
* @param {string} methodId - ID of the payment method
* @param {Object} methodsMap - Map of all payment methods by ID
* @return {string} The display name to use for the parent method
* @return {string} The display name of the method
*/
const getParentMethodName = ( parentId, methodsMap ) => {
const parentMethod = methodsMap[ parentId ];
return parentMethod
? parentMethod.itemTitle || parentMethod.title || ''
: '';
const getMethodName = ( methodId, methodsMap ) => {
const method = methodsMap[ methodId ];
return method ? method.itemTitle || method.title || '' : '';
};
/**
@ -38,7 +36,51 @@ const findDisabledParents = ( method, methodsMap ) => {
};
/**
* Hook to evaluate payment method dependencies
* Checks if method should be disabled due to value dependencies
*
* @param {Object} method - The payment method to check
* @param {Object} methodsMap - Map of all payment methods by ID
* @return {Object|null} Value dependency info if should be disabled, null otherwise
*/
const checkValueDependencies = ( method, methodsMap ) => {
const valueDependencies = method.depends_on_payment_methods_values;
if ( ! valueDependencies ) {
return null;
}
// Check each dependency against the actual state of the dependent method.
for ( const [ dependentId, requiredValue ] of Object.entries(
valueDependencies
) ) {
const dependent = methodsMap[ dependentId ];
if ( ! dependent ) {
continue;
}
// Example: card-button-gateway depends on credit-card-gateway being FALSE.
// So if credit-card-gateway is TRUE (enabled), card-button-gateway should be disabled.
// If the dependency requires a method to be false but it's enabled (or vice versa).
if (
typeof requiredValue === 'boolean' &&
dependent.enabled !== requiredValue
) {
// This dependency is violated - the dependent method is in the wrong state.
return {
dependentId,
dependentName: getMethodName( dependentId, methodsMap ),
requiredValue,
};
}
}
return null;
};
/**
* Hook to evaluate all payment method dependencies
*
* @param {Array} methods - List of payment methods
* @param {Object} methodsMap - Map of payment methods by ID
@ -51,6 +93,7 @@ const usePaymentDependencyState = ( methods, methodsMap ) => {
if ( methods && methodsMap && Object.keys( methodsMap ).length > 0 ) {
methods.forEach( ( method ) => {
if ( method && method.id ) {
// Check regular parent-child dependencies first.
const disabledParents = findDisabledParents(
method,
methodsMap
@ -59,18 +102,32 @@ const usePaymentDependencyState = ( methods, methodsMap ) => {
if ( disabledParents.length > 0 ) {
const parentId = disabledParents[ 0 ];
result[ method.id ] = {
type: 'parent',
isDisabled: true,
parentId,
parentName: getParentMethodName(
parentId,
methodsMap
),
parentName: getMethodName( parentId, methodsMap ),
};
return; // Skip other checks if already disabled.
}
// Check value dependencies.
const valueDependency = checkValueDependencies(
method,
methodsMap
);
if ( valueDependency ) {
result[ method.id ] = {
type: 'value',
isDisabled: true,
dependentId: valueDependency.dependentId,
dependentName: valueDependency.dependentName,
requiredValue: valueDependency.requiredValue,
};
}
}
} );
}
return result;
}, [ methods, methodsMap ] );
};

View file

@ -568,16 +568,17 @@ return array(
);
// Merchant capabilities, serve to show active or inactive badge and buttons.
$capabilities = array(
'apple_pay' => $features['apple_pay']['enabled'] ?? false,
'google_pay' => $features['google_pay']['enabled'] ?? false,
'acdc' => $features['advanced_credit_and_debit_cards']['enabled'] ?? false,
'save_paypal' => $features['save_paypal_and_venmo']['enabled'] ?? false,
'apple_pay' => $features['apple_pay']['enabled'] ?? false,
'google_pay' => $features['google_pay']['enabled'] ?? false,
'acdc' => $features['advanced_credit_and_debit_cards']['enabled'] ?? false,
'save_paypal' => $features['save_paypal_and_venmo']['enabled'] ?? false,
'alternative_payment_methods' => $features['alternative_payment_methods']['enabled'] ?? false,
);
$merchant_capabilities = array(
'save_paypal' => $capabilities['save_paypal'], // Save PayPal and Venmo eligibility.
'acdc' => $capabilities['acdc'] && ! $gateways['card-button'], // Advanced credit and debit cards eligibility.
'apm' => ! $gateways['card-button'], // Alternative payment methods eligibility.
'acdc' => $capabilities['acdc'], // Advanced credit and debit cards eligibility.
'apm' => $capabilities['alternative_payment_methods'], // Alternative payment methods eligibility.
'google_pay' => $capabilities['acdc'] && $capabilities['google_pay'], // Google Pay eligibility.
'apple_pay' => $capabilities['acdc'] && $capabilities['apple_pay'], // Apple Pay eligibility.
'pay_later' => $capabilities['acdc'] && ! $gateways['card-button'], // Pay Later eligibility.

View file

@ -107,6 +107,27 @@ class PaymentMethodsDependenciesDefinition {
);
}
/**
* Get payment method value dependencies for a specific method
*
* @return array Value dependencies for the method or empty array if none exist
*/
public function get_payment_method_value_dependencies(): array {
$dependencies = array(
CardButtonGateway::ID => array(
CreditCardGateway::ID => false,
),
CreditCardGateway::ID => array(
CardButtonGateway::ID => false,
),
);
return apply_filters(
'woocommerce_paypal_payments_method_value_dependencies',
$dependencies
);
}
/**
* Get method setting dependencies
*
@ -139,6 +160,15 @@ class PaymentMethodsDependenciesDefinition {
$method['depends_on_payment_methods'] = $payment_method_dependencies;
}
// Add payment method value dependency info if applicable.
$all_payment_method_value_dependencies = $this->get_payment_method_value_dependencies();
// Only add dependencies that directly apply to this method.
if ( isset( $all_payment_method_value_dependencies[ $method_id ] ) ) {
// Direct dependencies on other method values.
$method['depends_on_payment_methods_values'] = $all_payment_method_value_dependencies[ $method_id ];
}
// Check if this method has setting dependencies.
$method_setting_dependencies = $this->get_method_setting_dependencies( $method_id );
if ( ! empty( $method_setting_dependencies ) ) {

View file

@ -197,6 +197,10 @@ class PaymentRestEndpoint extends RestEndpoint {
$gateway_settings[ $key ]['depends_on_payment_methods'] = $payment_method['depends_on_payment_methods'];
}
if ( isset( $payment_method['depends_on_payment_methods_values'] ) ) {
$gateway_settings[ $key ]['depends_on_payment_methods_values'] = $payment_method['depends_on_payment_methods_values'];
}
if ( isset( $payment_method['depends_on_settings'] ) ) {
$gateway_settings[ $key ]['depends_on_settings'] = $payment_method['depends_on_settings'];
}

View file

@ -270,10 +270,14 @@ class SettingsDataManager {
$this->payment_methods->toggle_method_state( CardButtonGateway::ID, true );
}
// Enable all APM methods.
foreach ( $methods_apm as $method ) {
$this->payment_methods->toggle_method_state( $method['id'], true );
}
/**
* Allow plugins to modify apm payment gateway states before saving.
*
* @param PaymentSettings $payment_methods The payment methods object.
* @param PaymentSettings $methods_apm List of APM methods.
* @param ConfigurationFlagsDTO $flags Configuration flags that determine which gateways to enable.
*/
do_action( 'woocommerce_paypal_payments_toggle_payment_gateways_apms', $this->payment_methods, $methods_apm, $flags );
}
/**

View file

@ -13,8 +13,10 @@ use WC_Payment_Gateway;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution;
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway;
use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus;
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway;
use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus;
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\BancontactGateway;
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\BlikGateway;
@ -321,11 +323,13 @@ class SettingsModule implements ServiceModule, ExecutableModule {
// Unset BCDC if merchant is eligible for ACDC and country is eligible for card fields.
$card_fields_eligible = $container->get( 'card-fields.eligible' );
if ( $dcc_product_status->is_active() && $card_fields_eligible ) {
unset( $payment_methods[ CardButtonGateway::ID ] );
} else {
// For non-ACDC regions unset ACDC.
unset( $payment_methods[ CreditCardGateway::ID ] );
if ( 'MX' !== $container->get( 'api.shop.country' ) ) {
if ( $dcc_product_status->is_active() && $card_fields_eligible ) {
unset( $payment_methods[ CardButtonGateway::ID ] );
} else {
// For non-ACDC regions unset ACDC.
unset( $payment_methods[ CreditCardGateway::ID ] );
}
}
// Unset Venmo when store location is not United States.
@ -358,6 +362,18 @@ class SettingsModule implements ServiceModule, ExecutableModule {
unset( $payment_methods[ PayUponInvoiceGateway::ID ] );
}
// Unset all APMs other than OXXO for Mexico.
if ( 'MX' === $merchant_country ) {
unset( $payment_methods[ BancontactGateway::ID ] );
unset( $payment_methods[ BlikGateway::ID ] );
unset( $payment_methods[ EPSGateway::ID ] );
unset( $payment_methods[ IDealGateway::ID ] );
unset( $payment_methods[ MyBankGateway::ID ] );
unset( $payment_methods[ P24Gateway::ID ] );
unset( $payment_methods[ TrustlyGateway::ID ] );
unset( $payment_methods[ MultibancoGateway::ID ] );
}
return $payment_methods;
}
);
@ -539,6 +555,62 @@ class SettingsModule implements ServiceModule, ExecutableModule {
$payment_methods->toggle_method_state( AxoGateway::ID, true );
}
}
$general_settings = $container->get( 'settings.data.general' );
assert( $general_settings instanceof GeneralSettings );
$merchant_data = $general_settings->get_merchant_data();
$merchant_country = $merchant_data->merchant_country;
// Disable all extended checkout card methods if the store is in Mexico.
if ( 'MX' === $merchant_country ) {
$payment_methods->toggle_method_state( CreditCardGateway::ID, false );
$payment_methods->toggle_method_state( ApplePayGateway::ID, false );
$payment_methods->toggle_method_state( GooglePayGateway::ID, false );
}
},
10,
2
);
// Enable APMs after onboarding if the country is compatible.
add_action(
'woocommerce_paypal_payments_toggle_payment_gateways_apms',
function ( PaymentSettings $payment_methods, array $methods_apm ) use ( $container ) {
$general_settings = $container->get( 'settings.data.general' );
assert( $general_settings instanceof GeneralSettings );
$merchant_data = $general_settings->get_merchant_data();
$merchant_country = $merchant_data->merchant_country;
$logger = $container->get( 'woocommerce.logger.woocommerce' );
assert( $logger instanceof LoggerInterface );
$logger->info( 'Merchant country: ' . $merchant_country );
$logger->info( 'Merchant data: ' . json_encode( $merchant_data ) );
$logger->info( '$methods_apm: ' . json_encode( $methods_apm ) );
// Enable all APM methods.
foreach ( $methods_apm as $method ) {
// Skip PayUponInvoice if merchant is not in Germany.
if ( PayUponInvoiceGateway::ID === $method['id'] && 'DE' !== $merchant_country ) {
continue;
}
// For OXXO: enable ONLY if merchant is in Mexico.
if ( OXXO::ID === $method['id'] ) {
if ( 'MX' === $merchant_country ) {
$payment_methods->toggle_method_state( $method['id'], true );
}
continue;
}
// For all other APMs: enable only if merchant is NOT in Mexico.
if ( 'MX' !== $merchant_country ) {
$payment_methods->toggle_method_state( $method['id'], true );
}
}
},
10,
2