Merge branch 'trunk' into PCP-4156-implement-3ds-for-google-pay

This commit is contained in:
carmenmaymo 2025-03-20 16:30:05 +01:00
commit 784af34b1e
No known key found for this signature in database
GPG key ID: 6023F686B0F3102E
38 changed files with 1030 additions and 784 deletions

View file

@ -54,7 +54,7 @@ class Renderer {
const enabledSeparateGateways = Object.fromEntries(
Object.entries( settings.separate_buttons ).filter(
( [ s, data ] ) => document.querySelector( data.wrapper )
( [ , data ] ) => document.querySelector( data.wrapper )
)
);
const hasEnabledSeparateGateways =
@ -65,15 +65,16 @@ class Renderer {
this.renderButtons(
settings.button.wrapper,
settings.button.style,
contextConfig,
hasEnabledSeparateGateways
contextConfig
);
}
} else {
const allFundingSources = paypal.getFundingSources();
const separateFunding = allFundingSources.filter(
( s ) => ! ( s in enabledSeparateGateways )
);
// render each button separately
for ( const fundingSource of paypal
.getFundingSources()
.filter( ( s ) => ! ( s in enabledSeparateGateways ) ) ) {
for ( const fundingSource of separateFunding ) {
const style = normalizeStyleForFundingSource(
settings.button.style,
fundingSource
@ -83,7 +84,6 @@ class Renderer {
settings.button.wrapper,
style,
contextConfig,
hasEnabledSeparateGateways,
fundingSource
);
}
@ -103,26 +103,15 @@ class Renderer {
data.wrapper,
data.style,
contextConfig,
hasEnabledSeparateGateways,
fundingSource
);
}
}
renderButtons(
wrapper,
style,
contextConfig,
hasEnabledSeparateGateways,
fundingSource = null
) {
renderButtons( wrapper, style, contextConfig, fundingSource = null ) {
if (
! document.querySelector( wrapper ) ||
this.isAlreadyRendered(
wrapper,
fundingSource,
hasEnabledSeparateGateways
)
this.isAlreadyRendered( wrapper, fundingSource )
) {
// Try to render registered buttons again in case they were removed from the DOM by an external source.
widgetBuilder.renderButtons( [ wrapper, fundingSource ] );
@ -144,10 +133,7 @@ class Renderer {
this.onSmartButtonClick( data, actions );
}
venmoButtonClicked = false;
if ( data.fundingSource === 'venmo' ) {
venmoButtonClicked = true;
}
venmoButtonClicked = data.fundingSource === 'venmo';
},
onInit: ( data, actions ) => {
if ( this.onSmartButtonsInit ) {
@ -161,29 +147,29 @@ class Renderer {
if ( this.shouldEnableShippingCallback() ) {
options.onShippingOptionsChange = ( data, actions ) => {
const shippingOptionsChange =
! this.isVenmoButtonClickedWhenVaultingIsEnabled(
venmoButtonClicked
)
? handleShippingOptionsChange(
data,
actions,
this.defaultSettings
)
: null;
! this.isVenmoButtonClickedWhenVaultingIsEnabled(
venmoButtonClicked
)
? handleShippingOptionsChange(
data,
actions,
this.defaultSettings
)
: null;
return shippingOptionsChange;
};
options.onShippingAddressChange = ( data, actions ) => {
const shippingAddressChange =
! this.isVenmoButtonClickedWhenVaultingIsEnabled(
venmoButtonClicked
)
? handleShippingAddressChange(
data,
actions,
this.defaultSettings
)
: null;
! this.isVenmoButtonClickedWhenVaultingIsEnabled(
venmoButtonClicked
)
? handleShippingAddressChange(
data,
actions,
this.defaultSettings
)
: null;
return shippingAddressChange;
};
@ -228,12 +214,11 @@ class Renderer {
}
);
this.renderedSources.add( wrapper + ( fundingSource ?? '' ) );
this.renderedSources.add(
wrapper + ( fundingSource ? fundingSource : '' )
);
if (
typeof paypal !== 'undefined' &&
typeof paypal.Buttons !== 'undefined'
) {
if ( window.paypal?.Buttons ) {
widgetBuilder.registerButtons(
[ wrapper, fundingSource ],
buttonsOptions()
@ -246,15 +231,16 @@ class Renderer {
return venmoButtonClicked && this.defaultSettings.vaultingEnabled;
};
shouldEnableShippingCallback = () => {
shouldEnableShippingCallback = () => {
const needShipping =
this.defaultSettings.needShipping ||
this.defaultSettings.context === 'product';
return (
this.defaultSettings.should_handle_shipping_in_paypal &&
needShipping
);
};
};
isAlreadyRendered( wrapper, fundingSource ) {
return this.renderedSources.has( wrapper + ( fundingSource ?? '' ) );
@ -272,6 +258,7 @@ class Renderer {
this.onButtonsInitListeners[ wrapper ] = reset
? []
: this.onButtonsInitListeners[ wrapper ] || [];
this.onButtonsInitListeners[ wrapper ].push( handler );
}
@ -283,12 +270,11 @@ class Renderer {
if ( this.onButtonsInitListeners[ wrapper ] ) {
for ( const handler of this.onButtonsInitListeners[ wrapper ] ) {
if ( typeof handler === 'function' ) {
handler( {
wrapper,
...this.buttonsOptions[ wrapper ],
} );
if ( typeof handler !== 'function' ) {
continue;
}
handler( { wrapper, ...this.buttonsOptions[ wrapper ] } );
}
}
}
@ -297,10 +283,11 @@ class Renderer {
if ( ! this.buttonsOptions[ wrapper ] ) {
return;
}
try {
this.buttonsOptions[ wrapper ].actions.disable();
} catch ( err ) {
console.log( 'Failed to disable buttons: ' + err );
console.warn( 'Failed to disable buttons: ' + err );
}
}
@ -308,10 +295,11 @@ class Renderer {
if ( ! this.buttonsOptions[ wrapper ] ) {
return;
}
try {
this.buttonsOptions[ wrapper ].actions.enable();
} catch ( err ) {
console.log( 'Failed to enable buttons: ' + err );
console.warn( 'Failed to enable buttons: ' + err );
}
}
}

View file

@ -40,8 +40,9 @@ class WidgetBuilder {
renderButtons( wrapper ) {
wrapper = this.sanitizeWrapper( wrapper );
const entryKey = this.toKey( wrapper );
if ( ! this.buttons.has( this.toKey( wrapper ) ) ) {
if ( ! this.buttons.has( entryKey ) ) {
return;
}
@ -49,11 +50,11 @@ class WidgetBuilder {
return;
}
const entry = this.buttons.get( this.toKey( wrapper ) );
const entry = this.buttons.get( entryKey );
const btn = this.paypal.Buttons( entry.options );
if ( ! btn.isEligible() ) {
this.buttons.delete( this.toKey( wrapper ) );
this.buttons.delete( entryKey );
return;
}
@ -67,7 +68,7 @@ class WidgetBuilder {
}
renderAllButtons() {
for ( const [ wrapper, entry ] of this.buttons ) {
for ( const [ wrapper ] of this.buttons ) {
this.renderButtons( wrapper );
}
}

View file

@ -37,7 +37,7 @@ use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
return array(
'button.client_id' => static function ( ContainerInterface $container ): string {
@ -115,8 +115,8 @@ return array(
}
$no_smart_buttons = ! $settings_status->is_smart_button_enabled_for_location( $context );
$dcc_configuration = $container->get( 'wcgateway.configuration.dcc' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration );
$dcc_configuration = $container->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof CardPaymentsConfiguration );
if ( $no_smart_buttons && ! $dcc_configuration->is_enabled() ) {
// Smart buttons disabled, and also not using advanced card payments.
@ -167,7 +167,8 @@ return array(
$container->get( 'api.endpoint.payment-tokens' ),
$container->get( 'woocommerce.logger.woocommerce' ),
$container->get( 'button.handle-shipping-in-paypal' ),
$container->get( 'button.helper.disabled-funding-sources' )
$container->get( 'button.helper.disabled-funding-sources' ),
$container->get( 'wcgateway.configuration.card-configuration' )
);
},
'button.url' => static function ( ContainerInterface $container ): string {
@ -343,7 +344,8 @@ return array(
'button.helper.disabled-funding-sources' => static function ( ContainerInterface $container ): DisabledFundingSources {
return new DisabledFundingSources(
$container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.all-funding-sources' )
$container->get( 'wcgateway.all-funding-sources' ),
$container->get( 'wcgateway.configuration.card-configuration' )
);
},
'button.is-logged-in' => static function ( ContainerInterface $container ): bool {

View file

@ -54,6 +54,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WC_Shipping_Method;
use WC_Cart;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/**
* Class SmartButton
@ -237,33 +238,41 @@ class SmartButton implements SmartButtonInterface {
*/
private $disabled_funding_sources;
/**
* Provides details about the DCC configuration.
*
* @var CardPaymentsConfiguration
*/
private CardPaymentsConfiguration $dcc_configuration;
/**
* SmartButton constructor.
*
* @param string $module_url The URL to the module.
* @param string $version The assets version.
* @param SessionHandler $session_handler The Session handler.
* @param Settings $settings The Settings.
* @param PayerFactory $payer_factory The Payer factory.
* @param string $client_id The client ID.
* @param RequestData $request_data The Request Data helper.
* @param DccApplies $dcc_applies The DCC applies helper.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param MessagesApply $messages_apply The Messages apply helper.
* @param Environment $environment The environment object.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param SettingsStatus $settings_status The Settings status helper.
* @param CurrencyGetter $currency The getter of the 3-letter currency code of the shop.
* @param array $all_funding_sources All existing funding sources.
* @param bool $basic_checkout_validation_enabled Whether the basic JS validation of the form iss enabled.
* @param bool $early_validation_enabled Whether to execute WC validation of the checkout form.
* @param array $pay_now_contexts The contexts that should have the Pay Now button.
* @param string[] $funding_sources_without_redirect The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
* @param bool $vault_v3_enabled Whether Vault v3 module is enabled.
* @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
* @param LoggerInterface $logger The logger.
* @param bool $should_handle_shipping_in_paypal Whether the shipping should be handled in PayPal.
* @param DisabledFundingSources $disabled_funding_sources List of funding sources to be disabled.
* @param string $module_url The URL to the module.
* @param string $version The assets version.
* @param SessionHandler $session_handler The Session handler.
* @param Settings $settings The Settings.
* @param PayerFactory $payer_factory The Payer factory.
* @param string $client_id The client ID.
* @param RequestData $request_data The Request Data helper.
* @param DccApplies $dcc_applies The DCC applies helper.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param MessagesApply $messages_apply The Messages apply helper.
* @param Environment $environment The environment object.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param SettingsStatus $settings_status The Settings status helper.
* @param CurrencyGetter $currency The getter of the 3-letter currency code of the shop.
* @param array $all_funding_sources All existing funding sources.
* @param bool $basic_checkout_validation_enabled Whether the basic JS validation of the form iss enabled.
* @param bool $early_validation_enabled Whether to execute WC validation of the checkout form.
* @param array $pay_now_contexts The contexts that should have the Pay Now button.
* @param string[] $funding_sources_without_redirect The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
* @param bool $vault_v3_enabled Whether Vault v3 module is enabled.
* @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
* @param LoggerInterface $logger The logger.
* @param bool $should_handle_shipping_in_paypal Whether the shipping should be handled in PayPal.
* @param DisabledFundingSources $disabled_funding_sources List of funding sources to be disabled.
* @param CardPaymentsConfiguration $dcc_configuration The DCC Gateway Configuration.
*/
public function __construct(
string $module_url,
@ -289,9 +298,9 @@ class SmartButton implements SmartButtonInterface {
PaymentTokensEndpoint $payment_tokens_endpoint,
LoggerInterface $logger,
bool $should_handle_shipping_in_paypal,
DisabledFundingSources $disabled_funding_sources
DisabledFundingSources $disabled_funding_sources,
CardPaymentsConfiguration $dcc_configuration
) {
$this->module_url = $module_url;
$this->version = $version;
$this->session_handler = $session_handler;
@ -316,6 +325,7 @@ class SmartButton implements SmartButtonInterface {
$this->payment_tokens_endpoint = $payment_tokens_endpoint;
$this->should_handle_shipping_in_paypal = $should_handle_shipping_in_paypal;
$this->disabled_funding_sources = $disabled_funding_sources;
$this->dcc_configuration = $dcc_configuration;
}
/**
@ -331,76 +341,8 @@ class SmartButton implements SmartButtonInterface {
$this->render_message_wrapper_registrar();
}
if (
$this->settings->has( 'dcc_enabled' )
&& $this->settings->get( 'dcc_enabled' )
) {
add_action(
$this->checkout_dcc_button_renderer_hook(),
array(
$this,
'dcc_renderer',
),
11
);
add_action(
$this->pay_order_renderer_hook(),
array(
$this,
'dcc_renderer',
),
11
);
$subscription_helper = $this->subscription_helper;
add_filter(
'woocommerce_credit_card_form_fields',
function ( array $default_fields, $id ) use ( $subscription_helper ) : array {
if (
is_user_logged_in()
&& $this->settings->has( 'vault_enabled_dcc' )
&& $this->settings->get( 'vault_enabled_dcc' )
&& CreditCardGateway::ID === $id
&& apply_filters( 'woocommerce_paypal_payments_should_render_card_custom_fields', true )
) {
$default_fields['card-vault'] = sprintf(
'<p class="form-row form-row-wide"><label for="ppcp-credit-card-vault"><input class="ppcp-credit-card-vault" type="checkbox" id="ppcp-credit-card-vault" name="vault">%s</label></p>',
esc_html__( 'Save your Credit Card', 'woocommerce-paypal-payments' )
);
if ( $subscription_helper->cart_contains_subscription() || $subscription_helper->order_pay_contains_subscription() ) {
$default_fields['card-vault'] = '';
}
$tokens = $this->payment_token_repository->all_for_user_id( get_current_user_id() );
if ( $tokens && $this->payment_token_repository->tokens_contains_card( $tokens ) ) {
$output = sprintf(
'<p class="form-row form-row-wide"><label>%1$s</label><select id="saved-credit-card" name="saved_credit_card"><option value="">%2$s</option>',
esc_html__( 'Or select a saved Credit Card payment', 'woocommerce-paypal-payments' ),
esc_html__( 'Choose a saved payment', 'woocommerce-paypal-payments' )
);
foreach ( $tokens as $token ) {
if ( isset( $token->source()->card ) ) {
$output .= sprintf(
'<option value="%1$s">%2$s ...%3$s</option>',
$token->id(),
$token->source()->card->brand,
$token->source()->card->last_digits
);
}
}
$output .= '</select></p>';
$default_fields['saved-credit-card'] = $output;
}
}
return $default_fields;
},
10,
2
);
if ( $this->dcc_configuration->is_enabled() ) {
$this->render_dcc_wrapper();
}
if ( $this->is_free_trial_cart() ) {
@ -439,6 +381,74 @@ class SmartButton implements SmartButtonInterface {
return true;
}
/**
* Registers hooks and callbacks that are only relevant for DCC (ACDC) payments.
*
* @return void
*/
private function render_dcc_wrapper(): void {
add_action(
$this->checkout_dcc_button_renderer_hook(),
array( $this, 'dcc_renderer' ),
11
);
add_action(
$this->pay_order_renderer_hook(),
array( $this, 'dcc_renderer' ),
11
);
$subscription_helper = $this->subscription_helper;
add_filter(
'woocommerce_credit_card_form_fields',
function ( array $default_fields, $id ) use ( $subscription_helper ) : array {
if (
CreditCardGateway::ID === $id
&& is_user_logged_in()
&& $this->settings->has( 'vault_enabled_dcc' )
&& $this->settings->get( 'vault_enabled_dcc' )
&& apply_filters( 'woocommerce_paypal_payments_should_render_card_custom_fields', true )
) {
$default_fields['card-vault'] = sprintf(
'<p class="form-row form-row-wide"><label for="ppcp-credit-card-vault"><input class="ppcp-credit-card-vault" type="checkbox" id="ppcp-credit-card-vault" name="vault">%s</label></p>',
esc_html__( 'Save your Credit Card', 'woocommerce-paypal-payments' )
);
if ( $subscription_helper->cart_contains_subscription() || $subscription_helper->order_pay_contains_subscription() ) {
$default_fields['card-vault'] = '';
}
$tokens = $this->payment_token_repository->all_for_user_id( get_current_user_id() );
if ( $tokens && $this->payment_token_repository->tokens_contains_card( $tokens ) ) {
$output = sprintf(
'<p class="form-row form-row-wide"><label>%1$s</label><select id="saved-credit-card" name="saved_credit_card"><option value="">%2$s</option>',
esc_html__( 'Or select a saved Credit Card payment', 'woocommerce-paypal-payments' ),
esc_html__( 'Choose a saved payment', 'woocommerce-paypal-payments' )
);
foreach ( $tokens as $token ) {
if ( isset( $token->source()->card ) ) {
$output .= sprintf(
'<option value="%1$s">%2$s ...%3$s</option>',
$token->id(),
$token->source()->card->brand,
$token->source()->card->last_digits
);
}
}
$output .= '</select></p>';
$default_fields['saved-credit-card'] = $output;
}
}
return $default_fields;
},
10,
2
);
}
/**
* Registers the hooks to render the credit messaging HTML depending on the settings.
*
@ -719,9 +729,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
* Whether DCC fields can be rendered.
*/
public function can_render_dcc() : bool {
return $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' )
&& $this->settings->has( 'client_id' ) && $this->settings->get( 'client_id' )
&& $this->dcc_applies->for_country_currency()
return $this->dcc_configuration->is_acdc_enabled()
&& in_array(
$this->context(),
apply_filters( 'woocommerce_paypal_payments_can_render_dcc_contexts', array( 'checkout', 'pay-now', 'add-payment-method' ) ),
@ -1114,6 +1122,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
*/
public function script_data(): array {
$is_free_trial_cart = $this->is_free_trial_cart();
$is_acdc_enabled = $this->dcc_configuration->is_acdc_enabled();
$url_params = $this->url_params();
@ -1125,7 +1134,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
'client_id' => $this->client_id,
'currency' => $this->currency->get(),
'data_client_id' => array(
'set_attribute' => ( is_checkout() && $this->dcc_is_enabled() ) || $this->can_save_vault_token(),
'set_attribute' => ( is_checkout() && $is_acdc_enabled ) || $this->can_save_vault_token(),
'endpoint' => \WC_AJAX::get_endpoint( DataClientIdEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( DataClientIdEndpoint::nonce() ),
'user' => get_current_user_id(),
@ -1455,20 +1464,6 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
$disabled_funding_sources[] = 'paylater';
}
$disabled_funding_sources = array_filter(
$disabled_funding_sources,
/**
* Make sure paypal is not sent in disable funding.
*
* @param string $funding_source The funding_source.
*
* @psalm-suppress MissingClosureParamType
*/
function( $funding_source ) {
return $funding_source !== 'paypal';
}
);
if ( count( $disabled_funding_sources ) > 0 ) {
$params['disable-funding'] = implode( ',', array_unique( $disabled_funding_sources ) );
}
@ -1574,7 +1569,6 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
* The JS SKD components we need to load.
*
* @return array
* @throws NotFoundException If a setting was not found.
*/
private function components(): array {
$components = array();
@ -1586,9 +1580,12 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
if ( $this->should_load_messages() ) {
$components[] = 'messages';
}
if ( $this->dcc_is_enabled() ) {
// Card payments are only available on a checkout page.
if ( is_checkout() && $this->dcc_configuration->is_bcdc_enabled() ) {
$components[] = 'hosted-fields';
}
/**
* Filter to add further components from the extensions.
*
@ -1606,31 +1603,6 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
);
}
/**
* Whether DCC is enabled or not.
*
* @return bool
*/
private function dcc_is_enabled(): bool {
if ( ! is_checkout() ) {
return false;
}
if ( ! $this->dcc_applies->for_country_currency() ) {
return false;
}
$keys = array(
'client_id',
'client_secret',
'dcc_enabled',
);
foreach ( $keys as $key ) {
if ( ! $this->settings->has( $key ) || ! $this->settings->get( $key ) ) {
return false;
}
}
return true;
}
/**
* Determines the style for a given property in a given context.
*

View file

@ -5,14 +5,14 @@
* @package WooCommerce\PayPalCommerce\Button\Helper
*/
declare(strict_types=1);
declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Button\Helper;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/**
* Class DisabledFundingSources
@ -26,84 +26,187 @@ class DisabledFundingSources {
*
* @var Settings
*/
private $settings;
private Settings $settings;
/**
* All existing funding sources.
*
* @var array
*/
private $all_funding_sources;
private array $all_funding_sources;
/**
* Provides details about the DCC configuration.
*
* @var CardPaymentsConfiguration
*/
private CardPaymentsConfiguration $dcc_configuration;
/**
* DisabledFundingSources constructor.
*
* @param Settings $settings The settings.
* @param array $all_funding_sources All existing funding sources.
* @param Settings $settings The settings.
* @param array $all_funding_sources All existing funding sources.
* @param CardPaymentsConfiguration $dcc_configuration DCC gateway configuration.
*/
public function __construct( Settings $settings, array $all_funding_sources ) {
public function __construct( Settings $settings, array $all_funding_sources, CardPaymentsConfiguration $dcc_configuration ) {
$this->settings = $settings;
$this->all_funding_sources = $all_funding_sources;
$this->dcc_configuration = $dcc_configuration;
}
/**
* Returns the list of funding sources to be disabled.
*
* @param string $context The context.
* @return array|int[]|mixed|string[]
* @throws NotFoundException When the setting is not found.
* @return string[] List of disabled sources
*/
public function sources( string $context ) {
$disable_funding = $this->settings->has( 'disable_funding' )
? $this->settings->get( 'disable_funding' )
: array();
public function sources( string $context ) : array {
$block_contexts = array( 'checkout-block', 'cart-block' );
$flags = array(
'context' => $context,
'is_block_context' => in_array( $context, $block_contexts, true ),
'is_free_trial' => $this->is_free_trial_cart(),
);
$is_dcc_enabled = $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' );
// Free trials have a shorter, special funding-source rule.
if ( $flags['is_free_trial'] ) {
$disable_funding = $this->get_sources_for_free_trial();
if (
! is_checkout()
|| ( $is_dcc_enabled && in_array( $context, array( 'checkout-block', 'cart-block' ), true ) )
) {
$disable_funding[] = 'card';
return $this->sanitize_and_filter_sources( $disable_funding, $flags );
}
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
$is_separate_card_enabled = isset( $available_gateways[ CardButtonGateway::ID ] );
$disable_funding = $this->get_sources_from_settings();
if (
(
is_checkout()
&& ! in_array( $context, array( 'checkout-block', 'cart-block' ), true )
)
&& (
$is_dcc_enabled
|| $is_separate_card_enabled
)
) {
$key = array_search( 'card', $disable_funding, true );
if ( false !== $key ) {
unset( $disable_funding[ $key ] );
}
// Apply rules based on context and payment methods.
$disable_funding = $this->apply_context_rules( $disable_funding );
// Apply special rules for block checkout.
if ( $flags['is_block_context'] ) {
$disable_funding = $this->apply_block_checkout_rules( $disable_funding );
}
if ( in_array( $context, array( 'checkout-block', 'cart-block' ), true ) ) {
$disable_funding = array_merge(
return $this->sanitize_and_filter_sources( $disable_funding, $flags );
}
/**
* Gets disabled funding sources from settings.
*
* @return array
*/
private function get_sources_from_settings() : array {
try {
// Settings field present in the legacy UI.
$disabled_funding = $this->settings->get( 'disable_funding' );
} catch ( NotFoundException $exception ) {
$disabled_funding = array();
}
/**
* Filters the list of disabled funding methods. In the legacy UI, this
* list was accessible via a settings field.
*
* This filter allows merchants to programmatically disable funding sources
* in the new UI.
*/
return (array) apply_filters(
'woocommerce_paypal_payments_disabled_funding',
$disabled_funding
);
}
/**
* Gets disabled funding sources for free trial carts.
*
* Rule: Carts that include a free trial product can ONLY use the
* funding source "card" - all other sources are disabled.
*
* @return array
*/
private function get_sources_for_free_trial() : array {
// Disable all sources.
$disable_funding = array_keys( $this->all_funding_sources );
if ( is_checkout() && $this->dcc_configuration->is_bcdc_enabled() ) {
// If BCDC is used, re-enable card payments.
$disable_funding = array_filter(
$disable_funding,
array_diff(
array_keys( $this->all_funding_sources ),
array( 'venmo', 'paylater', 'paypal', 'card' )
)
static fn( string $funding_source ) => $funding_source !== 'card'
);
}
if ( $this->is_free_trial_cart() ) {
$all_sources = array_keys( $this->all_funding_sources );
if ( $is_dcc_enabled || $is_separate_card_enabled ) {
$all_sources = array_diff( $all_sources, array( 'card' ) );
}
$disable_funding = $all_sources;
return $disable_funding;
}
/**
* Applies rules based on context and payment methods.
*
* @param array $disable_funding The current disabled funding sources.
* @return array
*/
private function apply_context_rules( array $disable_funding ) : array {
if ( ! is_checkout() || $this->dcc_configuration->use_acdc() ) {
// Non-checkout pages, or ACDC capability: Don't load card button.
$disable_funding[] = 'card';
return $disable_funding;
}
return apply_filters( 'woocommerce_paypal_payments_disabled_funding_sources', $disable_funding );
return $disable_funding;
}
/**
* Applies special rules for block checkout.
*
* @param array $disable_funding The current disabled funding sources.
* @return array
*/
private function apply_block_checkout_rules( array $disable_funding ) : array {
/**
* Block checkout only supports the following funding methods:
* - PayPal
* - PayLater
* - Venmo
* - ACDC ("card", conditionally)
*/
$allowed_in_blocks = array( 'venmo', 'paylater', 'paypal', 'card' );
return array_merge(
$disable_funding,
array_diff( array_keys( $this->all_funding_sources ), $allowed_in_blocks )
);
}
/**
* Filters the disabled "funding-sources" list and returns a sanitized array.
*
* @param array $disable_funding The disabled funding sources.
* @param array $flags Decision flags.
* @return string[]
*/
private function sanitize_and_filter_sources( array $disable_funding, array $flags ) : array {
/**
* Filters the final list of disabled funding sources.
*
* @param array $diabled_funding The filter value, funding sources to be disabled.
* @param array $flags Decision flags to provide more context to filters.
*/
$disable_funding = apply_filters(
'woocommerce_paypal_payments_sdk_disabled_funding_hook',
$disable_funding,
array(
'context' => (string) ( $flags['context'] ?? '' ),
'is_block_context' => (bool) ( $flags['is_block_context'] ?? false ),
'is_free_trial' => (bool) ( $flags['is_free_trial'] ?? false ),
)
);
// Make sure "paypal" is never disabled in the funding-sources.
$disable_funding = array_filter(
$disable_funding,
static fn( string $funding_source ) => $funding_source !== 'paypal'
);
return array_unique( $disable_funding );
}
}