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

View file

@ -35,7 +35,7 @@ return array(
$container->get( 'axo.gateway' ), $container->get( 'axo.gateway' ),
fn(): SmartButtonInterface => $container->get( 'button.smart-button' ), fn(): SmartButtonInterface => $container->get( 'button.smart-button' ),
$container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.configuration.dcc' ), $container->get( 'wcgateway.configuration.card-configuration' ),
$container->get( 'settings.environment' ), $container->get( 'settings.environment' ),
$container->get( 'wcgateway.url' ), $container->get( 'wcgateway.url' ),
$container->get( 'axo.payment_method_selected_map' ), $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\Authentication\SdkClientToken;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; 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\ExecutableModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration;
/** /**
* Class AxoBlockModule * Class AxoBlockModule
@ -188,7 +184,7 @@ class AxoBlockModule implements ServiceModule, ExtendingModule, ExecutableModule
return; return;
} }
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' ); $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
if ( ! $dcc_configuration->use_fastlane() ) { if ( ! $dcc_configuration->use_fastlane() ) {
return; return;
} }

View file

@ -16,7 +16,7 @@ use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/** /**
* Class AxoBlockPaymentMethod * Class AxoBlockPaymentMethod
@ -61,9 +61,9 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
/** /**
* The DCC gateway settings. * The DCC gateway settings.
* *
* @var DCCGatewayConfiguration * @var CardPaymentsConfiguration
*/ */
protected DCCGatewayConfiguration $dcc_configuration; protected CardPaymentsConfiguration $dcc_configuration;
/** /**
* The environment object. * The environment object.
@ -101,7 +101,7 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
* @param WC_Payment_Gateway $gateway Credit card gateway. * @param WC_Payment_Gateway $gateway Credit card gateway.
* @param SmartButtonInterface|callable $smart_button The smart button script loading handler. * @param SmartButtonInterface|callable $smart_button The smart button script loading handler.
* @param Settings $settings The settings. * @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 Environment $environment The environment object.
* @param string $wcgateway_module_url The WcGateway module URL. * @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. * @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, WC_Payment_Gateway $gateway,
$smart_button, $smart_button,
Settings $settings, Settings $settings,
DCCGatewayConfiguration $dcc_configuration, CardPaymentsConfiguration $dcc_configuration,
Environment $environment, Environment $environment,
string $wcgateway_module_url, string $wcgateway_module_url,
array $payment_method_selected_map, 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\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter; use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter;
return array( return array(
@ -39,7 +39,10 @@ return array(
}, },
'axo.helpers.compatibility-checker' => static function ( ContainerInterface $container ) : CompatibilityChecker { '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. // If AXO is configured and onboarded.
@ -80,7 +83,7 @@ return array(
return new AxoGateway( return new AxoGateway(
$container->get( 'wcgateway.settings.render' ), $container->get( 'wcgateway.settings.render' ),
$container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.configuration.dcc' ), $container->get( 'wcgateway.configuration.card-configuration' ),
$container->get( 'wcgateway.url' ), $container->get( 'wcgateway.url' ),
$container->get( 'session.handler' ), $container->get( 'session.handler' ),
$container->get( 'wcgateway.order-processor' ), $container->get( 'wcgateway.order-processor' ),
@ -193,10 +196,7 @@ return array(
$compatibility_checker = $container->get( 'axo.helpers.compatibility-checker' ); $compatibility_checker = $container->get( 'axo.helpers.compatibility-checker' );
assert( $compatibility_checker instanceof CompatibilityChecker ); assert( $compatibility_checker instanceof CompatibilityChecker );
$settings = $container->get( 'wcgateway.settings' ); return $compatibility_checker->generate_settings_conflict_notice();
assert( $settings instanceof Settings );
return $compatibility_checker->generate_settings_conflict_notice( $settings );
}, },
'axo.checkout-config-notice' => static function ( ContainerInterface $container ) : string { '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 { 'axo.incompatible-plugins-notice.raw' => static function ( ContainerInterface $container ) : string {
$settings_notice_generator = new CompatibilityChecker( $settings_notice_generator = $container->get( 'axo.helpers.compatibility-checker' );
$container->get( 'axo.fastlane-incompatible-plugin-names' ) assert( $settings_notice_generator instanceof CompatibilityChecker );
);
return $settings_notice_generator->generate_incompatible_plugins_notice( true ); return $settings_notice_generator->generate_incompatible_plugins_notice( true );
}, },
'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string { 'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string {
$dcc_configuration = $container->get( 'wcgateway.configuration.dcc' ); $dcc_configuration = $container->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration ); assert( $dcc_configuration instanceof CardPaymentsConfiguration );
if ( $dcc_configuration->use_fastlane() ) { if ( $dcc_configuration->use_fastlane() ) {
$fastlane_settings_url = admin_url( $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\WcGateway\Settings\SettingsListener;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WC_Payment_Gateways; use WC_Payment_Gateways;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/** /**
* Class AxoModule * Class AxoModule
@ -98,8 +98,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
return $methods; return $methods;
} }
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' ); $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration ); assert( $dcc_configuration instanceof CardPaymentsConfiguration );
if ( ! $dcc_configuration->is_enabled() ) { if ( ! $dcc_configuration->is_enabled() ) {
return $methods; return $methods;
@ -164,8 +164,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
$listener = $c->get( 'wcgateway.settings.listener' ); $listener = $c->get( 'wcgateway.settings.listener' );
assert( $listener instanceof SettingsListener ); assert( $listener instanceof SettingsListener );
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' ); $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration ); assert( $dcc_configuration instanceof CardPaymentsConfiguration );
$listener->filter_settings( $listener->filter_settings(
$dcc_configuration->use_fastlane(), $dcc_configuration->use_fastlane(),
@ -247,8 +247,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
add_filter( add_filter(
'woocommerce_paypal_payments_sdk_components_hook', 'woocommerce_paypal_payments_sdk_components_hook',
function( $components ) use ( $c ) { function( $components ) use ( $c ) {
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' ); $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration ); assert( $dcc_configuration instanceof CardPaymentsConfiguration );
if ( ! $dcc_configuration->use_fastlane() ) { if ( ! $dcc_configuration->use_fastlane() ) {
return $components; return $components;
@ -262,8 +262,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
'wp_head', 'wp_head',
function () use ( $c ) { function () use ( $c ) {
// Add meta tag to allow feature-detection of the site's AXO payment state. // Add meta tag to allow feature-detection of the site's AXO payment state.
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' ); $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration ); assert( $dcc_configuration instanceof CardPaymentsConfiguration );
if ( $dcc_configuration->use_fastlane() ) { if ( $dcc_configuration->use_fastlane() ) {
// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
@ -403,8 +403,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
* @return bool * @return bool
*/ */
private function should_render_fastlane( ContainerInterface $c ): bool { private function should_render_fastlane( ContainerInterface $c ): bool {
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' ); $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration ); assert( $dcc_configuration instanceof CardPaymentsConfiguration );
$subscription_helper = $c->get( 'wc-subscriptions.helper' ); $subscription_helper = $c->get( 'wc-subscriptions.helper' );
assert( $subscription_helper instanceof SubscriptionHelper ); 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\Gateway\ProcessPaymentTrait;
use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException; use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/** /**
* Class AXOGateway. * Class AXOGateway.
@ -56,9 +56,9 @@ class AxoGateway extends WC_Payment_Gateway {
/** /**
* Gateway configuration object, providing relevant settings. * Gateway configuration object, providing relevant settings.
* *
* @var DCCGatewayConfiguration * @var CardPaymentsConfiguration
*/ */
protected DCCGatewayConfiguration $dcc_configuration; protected CardPaymentsConfiguration $dcc_configuration;
/** /**
* The WcGateway module URL. * The WcGateway module URL.
@ -133,24 +133,24 @@ class AxoGateway extends WC_Payment_Gateway {
/** /**
* AXOGateway constructor. * AXOGateway constructor.
* *
* @param SettingsRenderer $settings_renderer The settings renderer. * @param SettingsRenderer $settings_renderer The settings renderer.
* @param ContainerInterface $ppcp_settings The settings. * @param ContainerInterface $ppcp_settings The settings.
* @param DCCGatewayConfiguration $dcc_configuration The DCC Gateway configuration. * @param CardPaymentsConfiguration $dcc_configuration The DCC Gateway configuration.
* @param string $wcgateway_module_url The WcGateway module URL. * @param string $wcgateway_module_url The WcGateway module URL.
* @param SessionHandler $session_handler The Session Handler. * @param SessionHandler $session_handler The Session Handler.
* @param OrderProcessor $order_processor The Order processor. * @param OrderProcessor $order_processor The Order processor.
* @param array $card_icons The card icons. * @param array $card_icons The card icons.
* @param OrderEndpoint $order_endpoint The order endpoint. * @param OrderEndpoint $order_endpoint The order endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. * @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory.
* @param ShippingPreferenceFactory $shipping_preference_factory The shipping preference factory. * @param ShippingPreferenceFactory $shipping_preference_factory The shipping preference factory.
* @param TransactionUrlProvider $transaction_url_provider The transaction url provider. * @param TransactionUrlProvider $transaction_url_provider The transaction url provider.
* @param Environment $environment The environment. * @param Environment $environment The environment.
* @param LoggerInterface $logger The logger. * @param LoggerInterface $logger The logger.
*/ */
public function __construct( public function __construct(
SettingsRenderer $settings_renderer, SettingsRenderer $settings_renderer,
ContainerInterface $ppcp_settings, ContainerInterface $ppcp_settings,
DCCGatewayConfiguration $dcc_configuration, CardPaymentsConfiguration $dcc_configuration,
string $wcgateway_module_url, string $wcgateway_module_url,
SessionHandler $session_handler, SessionHandler $session_handler,
OrderProcessor $order_processor, OrderProcessor $order_processor,

View file

@ -6,16 +6,19 @@
* @package WooCommerce\PayPalCommerce\Axo\Helper * @package WooCommerce\PayPalCommerce\Axo\Helper
*/ */
declare(strict_types=1); declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Axo\Helper; namespace WooCommerce\PayPalCommerce\Axo\Helper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector; use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/** /**
* Class CompatibilityChecker * Class CompatibilityChecker
*
* DI service: 'axo.helpers.compatibility-checker'
*/ */
class CompatibilityChecker { class CompatibilityChecker {
/** /**
@ -33,20 +36,24 @@ class CompatibilityChecker {
protected array $checkout_compatibility; 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. * 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->incompatible_plugin_names = $incompatible_plugin_names;
$this->checkout_compatibility = array( $this->dcc_configuration = $dcc_configuration;
$this->checkout_compatibility = array(
'has_elementor_checkout' => null, 'has_elementor_checkout' => null,
'has_classic_checkout' => null, 'has_classic_checkout' => null,
'has_block_checkout' => null, 'has_block_checkout' => null,
@ -58,7 +65,7 @@ class CompatibilityChecker {
* *
* @return bool Whether the checkout uses Elementor. * @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 ) { if ( $this->checkout_compatibility['has_elementor_checkout'] === null ) {
$this->checkout_compatibility['has_elementor_checkout'] = CartCheckoutDetector::has_elementor_checkout(); $this->checkout_compatibility['has_elementor_checkout'] = CartCheckoutDetector::has_elementor_checkout();
} }
@ -71,7 +78,7 @@ class CompatibilityChecker {
* *
* @return bool Whether the checkout uses classic checkout. * @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 ) { if ( $this->checkout_compatibility['has_classic_checkout'] === null ) {
$this->checkout_compatibility['has_classic_checkout'] = CartCheckoutDetector::has_classic_checkout(); $this->checkout_compatibility['has_classic_checkout'] = CartCheckoutDetector::has_classic_checkout();
} }
@ -84,7 +91,7 @@ class CompatibilityChecker {
* *
* @return bool Whether the checkout uses block checkout. * @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 ) { if ( $this->checkout_compatibility['has_block_checkout'] === null ) {
$this->checkout_compatibility['has_block_checkout'] = CartCheckoutDetector::has_block_checkout(); $this->checkout_compatibility['has_block_checkout'] = CartCheckoutDetector::has_block_checkout();
} }
@ -92,29 +99,12 @@ class CompatibilityChecker {
return $this->checkout_compatibility['has_block_checkout']; 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. * Generates the full HTML of the notification.
* *
* @param string $message HTML of the inner message contents. * @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 $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. * @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. * @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. * @return bool Whether the setup is compatible.
*/ */
public function is_fastlane_compatible(): bool { public function is_fastlane_compatible() : bool {
// Check for incompatible plugins. // Check for incompatible plugins.
if ( ! empty( $this->incompatible_plugin_names ) ) { if ( ! empty( $this->incompatible_plugin_names ) ) {
return false; return false;
@ -165,7 +156,7 @@ class CompatibilityChecker {
* @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 * @return string
*/ */
public function generate_checkout_notice( bool $raw_message = false ): string { public function generate_checkout_notice( bool $raw_message = false ) : string {
$notice_content = ''; $notice_content = '';
// Check for checkout incompatibilities. // Check for checkout incompatibilities.
@ -213,7 +204,7 @@ class CompatibilityChecker {
* @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 * @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 ) ) { if ( empty( $this->incompatible_plugin_names ) ) {
return ''; return '';
} }
@ -235,12 +226,11 @@ class CompatibilityChecker {
/** /**
* Generates a warning notice with instructions on conflicting plugin-internal settings. * 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 * @return string
*/ */
public function generate_settings_conflict_notice( Settings $settings, bool $raw_message = false ) : string { public function generate_settings_conflict_notice( bool $raw_message = false ) : string {
if ( $this->is_dcc_enabled( $settings ) ) { if ( $this->dcc_configuration->is_enabled() ) {
return ''; return '';
} }

View file

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

View file

@ -37,7 +37,7 @@ use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure; use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
return array( return array(
'button.client_id' => static function ( ContainerInterface $container ): string { '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 ); $no_smart_buttons = ! $settings_status->is_smart_button_enabled_for_location( $context );
$dcc_configuration = $container->get( 'wcgateway.configuration.dcc' ); $dcc_configuration = $container->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration ); assert( $dcc_configuration instanceof CardPaymentsConfiguration );
if ( $no_smart_buttons && ! $dcc_configuration->is_enabled() ) { if ( $no_smart_buttons && ! $dcc_configuration->is_enabled() ) {
// Smart buttons disabled, and also not using advanced card payments. // Smart buttons disabled, and also not using advanced card payments.
@ -167,7 +167,8 @@ return array(
$container->get( 'api.endpoint.payment-tokens' ), $container->get( 'api.endpoint.payment-tokens' ),
$container->get( 'woocommerce.logger.woocommerce' ), $container->get( 'woocommerce.logger.woocommerce' ),
$container->get( 'button.handle-shipping-in-paypal' ), $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 { 'button.url' => static function ( ContainerInterface $container ): string {
@ -343,7 +344,8 @@ return array(
'button.helper.disabled-funding-sources' => static function ( ContainerInterface $container ): DisabledFundingSources { 'button.helper.disabled-funding-sources' => static function ( ContainerInterface $container ): DisabledFundingSources {
return new DisabledFundingSources( return new DisabledFundingSources(
$container->get( 'wcgateway.settings' ), $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 { '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 WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WC_Shipping_Method; use WC_Shipping_Method;
use WC_Cart; use WC_Cart;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/** /**
* Class SmartButton * Class SmartButton
@ -237,33 +238,41 @@ class SmartButton implements SmartButtonInterface {
*/ */
private $disabled_funding_sources; private $disabled_funding_sources;
/**
* Provides details about the DCC configuration.
*
* @var CardPaymentsConfiguration
*/
private CardPaymentsConfiguration $dcc_configuration;
/** /**
* SmartButton constructor. * SmartButton constructor.
* *
* @param string $module_url The URL to the module. * @param string $module_url The URL to the module.
* @param string $version The assets version. * @param string $version The assets version.
* @param SessionHandler $session_handler The Session handler. * @param SessionHandler $session_handler The Session handler.
* @param Settings $settings The Settings. * @param Settings $settings The Settings.
* @param PayerFactory $payer_factory The Payer factory. * @param PayerFactory $payer_factory The Payer factory.
* @param string $client_id The client ID. * @param string $client_id The client ID.
* @param RequestData $request_data The Request Data helper. * @param RequestData $request_data The Request Data helper.
* @param DccApplies $dcc_applies The DCC applies helper. * @param DccApplies $dcc_applies The DCC applies helper.
* @param SubscriptionHelper $subscription_helper The subscription helper. * @param SubscriptionHelper $subscription_helper The subscription helper.
* @param MessagesApply $messages_apply The Messages apply helper. * @param MessagesApply $messages_apply The Messages apply helper.
* @param Environment $environment The environment object. * @param Environment $environment The environment object.
* @param PaymentTokenRepository $payment_token_repository The payment token repository. * @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param SettingsStatus $settings_status The Settings status helper. * @param SettingsStatus $settings_status The Settings status helper.
* @param CurrencyGetter $currency The getter of the 3-letter currency code of the shop. * @param CurrencyGetter $currency The getter of the 3-letter currency code of the shop.
* @param array $all_funding_sources All existing funding sources. * @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 $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 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 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 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 bool $vault_v3_enabled Whether Vault v3 module is enabled.
* @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint. * @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
* @param LoggerInterface $logger The logger. * @param LoggerInterface $logger The logger.
* @param bool $should_handle_shipping_in_paypal Whether the shipping should be handled in PayPal. * @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 DisabledFundingSources $disabled_funding_sources List of funding sources to be disabled.
* @param CardPaymentsConfiguration $dcc_configuration The DCC Gateway Configuration.
*/ */
public function __construct( public function __construct(
string $module_url, string $module_url,
@ -289,9 +298,9 @@ class SmartButton implements SmartButtonInterface {
PaymentTokensEndpoint $payment_tokens_endpoint, PaymentTokensEndpoint $payment_tokens_endpoint,
LoggerInterface $logger, LoggerInterface $logger,
bool $should_handle_shipping_in_paypal, bool $should_handle_shipping_in_paypal,
DisabledFundingSources $disabled_funding_sources DisabledFundingSources $disabled_funding_sources,
CardPaymentsConfiguration $dcc_configuration
) { ) {
$this->module_url = $module_url; $this->module_url = $module_url;
$this->version = $version; $this->version = $version;
$this->session_handler = $session_handler; $this->session_handler = $session_handler;
@ -316,6 +325,7 @@ class SmartButton implements SmartButtonInterface {
$this->payment_tokens_endpoint = $payment_tokens_endpoint; $this->payment_tokens_endpoint = $payment_tokens_endpoint;
$this->should_handle_shipping_in_paypal = $should_handle_shipping_in_paypal; $this->should_handle_shipping_in_paypal = $should_handle_shipping_in_paypal;
$this->disabled_funding_sources = $disabled_funding_sources; $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(); $this->render_message_wrapper_registrar();
} }
if ( if ( $this->dcc_configuration->is_enabled() ) {
$this->settings->has( 'dcc_enabled' ) $this->render_dcc_wrapper();
&& $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->is_free_trial_cart() ) { if ( $this->is_free_trial_cart() ) {
@ -439,6 +381,74 @@ class SmartButton implements SmartButtonInterface {
return true; 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. * 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. * Whether DCC fields can be rendered.
*/ */
public function can_render_dcc() : bool { public function can_render_dcc() : bool {
return $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) return $this->dcc_configuration->is_acdc_enabled()
&& $this->settings->has( 'client_id' ) && $this->settings->get( 'client_id' )
&& $this->dcc_applies->for_country_currency()
&& in_array( && in_array(
$this->context(), $this->context(),
apply_filters( 'woocommerce_paypal_payments_can_render_dcc_contexts', array( 'checkout', 'pay-now', 'add-payment-method' ) ), 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 { public function script_data(): array {
$is_free_trial_cart = $this->is_free_trial_cart(); $is_free_trial_cart = $this->is_free_trial_cart();
$is_acdc_enabled = $this->dcc_configuration->is_acdc_enabled();
$url_params = $this->url_params(); $url_params = $this->url_params();
@ -1125,7 +1134,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
'client_id' => $this->client_id, 'client_id' => $this->client_id,
'currency' => $this->currency->get(), 'currency' => $this->currency->get(),
'data_client_id' => array( '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 ), 'endpoint' => \WC_AJAX::get_endpoint( DataClientIdEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( DataClientIdEndpoint::nonce() ), 'nonce' => wp_create_nonce( DataClientIdEndpoint::nonce() ),
'user' => get_current_user_id(), 'user' => get_current_user_id(),
@ -1455,20 +1464,6 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
$disabled_funding_sources[] = 'paylater'; $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 ) { if ( count( $disabled_funding_sources ) > 0 ) {
$params['disable-funding'] = implode( ',', array_unique( $disabled_funding_sources ) ); $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. * The JS SKD components we need to load.
* *
* @return array * @return array
* @throws NotFoundException If a setting was not found.
*/ */
private function components(): array { private function components(): array {
$components = array(); $components = array();
@ -1586,9 +1580,12 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
if ( $this->should_load_messages() ) { if ( $this->should_load_messages() ) {
$components[] = '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'; $components[] = 'hosted-fields';
} }
/** /**
* Filter to add further components from the extensions. * 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. * Determines the style for a given property in a given context.
* *

View file

@ -5,14 +5,14 @@
* @package WooCommerce\PayPalCommerce\Button\Helper * @package WooCommerce\PayPalCommerce\Button\Helper
*/ */
declare(strict_types=1); declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Button\Helper; namespace WooCommerce\PayPalCommerce\Button\Helper;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/** /**
* Class DisabledFundingSources * Class DisabledFundingSources
@ -26,84 +26,187 @@ class DisabledFundingSources {
* *
* @var Settings * @var Settings
*/ */
private $settings; private Settings $settings;
/** /**
* All existing funding sources. * All existing funding sources.
* *
* @var array * @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. * DisabledFundingSources constructor.
* *
* @param Settings $settings The settings. * @param Settings $settings The settings.
* @param array $all_funding_sources All existing funding sources. * @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->settings = $settings;
$this->all_funding_sources = $all_funding_sources; $this->all_funding_sources = $all_funding_sources;
$this->dcc_configuration = $dcc_configuration;
} }
/** /**
* Returns the list of funding sources to be disabled. * Returns the list of funding sources to be disabled.
* *
* @param string $context The context. * @param string $context The context.
* @return array|int[]|mixed|string[] * @return string[] List of disabled sources
* @throws NotFoundException When the setting is not found.
*/ */
public function sources( string $context ) { public function sources( string $context ) : array {
$disable_funding = $this->settings->has( 'disable_funding' ) $block_contexts = array( 'checkout-block', 'cart-block' );
? $this->settings->get( 'disable_funding' ) $flags = array(
: 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 ( return $this->sanitize_and_filter_sources( $disable_funding, $flags );
! is_checkout()
|| ( $is_dcc_enabled && in_array( $context, array( 'checkout-block', 'cart-block' ), true ) )
) {
$disable_funding[] = 'card';
} }
$available_gateways = WC()->payment_gateways->get_available_payment_gateways(); $disable_funding = $this->get_sources_from_settings();
$is_separate_card_enabled = isset( $available_gateways[ CardButtonGateway::ID ] );
if ( // Apply rules based on context and payment methods.
( $disable_funding = $this->apply_context_rules( $disable_funding );
is_checkout()
&& ! in_array( $context, array( 'checkout-block', 'cart-block' ), true ) // Apply special rules for block checkout.
) if ( $flags['is_block_context'] ) {
&& ( $disable_funding = $this->apply_block_checkout_rules( $disable_funding );
$is_dcc_enabled
|| $is_separate_card_enabled
)
) {
$key = array_search( 'card', $disable_funding, true );
if ( false !== $key ) {
unset( $disable_funding[ $key ] );
}
} }
if ( in_array( $context, array( 'checkout-block', 'cart-block' ), true ) ) { return $this->sanitize_and_filter_sources( $disable_funding, $flags );
$disable_funding = array_merge( }
/**
* 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, $disable_funding,
array_diff( static fn( string $funding_source ) => $funding_source !== 'card'
array_keys( $this->all_funding_sources ),
array( 'venmo', 'paylater', 'paypal', 'card' )
)
); );
} }
if ( $this->is_free_trial_cart() ) { return $disable_funding;
$all_sources = array_keys( $this->all_funding_sources ); }
if ( $is_dcc_enabled || $is_separate_card_enabled ) {
$all_sources = array_diff( $all_sources, array( 'card' ) ); /**
} * Applies rules based on context and payment methods.
$disable_funding = $all_sources; *
* @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\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/** /**
* Class CardFieldsModule * Class CardFieldsModule
@ -46,29 +46,51 @@ class CardFieldsModule implements ServiceModule, ExtendingModule, ExecutableModu
return true; return true;
} }
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
add_filter( add_filter(
'woocommerce_paypal_payments_sdk_components_hook', 'woocommerce_paypal_payments_sdk_components_hook',
function( $components ) use ( $c ) { static function( array $components ) use ( $c ) {
if ( ! $c->get( 'wcgateway.configuration.dcc' )->is_enabled() ) { $dcc_config = $c->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_config instanceof CardPaymentsConfiguration );
if ( ! $dcc_config->is_acdc_enabled() ) {
return $components; return $components;
} }
if ( in_array( 'hosted-fields', $components, true ) ) {
$key = array_search( 'hosted-fields', $components, true ); // Enable the new "card-fields" component.
if ( $key !== false ) {
unset( $components[ $key ] );
}
}
$components[] = 'card-fields'; $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( add_filter(
'woocommerce_credit_card_form_fields', 'woocommerce_credit_card_form_fields',
/** /**
@ -78,7 +100,7 @@ class CardFieldsModule implements ServiceModule, ExtendingModule, ExecutableModu
* @psalm-suppress MissingClosureParamType * @psalm-suppress MissingClosureParamType
*/ */
function( $default_fields, $id ) use ( $c ) { 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; return $default_fields;
} }
if ( CreditCardGateway::ID === $id && apply_filters( 'woocommerce_paypal_payments_enable_cardholder_name_field', false ) ) { 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( add_filter(
'ppcp_create_order_request_body_data', 'ppcp_create_order_request_body_data',
function( array $data, string $payment_method ) use ( $c ): array { 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; return $data;
} }
// phpcs:ignore WordPress.Security.NonceVerification.Missing // phpcs:ignore WordPress.Security.NonceVerification.Missing

View file

@ -10,7 +10,6 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Compat; namespace WooCommerce\PayPalCommerce\Compat;
use Exception; use Exception;
use Psr\Log\LoggerInterface;
use WC_Cart; use WC_Cart;
use WC_Order; use WC_Order;
use WC_Order_Item_Product; 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' ) ); add_action( 'woocommerce_paypal_payments_gateway_migrate', static fn() => delete_transient( 'ppcp_has_ppec_subscriptions' ) );
$this->legacy_ui_card_payment_mapping( $c );
return true; return true;
} }
@ -491,4 +492,34 @@ class CompatModule implements ServiceModule, ExtendingModule, ExecutableModule {
2 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\Onboarding\Render\OnboardingRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Helper\ConnectionState;
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
return array( return array(
'api.paypal-host' => function( ContainerInterface $container ) : string { 'api.paypal-host' => function( ContainerInterface $container ) : string {
@ -48,25 +50,57 @@ return array(
return new State( $settings ); return new State( $settings );
}, },
/** /**
* Checks if the onboarding process is completed and the merchant API can be used. * Merchant connection details, which includes the connection status
* This service is overwritten by the ppcp-settings module, when it's active. * (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' ); $state = $container->get( 'onboarding.state' );
assert( $state instanceof 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' ); $settings = $container->get( 'wcgateway.settings' );
assert( $settings instanceof 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 { 'settings.environment' => function ( ContainerInterface $container ) : Environment {
return new Environment( $state = $container->get( 'settings.connection-state' );
$container->get( 'settings.flag.is-sandbox' ) assert( $state instanceof ConnectionState );
);
return $state->get_environment();
}, },
'onboarding.assets' => function( ContainerInterface $container ) : OnboardingAssets { 'onboarding.assets' => function( ContainerInterface $container ) : OnboardingAssets {

View file

@ -153,34 +153,36 @@ return array(
return new ConnectionState( $is_connected, $environment ); 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 { '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' ); $state = $container->get( 'settings.connection-state' );
assert( $state instanceof ConnectionState ); assert( $state instanceof ConnectionState );
return $state->get_environment(); 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 { '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' ); $state = $container->get( 'settings.connection-state' );
assert( $state instanceof ConnectionState ); assert( $state instanceof ConnectionState );
return $state->is_connected(); 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 { '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' ); $state = $container->get( 'settings.connection-state' );
assert( $state instanceof ConnectionState ); assert( $state instanceof ConnectionState );

View file

@ -57,7 +57,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
* Returns whether the old settings UI should be loaded. * Returns whether the old settings UI should be loaded.
*/ */
public static function should_use_the_old_ui() : bool { 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' ); $show_new_ux = '1' === get_option( 'woocommerce-ppcp-is-new-merchant' );
if ( $show_new_ux ) { if ( $show_new_ux ) {
@ -394,7 +394,7 @@ class SettingsModule implements ServiceModule, ExecutableModule {
unset( $payment_methods[ OXXO::ID ] ); 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 ) { if ( 'DE' !== $merchant_country ) {
unset( $payment_methods[ PayUponInvoiceGateway::ID ] ); 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. // Enable Fastlane after onboarding if the store is compatible.
add_action( add_action(
'woocommerce_paypal_payments_apply_default_configuration', '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\WcGateway\Settings\SettingsRenderer;
use WooCommerce\PayPalCommerce\Axo\Helper\PropertiesDictionary; use WooCommerce\PayPalCommerce\Axo\Helper\PropertiesDictionary;
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway; 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( return array(
'wcgateway.paypal-gateway' => static function ( ContainerInterface $container ): PayPalGateway { 'wcgateway.paypal-gateway' => static function ( ContainerInterface $container ): PayPalGateway {
@ -117,7 +118,7 @@ return array(
$container->get( 'wcgateway.settings.render' ), $container->get( 'wcgateway.settings.render' ),
$container->get( 'wcgateway.order-processor' ), $container->get( 'wcgateway.order-processor' ),
$container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.configuration.dcc' ), $container->get( 'wcgateway.configuration.card-configuration' ),
$container->get( 'wcgateway.credit-card-icons' ), $container->get( 'wcgateway.credit-card-icons' ),
$container->get( 'wcgateway.url' ), $container->get( 'wcgateway.url' ),
$container->get( 'session.handler' ), $container->get( 'session.handler' ),
@ -333,7 +334,8 @@ return array(
$container->get( 'settings.flag.is-connected' ), $container->get( 'settings.flag.is-connected' ),
$container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.is-wc-payments-page' ), $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 { 'wcgateway.notice.card-button-without-paypal' => static function ( ContainerInterface $container ): GatewayWithoutPayPalAdminNotice {
@ -343,6 +345,7 @@ return array(
$container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.is-wc-payments-page' ), $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' ),
$container->get( 'wcgateway.settings.status' ) $container->get( 'wcgateway.settings.status' )
); );
}, },
@ -461,8 +464,8 @@ return array(
assert( $settings instanceof Settings ); assert( $settings instanceof Settings );
$axo_available = $container->has( 'axo.available' ) && $container->get( 'axo.available' ); $axo_available = $container->has( 'axo.available' ) && $container->get( 'axo.available' );
$dcc_configuration = $container->get( 'wcgateway.configuration.dcc' ); $dcc_configuration = $container->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration ); assert( $dcc_configuration instanceof CardPaymentsConfiguration );
if ( $axo_available && $dcc_configuration->use_fastlane() ) { if ( $axo_available && $dcc_configuration->use_fastlane() ) {
return ''; return '';
@ -684,8 +687,8 @@ return array(
$subscription_helper = $container->get( 'wc-subscriptions.helper' ); $subscription_helper = $container->get( 'wc-subscriptions.helper' );
assert( $subscription_helper instanceof SubscriptionHelper ); assert( $subscription_helper instanceof SubscriptionHelper );
$dcc_configuration = $container->get( 'wcgateway.configuration.dcc' ); $dcc_configuration = $container->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration ); assert( $dcc_configuration instanceof CardPaymentsConfiguration );
$fields = array( $fields = array(
'checkout_settings_heading' => array( 'checkout_settings_heading' => array(
@ -1359,11 +1362,12 @@ return array(
return new TransactionUrlProvider( $sandbox_url_base, $live_url_base ); return new TransactionUrlProvider( $sandbox_url_base, $live_url_base );
}, },
'wcgateway.configuration.dcc' => static function ( ContainerInterface $container ) : DCCGatewayConfiguration { 'wcgateway.configuration.card-configuration' => static function ( ContainerInterface $container ) : CardPaymentsConfiguration {
$settings = $container->get( 'wcgateway.settings' ); return new CardPaymentsConfiguration(
assert( $settings instanceof Settings ); $container->get( 'settings.connection-state' ),
$container->get( 'wcgateway.settings' ),
return new DCCGatewayConfiguration( $settings ); $container->get( 'api.helpers.dccapplies' )
);
}, },
'wcgateway.helper.dcc-product-status' => static function ( ContainerInterface $container ) : DCCProductStatus { '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\WcGateway\Settings\SettingsRenderer;
use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
/** /**
* Class CreditCardGateway * Class CreditCardGateway
@ -76,9 +76,9 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
/** /**
* The DCC Gateway Configuration. * The DCC Gateway Configuration.
* *
* @var DCCGatewayConfiguration * @var CardPaymentsConfiguration
*/ */
protected DCCGatewayConfiguration $dcc_configuration; protected CardPaymentsConfiguration $dcc_configuration;
/** /**
* The vaulted credit card handler. * The vaulted credit card handler.
@ -188,31 +188,31 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
/** /**
* CreditCardGateway constructor. * CreditCardGateway constructor.
* *
* @param SettingsRenderer $settings_renderer The Settings Renderer. * @param SettingsRenderer $settings_renderer The Settings Renderer.
* @param OrderProcessor $order_processor The Order processor. * @param OrderProcessor $order_processor The Order processor.
* @param ContainerInterface $config The settings. * @param ContainerInterface $config The settings.
* @param DCCGatewayConfiguration $dcc_configuration The DCC Gateway Configuration. * @param CardPaymentsConfiguration $dcc_configuration The DCC Gateway Configuration.
* @param array $card_icons The card icons. * @param array $card_icons The card icons.
* @param string $module_url The URL to the module. * @param string $module_url The URL to the module.
* @param SessionHandler $session_handler The Session Handler. * @param SessionHandler $session_handler The Session Handler.
* @param RefundProcessor $refund_processor The refund processor. * @param RefundProcessor $refund_processor The refund processor.
* @param TransactionUrlProvider $transaction_url_provider Service able to provide view transaction url base. * @param TransactionUrlProvider $transaction_url_provider Service able to provide view transaction url base.
* @param SubscriptionHelper $subscription_helper The subscription helper. * @param SubscriptionHelper $subscription_helper The subscription helper.
* @param PaymentsEndpoint $payments_endpoint The payments endpoint. * @param PaymentsEndpoint $payments_endpoint The payments endpoint.
* @param VaultedCreditCardHandler $vaulted_credit_card_handler The vaulted credit card handler. * @param VaultedCreditCardHandler $vaulted_credit_card_handler The vaulted credit card handler.
* @param Environment $environment The environment. * @param Environment $environment The environment.
* @param OrderEndpoint $order_endpoint The order endpoint. * @param OrderEndpoint $order_endpoint The order endpoint.
* @param CaptureCardPayment $capture_card_payment Capture card payment. * @param CaptureCardPayment $capture_card_payment Capture card payment.
* @param string $prefix The prefix. * @param string $prefix The prefix.
* @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint. * @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
* @param WooCommercePaymentTokens $wc_payment_tokens WooCommerce payment tokens factory. * @param WooCommercePaymentTokens $wc_payment_tokens WooCommerce payment tokens factory.
* @param LoggerInterface $logger The logger. * @param LoggerInterface $logger The logger.
*/ */
public function __construct( public function __construct(
SettingsRenderer $settings_renderer, SettingsRenderer $settings_renderer,
OrderProcessor $order_processor, OrderProcessor $order_processor,
ContainerInterface $config, ContainerInterface $config,
DCCGatewayConfiguration $dcc_configuration, CardPaymentsConfiguration $dcc_configuration,
array $card_icons, array $card_icons,
string $module_url, string $module_url,
SessionHandler $session_handler, SessionHandler $session_handler,

View file

@ -137,10 +137,6 @@ class PayUponInvoice {
*/ */
public function init(): void { public function init(): void {
if ( $this->pui_helper->is_pui_gateway_enabled() ) { 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->set( 'fraudnet_enabled', true );
$this->settings->persist(); $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 * Manages the merchants' connection status details and provides inspection
* methods to describe the current state. * methods to describe the current state.
*
* DI service: 'settings.connection-state'
*/ */
class ConnectionState { 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\AdminNotices\Entity\Message;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; 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. * Creates the admin message about the gateway being enabled without the PayPal gateway.
@ -65,15 +66,23 @@ class GatewayWithoutPayPalAdminNotice {
*/ */
protected $settings_status; protected $settings_status;
/**
* Provides details about the DCC configuration.
*
* @var CardPaymentsConfiguration
*/
private CardPaymentsConfiguration $dcc_configuration;
/** /**
* ConnectAdminNotice constructor. * ConnectAdminNotice constructor.
* *
* @param string $id The gateway ID. * @param string $id The gateway ID.
* @param bool $is_connected Whether onboading was completed. * @param bool $is_connected Whether onboarding was completed.
* @param ContainerInterface $settings The settings. * @param ContainerInterface $settings The settings.
* @param bool $is_payments_page Whether the current page is the WC payment page. * @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 bool $is_ppcp_settings_page Whether the current page is the PPCP settings page.
* @param SettingsStatus|null $settings_status The Settings status helper. * @param CardPaymentsConfiguration $dcc_configuration DCC gateway configuration.
* @param SettingsStatus|null $settings_status The Settings status helper.
*/ */
public function __construct( public function __construct(
string $id, string $id,
@ -81,6 +90,7 @@ class GatewayWithoutPayPalAdminNotice {
ContainerInterface $settings, ContainerInterface $settings,
bool $is_payments_page, bool $is_payments_page,
bool $is_ppcp_settings_page, bool $is_ppcp_settings_page,
CardPaymentsConfiguration $dcc_configuration,
?SettingsStatus $settings_status = null ?SettingsStatus $settings_status = null
) { ) {
$this->id = $id; $this->id = $id;
@ -88,6 +98,7 @@ class GatewayWithoutPayPalAdminNotice {
$this->settings = $settings; $this->settings = $settings;
$this->is_payments_page = $is_payments_page; $this->is_payments_page = $is_payments_page;
$this->is_ppcp_settings_page = $is_ppcp_settings_page; $this->is_ppcp_settings_page = $is_ppcp_settings_page;
$this->dcc_configuration = $dcc_configuration;
$this->settings_status = $settings_status; $this->settings_status = $settings_status;
} }
@ -181,7 +192,7 @@ class GatewayWithoutPayPalAdminNotice {
return self::NOTICE_DISABLED_LOCATION; 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' ); $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 ) { 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.Missing
// phpcs:enable WordPress.Security.NonceVerification.Recommended // 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_id', $merchant_id );
$this->settings->set( 'merchant_email', $merchant_email ); $this->settings->set( 'merchant_email', $merchant_email );
@ -367,7 +367,7 @@ class SettingsListener {
return; 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 { try {
$token = $this->bearer->bearer(); $token = $this->bearer->bearer();
@ -777,7 +777,7 @@ class SettingsListener {
return; 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; $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\WcGateway\Settings\SettingsRenderer;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Registrar\TaskRegistrarInterface; 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; use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\LocalApmProductStatus;
/** /**
@ -195,8 +195,8 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
$settings = $c->get( 'wcgateway.settings' ); $settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings ); assert( $settings instanceof Settings );
$dcc_configuration = $c->get( 'wcgateway.configuration.dcc' ); $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration ); assert( $dcc_configuration instanceof CardPaymentsConfiguration );
$assets = new SettingsPageAssets( $assets = new SettingsPageAssets(
$c->get( 'wcgateway.url' ), $c->get( 'wcgateway.url' ),
@ -619,8 +619,8 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
return $methods; return $methods;
} }
$dcc_configuration = $container->get( 'wcgateway.configuration.dcc' ); $dcc_configuration = $container->get( 'wcgateway.configuration.card-configuration' );
assert( $dcc_configuration instanceof DCCGatewayConfiguration ); assert( $dcc_configuration instanceof CardPaymentsConfiguration );
$standard_card_button = get_option( 'woocommerce_ppcp-card-button-gateway_settings' ); $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;
use WooCommerce\PayPalCommerce\TestCase; use WooCommerce\PayPalCommerce\TestCase;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
use function Brain\Monkey\Functions\when; use function Brain\Monkey\Functions\when;
class DisabledFundingSourcesTest extends TestCase class DisabledFundingSourcesTest extends TestCase
{ {
private $settings; private $settings;
private $dcc_configuration;
public function setUp(): void public function setUp(): void
{ {
parent::setUp(); parent::setUp();
$this->settings = Mockery::mock(Settings::class); $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() 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->setExpectations();
$this->setWcPaymentGateways(); $this->setWcPaymentGateways();
@ -43,7 +48,9 @@ class DisabledFundingSourcesTest extends TestCase
*/ */
public function test_is_checkout_false_add_card_when_checkout_context() 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->setExpectations();
$this->setWcPaymentGateways(); $this->setWcPaymentGateways();
@ -55,11 +62,17 @@ class DisabledFundingSourcesTest extends TestCase
public function test_is_checkout_true_add_allowed_sources_when_checkout_block_context() public function test_is_checkout_true_add_allowed_sources_when_checkout_block_context()
{ {
$sut = new DisabledFundingSources($this->settings, [ $this->dcc_configuration->shouldReceive('is_enabled')->andReturn(true);
'card' => 'Credit or debit cards', $this->dcc_configuration->shouldReceive('use_acdc')->andReturn(true);
'paypal' => 'PayPal', $sut = new DisabledFundingSources(
'foo' => 'Bar', $this->settings,
]); [
'card' => 'Credit or debit cards',
'paypal' => 'PayPal',
'foo' => 'Bar',
],
$this->dcc_configuration
);
$this->setExpectations(); $this->setExpectations();
$this->setWcPaymentGateways(); $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\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; 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 WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use function Brain\Monkey\Functions\when; use function Brain\Monkey\Functions\when;
@ -53,7 +53,7 @@ class CreditCardGatewayTest extends TestCase
$this->settingsRenderer = Mockery::mock(SettingsRenderer::class); $this->settingsRenderer = Mockery::mock(SettingsRenderer::class);
$this->orderProcessor = Mockery::mock(OrderProcessor::class); $this->orderProcessor = Mockery::mock(OrderProcessor::class);
$this->config = Mockery::mock(ContainerInterface::class); $this->config = Mockery::mock(ContainerInterface::class);
$this->dcc_configuration = Mockery::mock(DCCGatewayConfiguration::class); $this->dcc_configuration = Mockery::mock(CardPaymentsConfiguration::class);
$this->creditCardIcons = []; $this->creditCardIcons = [];
$this->moduleUrl = ''; $this->moduleUrl = '';
$this->sessionHandler = Mockery::mock(SessionHandler::class); $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. * Set new merchant flag on plugin install.
* *
* When installing the plugin for the first time, we direct the user to * 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. * @param string|false $version String with previous installed plugin version.
* Boolean false on first installation on a new site. * Boolean false on first installation on a new site.