Merge pull request #3219 from woocommerce/PCP-4307-card-fields-acdc-does-not-work-in-classic-checkout

Advanced Card Fields do not work in classic checkout (4307)
This commit is contained in:
Emili Castells 2025-03-20 10:44:53 +01:00 committed by GitHub
commit 20241d2cc2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 1001 additions and 693 deletions

View file

@ -21,7 +21,7 @@ class PartnerReferralsData {
* @deprecated Deprecates with the new UI. In this class, the products are
* always explicit, and should not be deducted from the
* DccApplies state at this point.
* Remove this with the legacy UI code.
* Remove this with the #legacy-ui code.
* @var DccApplies
*/
private DccApplies $dcc_applies;
@ -92,7 +92,7 @@ class PartnerReferralsData {
$first_party_features[] = 'BILLING_AGREEMENT';
}
// Backwards compatibility. Keep those features in the legacy UI (null-value).
// Backwards compatibility. Keep those features in the #legacy-ui (null-value).
// Move this into the previous condition, once legacy code is removed.
if ( false !== $use_subscriptions ) {
$first_party_features[] = 'FUTURE_PAYMENT';

View file

@ -35,7 +35,7 @@ return array(
$container->get( 'axo.gateway' ),
fn(): SmartButtonInterface => $container->get( 'button.smart-button' ),
$container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.configuration.dcc' ),
$container->get( 'wcgateway.configuration.card-configuration' ),
$container->get( 'settings.environment' ),
$container->get( 'wcgateway.url' ),
$container->get( 'axo.payment_method_selected_map' ),

View file

@ -14,15 +14,11 @@ use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint;
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;
/**
* Class AxoBlockModule
@ -188,7 +184,7 @@ class AxoBlockModule implements ServiceModule, ExtendingModule, ExecutableModule
return;
}
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' );
$dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
if ( ! $dcc_configuration->use_fastlane() ) {
return;
}

View file

@ -16,7 +16,7 @@ use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/**
* Class AxoBlockPaymentMethod
@ -61,9 +61,9 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
/**
* The DCC gateway settings.
*
* @var DCCGatewayConfiguration
* @var CardPaymentsConfiguration
*/
protected DCCGatewayConfiguration $dcc_configuration;
protected CardPaymentsConfiguration $dcc_configuration;
/**
* The environment object.
@ -101,7 +101,7 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
* @param WC_Payment_Gateway $gateway Credit card gateway.
* @param SmartButtonInterface|callable $smart_button The smart button script loading handler.
* @param Settings $settings The settings.
* @param DCCGatewayConfiguration $dcc_configuration The DCC gateway settings.
* @param CardPaymentsConfiguration $dcc_configuration The DCC gateway settings.
* @param Environment $environment The environment object.
* @param string $wcgateway_module_url The WcGateway module URL.
* @param array $payment_method_selected_map Mapping of payment methods to the PayPal Insights 'payment_method_selected' types.
@ -113,7 +113,7 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
WC_Payment_Gateway $gateway,
$smart_button,
Settings $settings,
DCCGatewayConfiguration $dcc_configuration,
CardPaymentsConfiguration $dcc_configuration,
Environment $environment,
string $wcgateway_module_url,
array $payment_method_selected_map,

View file

@ -17,7 +17,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter;
return array(
@ -39,7 +39,10 @@ return array(
},
'axo.helpers.compatibility-checker' => static function ( ContainerInterface $container ) : CompatibilityChecker {
return new CompatibilityChecker( $container->get( 'axo.fastlane-incompatible-plugin-names' ) );
return new CompatibilityChecker(
$container->get( 'axo.fastlane-incompatible-plugin-names' ),
$container->get( 'wcgateway.configuration.card-configuration' )
);
},
// If AXO is configured and onboarded.
@ -80,7 +83,7 @@ return array(
return new AxoGateway(
$container->get( 'wcgateway.settings.render' ),
$container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.configuration.dcc' ),
$container->get( 'wcgateway.configuration.card-configuration' ),
$container->get( 'wcgateway.url' ),
$container->get( 'session.handler' ),
$container->get( 'wcgateway.order-processor' ),
@ -193,10 +196,7 @@ return array(
$compatibility_checker = $container->get( 'axo.helpers.compatibility-checker' );
assert( $compatibility_checker instanceof CompatibilityChecker );
$settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
return $compatibility_checker->generate_settings_conflict_notice( $settings );
return $compatibility_checker->generate_settings_conflict_notice();
},
'axo.checkout-config-notice' => static function ( ContainerInterface $container ) : string {
@ -221,16 +221,15 @@ return array(
},
'axo.incompatible-plugins-notice.raw' => static function ( ContainerInterface $container ) : string {
$settings_notice_generator = new CompatibilityChecker(
$container->get( 'axo.fastlane-incompatible-plugin-names' )
);
$settings_notice_generator = $container->get( 'axo.helpers.compatibility-checker' );
assert( $settings_notice_generator instanceof CompatibilityChecker );
return $settings_notice_generator->generate_incompatible_plugins_notice( true );
},
'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string {
$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 ( $dcc_configuration->use_fastlane() ) {
$fastlane_settings_url = admin_url(

View file

@ -30,7 +30,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WC_Payment_Gateways;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/**
* Class AxoModule
@ -98,8 +98,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
return $methods;
}
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration );
$dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof CardPaymentsConfiguration );
if ( ! $dcc_configuration->is_enabled() ) {
return $methods;
@ -164,8 +164,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
$listener = $c->get( 'wcgateway.settings.listener' );
assert( $listener instanceof SettingsListener );
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration );
$dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof CardPaymentsConfiguration );
$listener->filter_settings(
$dcc_configuration->use_fastlane(),
@ -247,8 +247,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
add_filter(
'woocommerce_paypal_payments_sdk_components_hook',
function( $components ) use ( $c ) {
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration );
$dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof CardPaymentsConfiguration );
if ( ! $dcc_configuration->use_fastlane() ) {
return $components;
@ -262,8 +262,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
'wp_head',
function () use ( $c ) {
// Add meta tag to allow feature-detection of the site's AXO payment state.
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration );
$dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof CardPaymentsConfiguration );
if ( $dcc_configuration->use_fastlane() ) {
// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
@ -403,8 +403,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
* @return bool
*/
private function should_render_fastlane( ContainerInterface $c ): bool {
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration );
$dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof CardPaymentsConfiguration );
$subscription_helper = $c->get( 'wc-subscriptions.helper' );
assert( $subscription_helper instanceof SubscriptionHelper );

View file

@ -29,7 +29,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\ProcessPaymentTrait;
use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/**
* Class AXOGateway.
@ -56,9 +56,9 @@ class AxoGateway extends WC_Payment_Gateway {
/**
* Gateway configuration object, providing relevant settings.
*
* @var DCCGatewayConfiguration
* @var CardPaymentsConfiguration
*/
protected DCCGatewayConfiguration $dcc_configuration;
protected CardPaymentsConfiguration $dcc_configuration;
/**
* The WcGateway module URL.
@ -133,24 +133,24 @@ class AxoGateway extends WC_Payment_Gateway {
/**
* AXOGateway constructor.
*
* @param SettingsRenderer $settings_renderer The settings renderer.
* @param ContainerInterface $ppcp_settings The settings.
* @param DCCGatewayConfiguration $dcc_configuration The DCC Gateway configuration.
* @param string $wcgateway_module_url The WcGateway module URL.
* @param SessionHandler $session_handler The Session Handler.
* @param OrderProcessor $order_processor The Order processor.
* @param array $card_icons The card icons.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory.
* @param SettingsRenderer $settings_renderer The settings renderer.
* @param ContainerInterface $ppcp_settings The settings.
* @param CardPaymentsConfiguration $dcc_configuration The DCC Gateway configuration.
* @param string $wcgateway_module_url The WcGateway module URL.
* @param SessionHandler $session_handler The Session Handler.
* @param OrderProcessor $order_processor The Order processor.
* @param array $card_icons The card icons.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory.
* @param ShippingPreferenceFactory $shipping_preference_factory The shipping preference factory.
* @param TransactionUrlProvider $transaction_url_provider The transaction url provider.
* @param Environment $environment The environment.
* @param LoggerInterface $logger The logger.
* @param TransactionUrlProvider $transaction_url_provider The transaction url provider.
* @param Environment $environment The environment.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
SettingsRenderer $settings_renderer,
ContainerInterface $ppcp_settings,
DCCGatewayConfiguration $dcc_configuration,
CardPaymentsConfiguration $dcc_configuration,
string $wcgateway_module_url,
SessionHandler $session_handler,
OrderProcessor $order_processor,

View file

@ -6,16 +6,19 @@
* @package WooCommerce\PayPalCommerce\Axo\Helper
*/
declare(strict_types=1);
declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Axo\Helper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/**
* Class CompatibilityChecker
*
* DI service: 'axo.helpers.compatibility-checker'
*/
class CompatibilityChecker {
/**
@ -33,20 +36,24 @@ class CompatibilityChecker {
protected array $checkout_compatibility;
/**
* Stores whether DCC is enabled.
* Provides details about the DCC configuration.
*
* @var bool|null
* @var CardPaymentsConfiguration
*/
protected ?bool $is_dcc_enabled = null;
private CardPaymentsConfiguration $dcc_configuration;
/**
* CompatibilityChecker constructor.
*
* @param string[] $incompatible_plugin_names The list of Fastlane incompatible plugin names.
* @param string[] $incompatible_plugin_names The list of Fastlane incompatible
* plugin names.
* @param CardPaymentsConfiguration $dcc_configuration DCC gateway configuration.
*/
public function __construct( array $incompatible_plugin_names ) {
public function __construct( array $incompatible_plugin_names, CardPaymentsConfiguration $dcc_configuration ) {
$this->incompatible_plugin_names = $incompatible_plugin_names;
$this->checkout_compatibility = array(
$this->dcc_configuration = $dcc_configuration;
$this->checkout_compatibility = array(
'has_elementor_checkout' => null,
'has_classic_checkout' => null,
'has_block_checkout' => null,
@ -58,7 +65,7 @@ class CompatibilityChecker {
*
* @return bool Whether the checkout uses Elementor.
*/
protected function has_elementor_checkout(): bool {
protected function has_elementor_checkout() : bool {
if ( $this->checkout_compatibility['has_elementor_checkout'] === null ) {
$this->checkout_compatibility['has_elementor_checkout'] = CartCheckoutDetector::has_elementor_checkout();
}
@ -71,7 +78,7 @@ class CompatibilityChecker {
*
* @return bool Whether the checkout uses classic checkout.
*/
protected function has_classic_checkout(): bool {
protected function has_classic_checkout() : bool {
if ( $this->checkout_compatibility['has_classic_checkout'] === null ) {
$this->checkout_compatibility['has_classic_checkout'] = CartCheckoutDetector::has_classic_checkout();
}
@ -84,7 +91,7 @@ class CompatibilityChecker {
*
* @return bool Whether the checkout uses block checkout.
*/
protected function has_block_checkout(): bool {
protected function has_block_checkout() : bool {
if ( $this->checkout_compatibility['has_block_checkout'] === null ) {
$this->checkout_compatibility['has_block_checkout'] = CartCheckoutDetector::has_block_checkout();
}
@ -92,29 +99,12 @@ class CompatibilityChecker {
return $this->checkout_compatibility['has_block_checkout'];
}
/**
* Checks if DCC is enabled.
*
* @param Settings $settings The plugin settings container.
* @return bool Whether DCC is enabled.
*/
protected function is_dcc_enabled( Settings $settings ): bool {
if ( $this->is_dcc_enabled === null ) {
try {
$this->is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' );
} catch ( NotFoundException $ignored ) {
$this->is_dcc_enabled = false;
}
}
return $this->is_dcc_enabled;
}
/**
* Generates the full HTML of the notification.
*
* @param string $message HTML of the inner message contents.
* @param bool $is_error Whether the provided message is an error. Affects the notice color.
* @param string $message HTML of the inner message contents.
* @param bool $is_error Whether the provided message is an error. Affects the notice
* color.
* @param bool $raw_message Whether to return raw message without HTML wrappers.
*
* @return string The full HTML code of the notification, or an empty string, or raw message.
@ -136,11 +126,12 @@ class CompatibilityChecker {
}
/**
* Check if there aren't any incompatibilities that would prevent Fastlane from working properly.
* Check if there aren't any incompatibilities that would prevent Fastlane from working
* properly.
*
* @return bool Whether the setup is compatible.
*/
public function is_fastlane_compatible(): bool {
public function is_fastlane_compatible() : bool {
// Check for incompatible plugins.
if ( ! empty( $this->incompatible_plugin_names ) ) {
return false;
@ -165,7 +156,7 @@ class CompatibilityChecker {
* @param bool $raw_message Whether to return raw message without HTML wrappers.
* @return string
*/
public function generate_checkout_notice( bool $raw_message = false ): string {
public function generate_checkout_notice( bool $raw_message = false ) : string {
$notice_content = '';
// Check for checkout incompatibilities.
@ -213,7 +204,7 @@ class CompatibilityChecker {
* @param bool $raw_message Whether to return raw message without HTML wrappers.
* @return string
*/
public function generate_incompatible_plugins_notice( bool $raw_message = false ): string {
public function generate_incompatible_plugins_notice( bool $raw_message = false ) : string {
if ( empty( $this->incompatible_plugin_names ) ) {
return '';
}
@ -235,12 +226,11 @@ class CompatibilityChecker {
/**
* Generates a warning notice with instructions on conflicting plugin-internal settings.
*
* @param Settings $settings The plugin settings container, which is checked for conflicting values.
* @param bool $raw_message Whether to return raw message without HTML wrappers.
* @param bool $raw_message Whether to return raw message without HTML wrappers.
* @return string
*/
public function generate_settings_conflict_notice( Settings $settings, bool $raw_message = false ) : string {
if ( $this->is_dcc_enabled( $settings ) ) {
public function generate_settings_conflict_notice( bool $raw_message = false ) : string {
if ( $this->dcc_configuration->is_enabled() ) {
return '';
}

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 );
}
}

View file

@ -16,7 +16,7 @@ use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/**
* Class CardFieldsModule
@ -46,29 +46,51 @@ class CardFieldsModule implements ServiceModule, ExtendingModule, ExecutableModu
return true;
}
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
add_filter(
'woocommerce_paypal_payments_sdk_components_hook',
function( $components ) use ( $c ) {
if ( ! $c->get( 'wcgateway.configuration.dcc' )->is_enabled() ) {
static function( array $components ) use ( $c ) {
$dcc_config = $c->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_config instanceof CardPaymentsConfiguration );
if ( ! $dcc_config->is_acdc_enabled() ) {
return $components;
}
if ( in_array( 'hosted-fields', $components, true ) ) {
$key = array_search( 'hosted-fields', $components, true );
if ( $key !== false ) {
unset( $components[ $key ] );
}
}
// Enable the new "card-fields" component.
$components[] = 'card-fields';
return $components;
// Ensure the older "hosted-fields" component is not loaded.
return array_filter(
$components,
static fn( string $component ) => $component !== 'hosted-fields'
);
}
);
add_filter(
'woocommerce_paypal_payments_sdk_disabled_funding_hook',
static function ( array $disable_funding, array $flags ) use ( $c ) {
if ( true === $flags['is_block_context'] ) {
return $disable_funding;
}
$dcc_config = $c->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_config instanceof CardPaymentsConfiguration );
if ( ! $dcc_config->is_acdc_enabled() ) {
return $disable_funding;
}
// For ACDC payments we need the funding source "card"!
return array_filter(
$disable_funding,
static fn( string $funding_source ) => $funding_source !== 'card'
);
},
10,
2
);
add_filter(
'woocommerce_credit_card_form_fields',
/**
@ -78,7 +100,7 @@ class CardFieldsModule implements ServiceModule, ExtendingModule, ExecutableModu
* @psalm-suppress MissingClosureParamType
*/
function( $default_fields, $id ) use ( $c ) {
if ( ! $c->get( 'wcgateway.configuration.dcc' )->is_enabled() ) {
if ( ! $c->get( 'wcgateway.configuration.card-configuration' )->is_enabled() ) {
return $default_fields;
}
if ( CreditCardGateway::ID === $id && apply_filters( 'woocommerce_paypal_payments_enable_cardholder_name_field', false ) ) {
@ -113,7 +135,7 @@ class CardFieldsModule implements ServiceModule, ExtendingModule, ExecutableModu
add_filter(
'ppcp_create_order_request_body_data',
function( array $data, string $payment_method ) use ( $c ): array {
if ( ! $c->get( 'wcgateway.configuration.dcc' )->is_enabled() ) {
if ( ! $c->get( 'wcgateway.configuration.card-configuration' )->is_enabled() ) {
return $data;
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing

View file

@ -10,7 +10,6 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Compat;
use Exception;
use Psr\Log\LoggerInterface;
use WC_Cart;
use WC_Order;
use WC_Order_Item_Product;
@ -89,6 +88,8 @@ class CompatModule implements ServiceModule, ExtendingModule, ExecutableModule {
add_action( 'woocommerce_paypal_payments_gateway_migrate', static fn() => delete_transient( 'ppcp_has_ppec_subscriptions' ) );
$this->legacy_ui_card_payment_mapping( $c );
return true;
}
@ -491,4 +492,34 @@ class CompatModule implements ServiceModule, ExtendingModule, ExecutableModule {
2
);
}
/**
* Responsible to keep the credit card payment configuration backwards
* compatible with the legacy UI.
*
* This method can be removed with the #legacy-ui code.
*
* @param ContainerInterface $container DI container instance.
* @return void
*/
protected function legacy_ui_card_payment_mapping( ContainerInterface $container ) : void {
$new_ui = $container->get( 'wcgateway.settings.admin-settings-enabled' );
if ( $new_ui ) {
return;
}
add_filter(
'woocommerce_paypal_payments_is_acdc_active',
static function ( bool $is_acdc ) use ( $container ) : bool {
$settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
try {
return (bool) $settings->get( 'dcc_enabled' );
} catch ( NotFoundException $exception ) {
return $is_acdc;
}
}
);
}
}

View file

@ -19,6 +19,8 @@ use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingSendOnlyNoticeRendere
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Helper\ConnectionState;
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
return array(
'api.paypal-host' => function( ContainerInterface $container ) : string {
@ -48,25 +50,57 @@ return array(
return new State( $settings );
},
/**
* Checks if the onboarding process is completed and the merchant API can be used.
* This service is overwritten by the ppcp-settings module, when it's active.
* Merchant connection details, which includes the connection status
* (onboarding/connected) and connection-aware environment checks.
* This is the preferred solution to check environment and connection state.
*/
'settings.flag.is-connected' => static function ( ContainerInterface $container ) : bool {
'settings.connection-state' => static function ( ContainerInterface $container ) : ConnectionState {
$state = $container->get( 'onboarding.state' );
assert( $state instanceof State );
return $state->current_state() >= State::STATE_ONBOARDED;
},
'settings.flag.is-sandbox' => static function ( ContainerInterface $container ) : bool {
$settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
return $settings->has( 'sandbox_on' ) && $settings->get( 'sandbox_on' );
$is_sandbox = $settings->has( 'sandbox_on' ) && $settings->get( 'sandbox_on' );
$is_connected = $state->current_state() >= State::STATE_ONBOARDED;
$environment = new Environment( $is_sandbox );
return new ConnectionState( $is_connected, $environment );
},
/**
* Checks if the onboarding process is completed and the merchant API can be used.
* This service only resolves the connection status once per request.
*
* @deprecated Use 'settings.connection-state' instead.
*/
'settings.flag.is-connected' => static function ( ContainerInterface $container ) : bool {
$state = $container->get( 'settings.connection-state' );
assert( $state instanceof ConnectionState );
return $state->is_connected();
},
/**
* Determines whether the merchant is connected to a sandbox account.
* This service only resolves the sandbox flag once per request.
*
* @deprecated Use 'settings.connection-state' instead.
*/
'settings.flag.is-sandbox' => static function ( ContainerInterface $container ) : bool {
$state = $container->get( 'settings.connection-state' );
assert( $state instanceof ConnectionState );
return $state->is_sandbox();
},
/**
* Returns details about the connected environment (production/sandbox).
*
* @deprecated Directly use 'settings.connection-state' instead of this.
*/
'settings.environment' => function ( ContainerInterface $container ) : Environment {
return new Environment(
$container->get( 'settings.flag.is-sandbox' )
);
$state = $container->get( 'settings.connection-state' );
assert( $state instanceof ConnectionState );
return $state->get_environment();
},
'onboarding.assets' => function( ContainerInterface $container ) : OnboardingAssets {

View file

@ -153,34 +153,36 @@ return array(
return new ConnectionState( $is_connected, $environment );
},
/**
* Returns details about the connected environment (production/sandbox).
*
* @deprecated Directly use 'settings.connection-state' instead of this.
*/
'settings.environment' => static function ( ContainerInterface $container ) : Environment {
// We should remove this service in favor of directly using `settings.connection-state`.
$state = $container->get( 'settings.connection-state' );
assert( $state instanceof ConnectionState );
return $state->get_environment();
},
/**
* Checks if valid merchant connection details are stored in the DB.
* Checks if the onboarding process is completed and the merchant API can be used.
* This service only resolves the connection status once per request.
*
* @deprecated Use 'settings.connection-state' instead.
*/
'settings.flag.is-connected' => static function ( ContainerInterface $container ) : bool {
/*
* This service only resolves the connection status once per request.
* We should remove this service in favor of directly using `settings.connection-state`.
*/
$state = $container->get( 'settings.connection-state' );
assert( $state instanceof ConnectionState );
return $state->is_connected();
},
/**
* Checks if the merchant is connected to a sandbox environment.
* Determines whether the merchant is connected to a sandbox account.
* This service only resolves the sandbox flag once per request.
*
* @deprecated Use 'settings.connection-state' instead.
*/
'settings.flag.is-sandbox' => static function ( ContainerInterface $container ) : bool {
/*
* This service only resolves the sandbox flag once per request.
* We should remove this service in favor of directly using `settings.connection-state`.
*/
$state = $container->get( 'settings.connection-state' );
assert( $state instanceof ConnectionState );

View file

@ -57,7 +57,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
* Returns whether the old settings UI should be loaded.
*/
public static function should_use_the_old_ui() : bool {
// New merchants should never see the legacy UI.
// New merchants should never see the #legacy-ui.
$show_new_ux = '1' === get_option( 'woocommerce-ppcp-is-new-merchant' );
if ( $show_new_ux ) {
@ -394,7 +394,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
unset( $payment_methods[ OXXO::ID ] );
}
// Unset Pay Unon Invoice if merchant country is not Germany.
// Unset Pay Upon Invoice if merchant country is not Germany.
if ( 'DE' !== $merchant_country ) {
unset( $payment_methods[ PayUponInvoiceGateway::ID ] );
}
@ -577,35 +577,6 @@ class SettingsModule implements ServiceModule, ExecutableModule {
);
}
/**
* Unsets the BCDC black button if merchant is eligible for ACDC.
*/
add_filter(
'woocommerce_paypal_payments_disabled_funding_sources',
/**
* Unsets the BCDC black button if merchant is eligible for ACDC.
*
* @param int[]|string[]|mixed $disable_funding The disabled funding sources.
* @return int[]|string[]|mixed The disabled funding sources.
*
* @psalm-suppress MissingClosureParamType
*/
static function ( $disable_funding ) use ( $container ) {
if ( ! is_array( $disable_funding ) || in_array( 'card', $disable_funding, true ) ) {
return $disable_funding;
}
$dcc_product_status = $container->get( 'wcgateway.helper.dcc-product-status' );
assert( $dcc_product_status instanceof DCCProductStatus );
if ( $dcc_product_status->is_active() ) {
$disable_funding[] = 'card';
}
return $disable_funding;
}
);
// Enable Fastlane after onboarding if the store is compatible.
add_action(
'woocommerce_paypal_payments_apply_default_configuration',

View file

@ -83,7 +83,8 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use WooCommerce\PayPalCommerce\Axo\Helper\PropertiesDictionary;
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
use WooCommerce\PayPalCommerce\WcGateway\Helper\ConnectionState;
return array(
'wcgateway.paypal-gateway' => static function ( ContainerInterface $container ): PayPalGateway {
@ -117,7 +118,7 @@ return array(
$container->get( 'wcgateway.settings.render' ),
$container->get( 'wcgateway.order-processor' ),
$container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.configuration.dcc' ),
$container->get( 'wcgateway.configuration.card-configuration' ),
$container->get( 'wcgateway.credit-card-icons' ),
$container->get( 'wcgateway.url' ),
$container->get( 'session.handler' ),
@ -333,7 +334,8 @@ return array(
$container->get( 'settings.flag.is-connected' ),
$container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.is-wc-payments-page' ),
$container->get( 'wcgateway.is-ppcp-settings-page' )
$container->get( 'wcgateway.is-ppcp-settings-page' ),
$container->get( 'wcgateway.configuration.card-configuration' )
);
},
'wcgateway.notice.card-button-without-paypal' => static function ( ContainerInterface $container ): GatewayWithoutPayPalAdminNotice {
@ -343,6 +345,7 @@ return array(
$container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.is-wc-payments-page' ),
$container->get( 'wcgateway.is-ppcp-settings-page' ),
$container->get( 'wcgateway.configuration.card-configuration' ),
$container->get( 'wcgateway.settings.status' )
);
},
@ -461,8 +464,8 @@ return array(
assert( $settings instanceof Settings );
$axo_available = $container->has( 'axo.available' ) && $container->get( 'axo.available' );
$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 ( $axo_available && $dcc_configuration->use_fastlane() ) {
return '';
@ -684,8 +687,8 @@ return array(
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
assert( $subscription_helper instanceof SubscriptionHelper );
$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 );
$fields = array(
'checkout_settings_heading' => array(
@ -1359,11 +1362,12 @@ return array(
return new TransactionUrlProvider( $sandbox_url_base, $live_url_base );
},
'wcgateway.configuration.dcc' => static function ( ContainerInterface $container ) : DCCGatewayConfiguration {
$settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
return new DCCGatewayConfiguration( $settings );
'wcgateway.configuration.card-configuration' => static function ( ContainerInterface $container ) : CardPaymentsConfiguration {
return new CardPaymentsConfiguration(
$container->get( 'settings.connection-state' ),
$container->get( 'wcgateway.settings' ),
$container->get( 'api.helpers.dccapplies' )
);
},
'wcgateway.helper.dcc-product-status' => static function ( ContainerInterface $container ) : DCCProductStatus {

View file

@ -34,7 +34,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/**
* Class CreditCardGateway
@ -76,9 +76,9 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
/**
* The DCC Gateway Configuration.
*
* @var DCCGatewayConfiguration
* @var CardPaymentsConfiguration
*/
protected DCCGatewayConfiguration $dcc_configuration;
protected CardPaymentsConfiguration $dcc_configuration;
/**
* The vaulted credit card handler.
@ -188,31 +188,31 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
/**
* CreditCardGateway constructor.
*
* @param SettingsRenderer $settings_renderer The Settings Renderer.
* @param OrderProcessor $order_processor The Order processor.
* @param ContainerInterface $config The settings.
* @param DCCGatewayConfiguration $dcc_configuration The DCC Gateway Configuration.
* @param array $card_icons The card icons.
* @param string $module_url The URL to the module.
* @param SessionHandler $session_handler The Session Handler.
* @param RefundProcessor $refund_processor The refund processor.
* @param TransactionUrlProvider $transaction_url_provider Service able to provide view transaction url base.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param PaymentsEndpoint $payments_endpoint The payments endpoint.
* @param VaultedCreditCardHandler $vaulted_credit_card_handler The vaulted credit card handler.
* @param Environment $environment The environment.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param CaptureCardPayment $capture_card_payment Capture card payment.
* @param string $prefix The prefix.
* @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
* @param WooCommercePaymentTokens $wc_payment_tokens WooCommerce payment tokens factory.
* @param LoggerInterface $logger The logger.
* @param SettingsRenderer $settings_renderer The Settings Renderer.
* @param OrderProcessor $order_processor The Order processor.
* @param ContainerInterface $config The settings.
* @param CardPaymentsConfiguration $dcc_configuration The DCC Gateway Configuration.
* @param array $card_icons The card icons.
* @param string $module_url The URL to the module.
* @param SessionHandler $session_handler The Session Handler.
* @param RefundProcessor $refund_processor The refund processor.
* @param TransactionUrlProvider $transaction_url_provider Service able to provide view transaction url base.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param PaymentsEndpoint $payments_endpoint The payments endpoint.
* @param VaultedCreditCardHandler $vaulted_credit_card_handler The vaulted credit card handler.
* @param Environment $environment The environment.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param CaptureCardPayment $capture_card_payment Capture card payment.
* @param string $prefix The prefix.
* @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
* @param WooCommercePaymentTokens $wc_payment_tokens WooCommerce payment tokens factory.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
SettingsRenderer $settings_renderer,
OrderProcessor $order_processor,
ContainerInterface $config,
DCCGatewayConfiguration $dcc_configuration,
CardPaymentsConfiguration $dcc_configuration,
array $card_icons,
string $module_url,
SessionHandler $session_handler,

View file

@ -137,10 +137,6 @@ class PayUponInvoice {
*/
public function init(): void {
if ( $this->pui_helper->is_pui_gateway_enabled() ) {
/*
* TODO new-ux: Check if we still support this setting, or if it's always enabled.
* If fraudnet is not configurable in new UI, we can ignore this.
*/
$this->settings->set( 'fraudnet_enabled', true );
$this->settings->persist();
}

View file

@ -0,0 +1,386 @@
<?php
/**
* Encapsulates all configuration details for "Credit & Debit Card" gateway.
*
* The DCC gateway is also referred to as ACDC or "Advanced Card Processing".
* When active, we load the newer "card-fields" SDK component, instead of the
* old "hosted-fields" component.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Helper
*/
declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\Axo\Helper\PropertiesDictionary;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
/**
* A configuration proxy service that provides details about credit card gateway
* configuration.
*
* Terminology:
* - DCC or ACDC refers to the new "Advanced Card Processing" integration.
* - BCDC is the older "Credit and Debit Cards" integration.
* - AXO is Fastlane, which is an improved UI for ACDC.
*
* Technical implementation via the JS SDK:
*
* a. Funding source
* - When the funding-source "card" controls the black "Debit or Credit Cards" button:
* It's hidden when the funding-source is disabled, and displayed otherwise.
* See implementation in class `DisabledFundingSources`.
*
* b. Components
* - "card-fields" is used by ACDC and AXO.
* - The component "hosted-fields" is mentioned in the code, but unclear where/when it's used.
*
* DI service: 'wcgateway.configuration.card-configuration'
*/
class CardPaymentsConfiguration {
/**
* The connection state.
*
* @var ConnectionState
*/
private ConnectionState $connection_state;
/**
* The plugin settings instance.
*
* @var Settings
*/
private Settings $settings;
/**
* Helper to determine availability of DCC features.
*
* @var DccApplies
*/
private DccApplies $dcc_applies;
/**
* This classes lazily resolves settings on first access. This flag indicates
* whether the setting values were resolved, or still need to be evaluated.
*
* @var bool
*/
public bool $is_resolved = false;
/**
* Indicates whether the merchant uses ACDC (true) or BCDC (false).
*
* @var bool
*/
private bool $use_acdc = false;
/**
* Whether the Credit Card gateway is enabled.
*
* @var bool
*/
private bool $is_enabled = false;
/**
* Whether to use the Fastlane UI.
*
* @var bool
*/
private bool $use_fastlane = false;
/**
* Gateway title.
*
* @var string
*/
private string $gateway_title = '';
/**
* Gateway description.
*
* @var string
*/
private string $gateway_description = '';
/**
* Whether to display the cardholder's name on the payment form.
*
* @var string
*/
private string $show_name_on_card = 'no';
/**
* Whether the Fastlane watermark should be hidden on the front-end.
*
* @var bool
*/
private bool $hide_fastlane_watermark = false;
/**
* Initializes the gateway details based on the provided Settings instance.
*
* @param ConnectionState $connection_state Connection state instance.
* @param Settings $settings Plugin settings instance.
* @param DccApplies $dcc_applies DCC eligibility helper.
*/
public function __construct( ConnectionState $connection_state, Settings $settings, DccApplies $dcc_applies ) {
$this->connection_state = $connection_state;
$this->settings = $settings;
$this->dcc_applies = $dcc_applies;
$this->is_resolved = false;
}
/**
* Marks the current settings as "outdated". The next time a setting is accessed
* it will be resolved using the current settings.
*
* @return void
*/
public function refresh() : void {
$this->is_resolved = false;
}
/**
* Ensures the internally cached flags correctly reflect the current settings.
*
* @return void
*/
private function ensure_resolved_values() : void {
if ( $this->is_resolved ) {
return;
}
$this->resolve();
$this->is_resolved = true;
}
/**
* Refreshes the internal gateway configuration based on the current settings.
*/
private function resolve() : void {
$show_on_card_options = array_keys( PropertiesDictionary::cardholder_name_options() );
$show_on_card_value = null;
// Reset all flags, disable everything.
$this->use_acdc = false;
$this->is_enabled = false;
$this->use_fastlane = false;
$this->gateway_title = '';
$this->gateway_description = '';
$this->show_name_on_card = $show_on_card_options[0]; // 'no'.
$this->hide_fastlane_watermark = false;
/**
* Allow modules or other plugins to disable card payments for this shop.
*/
$disable_card_payments = apply_filters(
'woocommerce_paypal_payments_card_payments_disabled',
false
);
if ( $disable_card_payments ) {
return;
}
if ( ! $this->connection_state->is_connected() ) {
return;
}
try {
$is_paypal_enabled = filter_var( $this->settings->get( 'enabled' ), FILTER_VALIDATE_BOOLEAN );
// When the core payment logic of the plugin is disabled, we cannot handle card payments.
if ( ! $is_paypal_enabled ) {
return;
}
$is_dcc_enabled = filter_var( $this->settings->get( 'dcc_enabled' ), FILTER_VALIDATE_BOOLEAN );
$this->use_fastlane = filter_var( $this->settings->get( 'axo_enabled' ), FILTER_VALIDATE_BOOLEAN );
if ( $this->settings->has( 'dcc_gateway_title' ) ) {
$this->gateway_title = $this->settings->get( 'dcc_gateway_title' );
}
if ( $this->settings->has( 'dcc_gateway_description' ) ) {
$this->gateway_description = $this->settings->get( 'dcc_gateway_description' );
}
if ( $this->settings->has( 'dcc_name_on_card' ) ) {
$show_on_card_value = $this->settings->get( 'dcc_name_on_card' );
} elseif ( $this->settings->has( 'axo_name_on_card' ) ) {
// Legacy. The AXO gateway setting was replaced by the DCC setting.
// Remove this condition with the #legacy-ui.
$show_on_card_value = $this->settings->get( 'axo_name_on_card' );
}
if ( in_array( $show_on_card_value, $show_on_card_options, true ) ) {
$this->show_name_on_card = $show_on_card_value;
}
} catch ( NotFoundException $exception ) {
// A setting is missing in the DB, disable card payments.
return;
}
/**
* Filters the "Card Payments Enabled" status. This allows other modules
* to override the flag.
*/
$this->is_enabled = (bool) apply_filters(
'woocommerce_paypal_payments_is_card_payment_enabled',
$is_dcc_enabled
);
/**
* Filters the "ACDC" state. When a filter callback sets this to false
* the plugin assumes to be in BCDC mode.
*/
$this->use_acdc = (bool) apply_filters(
'woocommerce_paypal_payments_is_acdc_active',
$this->dcc_applies->for_country_currency()
);
/**
* Changing this to true (and hiding the watermark) has potential legal
* consequences, and therefore is generally discouraged.
*
* @since 2024-09-26 - replace the UI checkbox "axo_privacy" with a filter.
*/
$this->hide_fastlane_watermark = add_filter(
'woocommerce_paypal_payments_fastlane_watermark_enabled',
'__return_false'
);
}
/**
* Indicated whether the merchant is in ACDC mode.
*
* @return bool
*/
public function use_acdc() : bool {
$this->ensure_resolved_values();
return $this->use_acdc;
}
/**
* Whether card payments are enabled.
*
* Requires PayPal features to be enabled.
*
* @internal Use "is_acdc_enabled()" or "is_bcdc_enabled()" instead.
* @return bool
*/
public function is_enabled() : bool {
$this->ensure_resolved_values();
return $this->is_enabled;
}
/**
* True, if the card payments are enabled and the merchant is in ACDC mode.
* This also unlocks card payments on block pages.
*
* If this returns false, the following payment methods are unavailable:
* - Advanced Card Processing
* - Fastlane
* - Google Pay
* - Apple Pay
*
* @return bool
*/
public function is_acdc_enabled() : bool {
return $this->is_enabled() && $this->use_acdc();
}
/**
* True, if card payments are enabled and the merchant is in BCDC mode.
*
* The BCDC integration is not supported by block checkout:
* When this returns true, disable card payments on block pages.
*
* @return bool
*/
public function is_bcdc_enabled() : bool {
return $this->is_enabled() && ! $this->use_acdc();
}
/**
* Whether to prefer Fastlane instead of the default Credit Card UI, if
* available in the shop's region.
*
* Requires PayPal features and the "Advanced Card Payments" gateway to be enabled.
*
* @return bool
*/
public function use_fastlane() : bool {
return $this->is_acdc_enabled() && $this->use_fastlane;
}
/**
* User facing title of the gateway.
*
* @param string $fallback Fallback title if the gateway title is not set.
*
* @return string Display title of the gateway.
*/
public function gateway_title( string $fallback = '' ) : string {
$this->ensure_resolved_values();
if ( $this->gateway_title ) {
return $this->gateway_title;
}
return $fallback ?: __( 'Advanced Card Processing', 'woocommerce-paypal-payments' );
}
/**
* Descriptive text to display on the frontend.
*
* @param string $fallback Fallback description if the gateway description is not set.
*
* @return string Display description of the gateway.
*/
public function gateway_description( string $fallback = '' ) : string {
$this->ensure_resolved_values();
if ( $this->gateway_description ) {
return $this->gateway_description;
}
return $fallback ?: __(
'Accept debit and credit cards, and local payment methods with PayPals latest solution.',
'woocommerce-paypal-payments'
);
}
/**
* Whether to show a field for the cardholder's name in the payment form.
*
* Note, that this getter returns a string (not a boolean) because the
* setting is integrated as a select-list, not a toggle or checkbox.
*
* @return string ['yes'|'no']
*/
public function show_name_on_card() : string {
$this->ensure_resolved_values();
return $this->show_name_on_card;
}
/**
* Whether to display the watermark (text branding) for the Fastlane payment
* method in the front end.
*
* Note: This setting is planned but not implemented yet.
*
* @retun bool True means, the default watermark is displayed to customers.
*/
public function show_fastlane_watermark() : bool {
$this->ensure_resolved_values();
return ! $this->hide_fastlane_watermark;
}
}

View file

@ -14,6 +14,8 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
*
* Manages the merchants' connection status details and provides inspection
* methods to describe the current state.
*
* DI service: 'settings.connection-state'
*/
class ConnectionState {
/**

View file

@ -1,215 +0,0 @@
<?php
/**
* Encapsulates all configuration details for "Credit & Debit Card" gateway.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Helper
*/
declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\Axo\Helper\PropertiesDictionary;
/**
* A simple DTO that provides access to the DCC/AXO gateway settings.
*
* This class should not implement business logic, but only provide a convenient
* way to access gateway settings by wrapping the Settings instance.
*/
class DCCGatewayConfiguration {
/**
* The plugin settings instance.
*
* @var Settings
*/
private Settings $settings;
/**
* Whether the Credit Card gateway is enabled.
*
* @var bool
*/
private bool $is_enabled = false;
/**
* Whether to use the Fastlane UI.
*
* @var bool
*/
private bool $use_fastlane = false;
/**
* Gateway title.
*
* @var string
*/
private string $gateway_title = '';
/**
* Gateway description.
*
* @var string
*/
private string $gateway_description = '';
/**
* Whether to display the cardholder's name on the payment form.
*
* @var string
*/
private string $show_name_on_card = 'no';
/**
* Whether the Fastlane watermark should be hidden on the front-end.
*
* @var bool
*/
private bool $hide_fastlane_watermark = false;
/**
* Initializes the gateway details based on the provided Settings instance.
*
* @throws NotFoundException If an expected gateway setting is not found.
*
* @param Settings $settings Plugin settings instance.
*/
public function __construct( Settings $settings ) {
$this->settings = $settings;
$this->refresh();
}
/**
* Refreshes the gateway configuration based on the current settings.
*
* This method should be used sparingly, usually only on the settings page
* when changes in gateway settings must be reflected immediately.
*
* @throws NotFoundException If an expected gateway setting is not found.
*/
public function refresh() : void {
$is_paypal_enabled = $this->settings->has( 'enabled' )
&& filter_var( $this->settings->get( 'enabled' ), FILTER_VALIDATE_BOOLEAN );
$is_dcc_enabled = $this->settings->has( 'dcc_enabled' )
&& filter_var( $this->settings->get( 'dcc_enabled' ), FILTER_VALIDATE_BOOLEAN );
$is_axo_enabled = $this->settings->has( 'axo_enabled' )
&& filter_var( $this->settings->get( 'axo_enabled' ), FILTER_VALIDATE_BOOLEAN );
$this->is_enabled = $is_paypal_enabled && $is_dcc_enabled;
$this->use_fastlane = $this->is_enabled && $is_axo_enabled;
$this->gateway_title = $this->settings->has( 'dcc_gateway_title' ) ?
$this->settings->get( 'dcc_gateway_title' ) : '';
$this->gateway_description = $this->settings->has( 'dcc_gateway_description' ) ?
$this->settings->get( 'dcc_gateway_description' ) : '';
$show_on_card = '';
if ( $this->settings->has( 'dcc_name_on_card' ) ) {
$show_on_card = $this->settings->get( 'dcc_name_on_card' );
} elseif ( $this->settings->has( 'axo_name_on_card' ) ) {
// Legacy. The AXO gateway setting was replaced by the DCC setting.
$show_on_card = $this->settings->get( 'axo_name_on_card' );
}
$valid_options = array_keys( PropertiesDictionary::cardholder_name_options() );
$this->show_name_on_card = in_array( $show_on_card, $valid_options, true )
? $show_on_card
: $valid_options[0];
/**
* Moved from setting "axo_privacy" to a hook-only filter:
* Changing this to true (and hiding the watermark) has potential legal
* consequences, and therefore is generally discouraged.
*/
$this->hide_fastlane_watermark = add_filter(
'woocommerce_paypal_payments_fastlane_watermark_enabled',
'__return_false'
);
}
/**
* Whether the Credit Card gateway is enabled.
*
* Requires PayPal features to be enabled.
*
* @return bool
* @todo Some classes still directly access `$settings->get('dcc_enabled')`
*/
public function is_enabled() : bool {
return $this->is_enabled;
}
/**
* Whether to prefer Fastlane instead of the default Credit Card UI, if
* available in the shop's region.
*
* Requires PayPal features and the Credit Card gateway to be enabled.
*
* @return bool
*/
public function use_fastlane() : bool {
return $this->use_fastlane;
}
/**
* User facing title of the gateway.
*
* @param string $fallback Fallback title if the gateway title is not set.
*
* @return string Display title of the gateway.
*/
public function gateway_title( string $fallback = '' ) : string {
if ( $this->gateway_title ) {
return $this->gateway_title;
}
return $fallback ?: __( 'Advanced Card Processing', 'woocommerce-paypal-payments' );
}
/**
* Descriptive text to display on the frontend.
*
* @param string $fallback Fallback description if the gateway description is not set.
*
* @return string Display description of the gateway.
*/
public function gateway_description( string $fallback = '' ) : string {
if ( $this->gateway_description ) {
return $this->gateway_description;
}
return $fallback ?: __(
'Accept debit and credit cards, and local payment methods with PayPals latest solution.',
'woocommerce-paypal-payments'
);
}
/**
* Whether to show a field for the cardholder's name in the payment form.
*
* Note, that this getter returns a string (not a boolean) because the
* setting is integrated as a select-list, not a toggle or checkbox.
*
* @return string ['yes'|'no']
*/
public function show_name_on_card() : string {
return $this->show_name_on_card;
}
/**
* Whether to display the watermark (text branding) for the Fastlane payment
* method in the front end.
*
* Note: This setting is planned but not implemented yet.
*
* @retun bool True means, the default watermark is displayed to customers.
*/
public function show_fastlane_watermark() : bool {
return ! $this->hide_fastlane_watermark;
}
}

View file

@ -13,6 +13,7 @@ use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/**
* Creates the admin message about the gateway being enabled without the PayPal gateway.
@ -65,15 +66,23 @@ class GatewayWithoutPayPalAdminNotice {
*/
protected $settings_status;
/**
* Provides details about the DCC configuration.
*
* @var CardPaymentsConfiguration
*/
private CardPaymentsConfiguration $dcc_configuration;
/**
* ConnectAdminNotice constructor.
*
* @param string $id The gateway ID.
* @param bool $is_connected Whether onboading was completed.
* @param ContainerInterface $settings The settings.
* @param bool $is_payments_page Whether the current page is the WC payment page.
* @param bool $is_ppcp_settings_page Whether the current page is the PPCP settings page.
* @param SettingsStatus|null $settings_status The Settings status helper.
* @param string $id The gateway ID.
* @param bool $is_connected Whether onboarding was completed.
* @param ContainerInterface $settings The settings.
* @param bool $is_payments_page Whether the current page is the WC payment page.
* @param bool $is_ppcp_settings_page Whether the current page is the PPCP settings page.
* @param CardPaymentsConfiguration $dcc_configuration DCC gateway configuration.
* @param SettingsStatus|null $settings_status The Settings status helper.
*/
public function __construct(
string $id,
@ -81,6 +90,7 @@ class GatewayWithoutPayPalAdminNotice {
ContainerInterface $settings,
bool $is_payments_page,
bool $is_ppcp_settings_page,
CardPaymentsConfiguration $dcc_configuration,
?SettingsStatus $settings_status = null
) {
$this->id = $id;
@ -88,6 +98,7 @@ class GatewayWithoutPayPalAdminNotice {
$this->settings = $settings;
$this->is_payments_page = $is_payments_page;
$this->is_ppcp_settings_page = $is_ppcp_settings_page;
$this->dcc_configuration = $dcc_configuration;
$this->settings_status = $settings_status;
}
@ -181,7 +192,7 @@ class GatewayWithoutPayPalAdminNotice {
return self::NOTICE_DISABLED_LOCATION;
}
$is_dcc_enabled = $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) ?? false;
$is_dcc_enabled = $this->dcc_configuration->is_enabled();
$is_card_button_allowed = $this->settings->has( 'allow_card_button_gateway' ) && $this->settings->get( 'allow_card_button_gateway' );
if ( $is_dcc_enabled && $is_card_button_allowed ) {

View file

@ -263,7 +263,7 @@ class SettingsListener {
// phpcs:enable WordPress.Security.NonceVerification.Missing
// phpcs:enable WordPress.Security.NonceVerification.Recommended
// This method is only used for legacy UI, `settings->set` is valid here.
// This method is only used for #legacy-ui, `settings->set` is valid here.
$this->settings->set( 'merchant_id', $merchant_id );
$this->settings->set( 'merchant_email', $merchant_email );
@ -367,7 +367,7 @@ class SettingsListener {
return;
}
// This method is only used for legacy UI, `settings->set` is valid here.
// This method is only used for #legacy-ui, `settings->set` is valid here.
try {
$token = $this->bearer->bearer();
@ -777,7 +777,7 @@ class SettingsListener {
return;
}
// This method is only used for legacy UI, `settings->set` is valid here.
// This method is only used for #legacy-ui, `settings->set` is valid here.
$existing_setting_value = $this->settings->has( $setting_slug ) ? $this->settings->get( $setting_slug ) : null;

View file

@ -58,7 +58,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Registrar\TaskRegistrarInterface;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\LocalApmProductStatus;
/**
@ -195,8 +195,8 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration );
$dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof CardPaymentsConfiguration );
$assets = new SettingsPageAssets(
$c->get( 'wcgateway.url' ),
@ -619,8 +619,8 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
return $methods;
}
$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 );
$standard_card_button = get_option( 'woocommerce_ppcp-card-button-gateway_settings' );

View file

@ -8,17 +8,20 @@ use WC_Payment_Gateways;
use WooCommerce;
use WooCommerce\PayPalCommerce\TestCase;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
use function Brain\Monkey\Functions\when;
class DisabledFundingSourcesTest extends TestCase
{
private $settings;
private $dcc_configuration;
public function setUp(): void
{
parent::setUp();
$this->settings = Mockery::mock(Settings::class);
$this->dcc_configuration = Mockery::mock(CardPaymentsConfiguration::class);
}
/**
@ -27,7 +30,9 @@ class DisabledFundingSourcesTest extends TestCase
*/
public function test_is_checkout_true_add_card_when_checkout_block_context()
{
$sut = new DisabledFundingSources($this->settings, []);
$this->dcc_configuration->shouldReceive('is_enabled')->andReturn(true);
$this->dcc_configuration->shouldReceive('use_acdc')->andReturn(true);
$sut = new DisabledFundingSources($this->settings, [], $this->dcc_configuration);
$this->setExpectations();
$this->setWcPaymentGateways();
@ -43,7 +48,9 @@ class DisabledFundingSourcesTest extends TestCase
*/
public function test_is_checkout_false_add_card_when_checkout_context()
{
$sut = new DisabledFundingSources($this->settings, []);
$this->dcc_configuration->shouldReceive('is_enabled')->andReturn(true);
$this->dcc_configuration->shouldReceive('use_acdc')->andReturn(true);
$sut = new DisabledFundingSources($this->settings, [], $this->dcc_configuration);
$this->setExpectations();
$this->setWcPaymentGateways();
@ -55,11 +62,17 @@ class DisabledFundingSourcesTest extends TestCase
public function test_is_checkout_true_add_allowed_sources_when_checkout_block_context()
{
$sut = new DisabledFundingSources($this->settings, [
'card' => 'Credit or debit cards',
'paypal' => 'PayPal',
'foo' => 'Bar',
]);
$this->dcc_configuration->shouldReceive('is_enabled')->andReturn(true);
$this->dcc_configuration->shouldReceive('use_acdc')->andReturn(true);
$sut = new DisabledFundingSources(
$this->settings,
[
'card' => 'Credit or debit cards',
'paypal' => 'PayPal',
'foo' => 'Bar',
],
$this->dcc_configuration
);
$this->setExpectations();
$this->setWcPaymentGateways();

View file

@ -19,7 +19,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Endpoint\CaptureCardPayment;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use function Brain\Monkey\Functions\when;
@ -53,7 +53,7 @@ class CreditCardGatewayTest extends TestCase
$this->settingsRenderer = Mockery::mock(SettingsRenderer::class);
$this->orderProcessor = Mockery::mock(OrderProcessor::class);
$this->config = Mockery::mock(ContainerInterface::class);
$this->dcc_configuration = Mockery::mock(DCCGatewayConfiguration::class);
$this->dcc_configuration = Mockery::mock(CardPaymentsConfiguration::class);
$this->creditCardIcons = [];
$this->moduleUrl = '';
$this->sessionHandler = Mockery::mock(SessionHandler::class);

View file

@ -235,7 +235,7 @@ define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' );
* Set new merchant flag on plugin install.
*
* When installing the plugin for the first time, we direct the user to
* the new UI without a data migration, and fully hide the legacy UI.
* the new UI without a data migration, and fully hide the #legacy-ui.
*
* @param string|false $version String with previous installed plugin version.
* Boolean false on first installation on a new site.