diff --git a/.ddev/commands/web/orchestrate.d/40_setup_and_activate.sh b/.ddev/commands/web/orchestrate.d/51_setup_and_activate.sh similarity index 100% rename from .ddev/commands/web/orchestrate.d/40_setup_and_activate.sh rename to .ddev/commands/web/orchestrate.d/51_setup_and_activate.sh diff --git a/.env.integration b/.env.integration deleted file mode 100644 index 8ed93a99d..000000000 --- a/.env.integration +++ /dev/null @@ -1,40 +0,0 @@ -PPCP_INTEGRATION_WP_DIR=${ROOT_DIR}/.ddev/wordpress - -BASEURL="https://woocommerce-paypal-payments.ddev.site" -AUTHORIZATION="Bearer ABC123" - -CHECKOUT_URL="/checkout" -CHECKOUT_PAGE_ID=7 -CART_URL="/cart" -BLOCK_CHECKOUT_URL="/checkout-block" -BLOCK_CHECKOUT_PAGE_ID=22 -BLOCK_CART_URL="/cart-block" - -PRODUCT_URL="/product/prod" -PRODUCT_ID=123 - -SUBSCRIPTION_URL="/product/sub" - -PAYPAL_SUBSCRIPTIONS_PRODUCT_ID=252 - -APM_ID="sofort" - -WP_MERCHANT_USER="admin" -WP_MERCHANT_PASSWORD="admin" - -WP_CUSTOMER_USER="customer" -WP_CUSTOMER_PASSWORD="password" - -CUSTOMER_EMAIL="customer@example.com" -CUSTOMER_PASSWORD="password" -CUSTOMER_FIRST_NAME="John" -CUSTOMER_LAST_NAME="Doe" -CUSTOMER_COUNTRY="DE" -CUSTOMER_ADDRESS="street 1" -CUSTOMER_POSTCODE="12345" -CUSTOMER_CITY="city" -CUSTOMER_PHONE="1234567890" - -CREDIT_CARD_NUMBER="1234567890" -CREDIT_CARD_EXPIRATION="01/2042" -CREDIT_CARD_CVV="123" diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 716a4a0b7..e02629141 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -36,4 +36,4 @@ jobs: run: ddev php tests/integration/PHPUnit/setup.php - name: Run PHPUnit - run: ddev exec phpunit -c tests/integration/phpunit.xml.dist + run: ddev exec phpunit --exclude skip-ci -c tests/integration/phpunit.xml.dist diff --git a/.gitignore b/.gitignore index 0da873dac..69f38eab7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,9 @@ modules/*/assets/* !modules/ppcp-wc-gateway/assets/images *.zip .env -.env.e2e +.env.integration auth.json .DS_Store tests/.DS_Store .composer_compiled_assets + diff --git a/modules/ppcp-api-client/src/Repository/PartnerReferralsData.php b/modules/ppcp-api-client/src/Repository/PartnerReferralsData.php index d214c9df7..b95ae1ca3 100644 --- a/modules/ppcp-api-client/src/Repository/PartnerReferralsData.php +++ b/modules/ppcp-api-client/src/Repository/PartnerReferralsData.php @@ -21,7 +21,7 @@ class PartnerReferralsData { * @deprecated Deprecates with the new UI. In this class, the products are * always explicit, and should not be deducted from the * DccApplies state at this point. - * Remove this with the legacy UI code. + * Remove this with the #legacy-ui code. * @var DccApplies */ private DccApplies $dcc_applies; @@ -92,7 +92,7 @@ class PartnerReferralsData { $first_party_features[] = 'BILLING_AGREEMENT'; } - // Backwards compatibility. Keep those features in the legacy UI (null-value). + // Backwards compatibility. Keep those features in the #legacy-ui (null-value). // Move this into the previous condition, once legacy code is removed. if ( false !== $use_subscriptions ) { $first_party_features[] = 'FUTURE_PAYMENT'; diff --git a/modules/ppcp-axo-block/services.php b/modules/ppcp-axo-block/services.php index b93513148..a93d9096c 100644 --- a/modules/ppcp-axo-block/services.php +++ b/modules/ppcp-axo-block/services.php @@ -35,7 +35,7 @@ return array( $container->get( 'axo.gateway' ), fn(): SmartButtonInterface => $container->get( 'button.smart-button' ), $container->get( 'wcgateway.settings' ), - $container->get( 'wcgateway.configuration.dcc' ), + $container->get( 'wcgateway.configuration.card-configuration' ), $container->get( 'settings.environment' ), $container->get( 'wcgateway.url' ), $container->get( 'axo.payment_method_selected_map' ), diff --git a/modules/ppcp-axo-block/src/AxoBlockModule.php b/modules/ppcp-axo-block/src/AxoBlockModule.php index cd5b0fa25..307898cdc 100644 --- a/modules/ppcp-axo-block/src/AxoBlockModule.php +++ b/modules/ppcp-axo-block/src/AxoBlockModule.php @@ -14,15 +14,11 @@ use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; -use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint; -use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; -use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; -use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; /** * Class AxoBlockModule @@ -74,6 +70,10 @@ class AxoBlockModule implements ServiceModule, ExtendingModule, ExecutableModule add_filter( 'woocommerce_paypal_payments_localized_script_data', function( array $localized_script_data ) use ( $c ) { + if ( ! $c->has( 'axo.available' ) || ! $c->get( 'axo.available' ) ) { + return $localized_script_data; + } + $module = $this; $api = $c->get( 'api.sdk-client-token' ); assert( $api instanceof SdkClientToken ); @@ -188,7 +188,7 @@ class AxoBlockModule implements ServiceModule, ExtendingModule, ExecutableModule return; } - $dcc_configuration = $c->get( 'wcgateway.configuration.dcc' ); + $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' ); if ( ! $dcc_configuration->use_fastlane() ) { return; } diff --git a/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php b/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php index db4d5438e..92a76e101 100644 --- a/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php +++ b/modules/ppcp-axo-block/src/AxoBlockPaymentMethod.php @@ -16,7 +16,7 @@ use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; -use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration; /** * Class AxoBlockPaymentMethod @@ -61,9 +61,9 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType { /** * The DCC gateway settings. * - * @var DCCGatewayConfiguration + * @var CardPaymentsConfiguration */ - protected DCCGatewayConfiguration $dcc_configuration; + protected CardPaymentsConfiguration $dcc_configuration; /** * The environment object. @@ -101,7 +101,7 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType { * @param WC_Payment_Gateway $gateway Credit card gateway. * @param SmartButtonInterface|callable $smart_button The smart button script loading handler. * @param Settings $settings The settings. - * @param DCCGatewayConfiguration $dcc_configuration The DCC gateway settings. + * @param CardPaymentsConfiguration $dcc_configuration The DCC gateway settings. * @param Environment $environment The environment object. * @param string $wcgateway_module_url The WcGateway module URL. * @param array $payment_method_selected_map Mapping of payment methods to the PayPal Insights 'payment_method_selected' types. @@ -113,7 +113,7 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType { WC_Payment_Gateway $gateway, $smart_button, Settings $settings, - DCCGatewayConfiguration $dcc_configuration, + CardPaymentsConfiguration $dcc_configuration, Environment $environment, string $wcgateway_module_url, array $payment_method_selected_map, diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index 7db0b0dc5..91d8b49e8 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -17,7 +17,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; -use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration; use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter; return array( @@ -39,7 +39,10 @@ return array( }, 'axo.helpers.compatibility-checker' => static function ( ContainerInterface $container ) : CompatibilityChecker { - return new CompatibilityChecker( $container->get( 'axo.fastlane-incompatible-plugin-names' ) ); + return new CompatibilityChecker( + $container->get( 'axo.fastlane-incompatible-plugin-names' ), + $container->get( 'wcgateway.configuration.card-configuration' ) + ); }, // If AXO is configured and onboarded. @@ -80,7 +83,7 @@ return array( return new AxoGateway( $container->get( 'wcgateway.settings.render' ), $container->get( 'wcgateway.settings' ), - $container->get( 'wcgateway.configuration.dcc' ), + $container->get( 'wcgateway.configuration.card-configuration' ), $container->get( 'wcgateway.url' ), $container->get( 'session.handler' ), $container->get( 'wcgateway.order-processor' ), @@ -193,10 +196,7 @@ return array( $compatibility_checker = $container->get( 'axo.helpers.compatibility-checker' ); assert( $compatibility_checker instanceof CompatibilityChecker ); - $settings = $container->get( 'wcgateway.settings' ); - assert( $settings instanceof Settings ); - - return $compatibility_checker->generate_settings_conflict_notice( $settings ); + return $compatibility_checker->generate_settings_conflict_notice(); }, 'axo.checkout-config-notice' => static function ( ContainerInterface $container ) : string { @@ -221,16 +221,15 @@ return array( }, 'axo.incompatible-plugins-notice.raw' => static function ( ContainerInterface $container ) : string { - $settings_notice_generator = new CompatibilityChecker( - $container->get( 'axo.fastlane-incompatible-plugin-names' ) - ); + $settings_notice_generator = $container->get( 'axo.helpers.compatibility-checker' ); + assert( $settings_notice_generator instanceof CompatibilityChecker ); return $settings_notice_generator->generate_incompatible_plugins_notice( true ); }, 'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string { - $dcc_configuration = $container->get( 'wcgateway.configuration.dcc' ); - assert( $dcc_configuration instanceof DCCGatewayConfiguration ); + $dcc_configuration = $container->get( 'wcgateway.configuration.card-configuration' ); + assert( $dcc_configuration instanceof CardPaymentsConfiguration ); if ( $dcc_configuration->use_fastlane() ) { $fastlane_settings_url = admin_url( diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 5624273a3..f097882b9 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -30,7 +30,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener; use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; use WC_Payment_Gateways; -use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration; /** * Class AxoModule @@ -98,8 +98,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { return $methods; } - $dcc_configuration = $c->get( 'wcgateway.configuration.dcc' ); - assert( $dcc_configuration instanceof DCCGatewayConfiguration ); + $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' ); + assert( $dcc_configuration instanceof CardPaymentsConfiguration ); if ( ! $dcc_configuration->is_enabled() ) { return $methods; @@ -164,8 +164,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { $listener = $c->get( 'wcgateway.settings.listener' ); assert( $listener instanceof SettingsListener ); - $dcc_configuration = $c->get( 'wcgateway.configuration.dcc' ); - assert( $dcc_configuration instanceof DCCGatewayConfiguration ); + $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' ); + assert( $dcc_configuration instanceof CardPaymentsConfiguration ); $listener->filter_settings( $dcc_configuration->use_fastlane(), @@ -247,8 +247,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { add_filter( 'woocommerce_paypal_payments_sdk_components_hook', function( $components ) use ( $c ) { - $dcc_configuration = $c->get( 'wcgateway.configuration.dcc' ); - assert( $dcc_configuration instanceof DCCGatewayConfiguration ); + $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' ); + assert( $dcc_configuration instanceof CardPaymentsConfiguration ); if ( ! $dcc_configuration->use_fastlane() ) { return $components; @@ -262,8 +262,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { 'wp_head', function () use ( $c ) { // Add meta tag to allow feature-detection of the site's AXO payment state. - $dcc_configuration = $c->get( 'wcgateway.configuration.dcc' ); - assert( $dcc_configuration instanceof DCCGatewayConfiguration ); + $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' ); + assert( $dcc_configuration instanceof CardPaymentsConfiguration ); if ( $dcc_configuration->use_fastlane() ) { // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript @@ -403,8 +403,8 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { * @return bool */ private function should_render_fastlane( ContainerInterface $c ): bool { - $dcc_configuration = $c->get( 'wcgateway.configuration.dcc' ); - assert( $dcc_configuration instanceof DCCGatewayConfiguration ); + $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' ); + assert( $dcc_configuration instanceof CardPaymentsConfiguration ); $subscription_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscription_helper instanceof SubscriptionHelper ); diff --git a/modules/ppcp-axo/src/Gateway/AxoGateway.php b/modules/ppcp-axo/src/Gateway/AxoGateway.php index dda7c2182..285bc8626 100644 --- a/modules/ppcp-axo/src/Gateway/AxoGateway.php +++ b/modules/ppcp-axo/src/Gateway/AxoGateway.php @@ -29,7 +29,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; use WooCommerce\PayPalCommerce\WcGateway\Gateway\ProcessPaymentTrait; use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException; use WooCommerce\PayPalCommerce\Session\SessionHandler; -use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration; /** * Class AXOGateway. @@ -56,9 +56,9 @@ class AxoGateway extends WC_Payment_Gateway { /** * Gateway configuration object, providing relevant settings. * - * @var DCCGatewayConfiguration + * @var CardPaymentsConfiguration */ - protected DCCGatewayConfiguration $dcc_configuration; + protected CardPaymentsConfiguration $dcc_configuration; /** * The WcGateway module URL. @@ -133,24 +133,24 @@ class AxoGateway extends WC_Payment_Gateway { /** * AXOGateway constructor. * - * @param SettingsRenderer $settings_renderer The settings renderer. - * @param ContainerInterface $ppcp_settings The settings. - * @param DCCGatewayConfiguration $dcc_configuration The DCC Gateway configuration. - * @param string $wcgateway_module_url The WcGateway module URL. - * @param SessionHandler $session_handler The Session Handler. - * @param OrderProcessor $order_processor The Order processor. - * @param array $card_icons The card icons. - * @param OrderEndpoint $order_endpoint The order endpoint. - * @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. + * @param SettingsRenderer $settings_renderer The settings renderer. + * @param ContainerInterface $ppcp_settings The settings. + * @param CardPaymentsConfiguration $dcc_configuration The DCC Gateway configuration. + * @param string $wcgateway_module_url The WcGateway module URL. + * @param SessionHandler $session_handler The Session Handler. + * @param OrderProcessor $order_processor The Order processor. + * @param array $card_icons The card icons. + * @param OrderEndpoint $order_endpoint The order endpoint. + * @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory. * @param ShippingPreferenceFactory $shipping_preference_factory The shipping preference factory. - * @param TransactionUrlProvider $transaction_url_provider The transaction url provider. - * @param Environment $environment The environment. - * @param LoggerInterface $logger The logger. + * @param TransactionUrlProvider $transaction_url_provider The transaction url provider. + * @param Environment $environment The environment. + * @param LoggerInterface $logger The logger. */ public function __construct( SettingsRenderer $settings_renderer, ContainerInterface $ppcp_settings, - DCCGatewayConfiguration $dcc_configuration, + CardPaymentsConfiguration $dcc_configuration, string $wcgateway_module_url, SessionHandler $session_handler, OrderProcessor $order_processor, diff --git a/modules/ppcp-axo/src/Helper/CompatibilityChecker.php b/modules/ppcp-axo/src/Helper/CompatibilityChecker.php index f06b3e144..9d27781db 100644 --- a/modules/ppcp-axo/src/Helper/CompatibilityChecker.php +++ b/modules/ppcp-axo/src/Helper/CompatibilityChecker.php @@ -6,16 +6,19 @@ * @package WooCommerce\PayPalCommerce\Axo\Helper */ -declare(strict_types=1); +declare( strict_types = 1 ); namespace WooCommerce\PayPalCommerce\Axo\Helper; use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration; /** * Class CompatibilityChecker + * + * DI service: 'axo.helpers.compatibility-checker' */ class CompatibilityChecker { /** @@ -33,20 +36,24 @@ class CompatibilityChecker { protected array $checkout_compatibility; /** - * Stores whether DCC is enabled. + * Provides details about the DCC configuration. * - * @var bool|null + * @var CardPaymentsConfiguration */ - protected ?bool $is_dcc_enabled = null; + private CardPaymentsConfiguration $dcc_configuration; /** * CompatibilityChecker constructor. * - * @param string[] $incompatible_plugin_names The list of Fastlane incompatible plugin names. + * @param string[] $incompatible_plugin_names The list of Fastlane incompatible + * plugin names. + * @param CardPaymentsConfiguration $dcc_configuration DCC gateway configuration. */ - public function __construct( array $incompatible_plugin_names ) { + public function __construct( array $incompatible_plugin_names, CardPaymentsConfiguration $dcc_configuration ) { $this->incompatible_plugin_names = $incompatible_plugin_names; - $this->checkout_compatibility = array( + $this->dcc_configuration = $dcc_configuration; + + $this->checkout_compatibility = array( 'has_elementor_checkout' => null, 'has_classic_checkout' => null, 'has_block_checkout' => null, @@ -58,7 +65,7 @@ class CompatibilityChecker { * * @return bool Whether the checkout uses Elementor. */ - protected function has_elementor_checkout(): bool { + protected function has_elementor_checkout() : bool { if ( $this->checkout_compatibility['has_elementor_checkout'] === null ) { $this->checkout_compatibility['has_elementor_checkout'] = CartCheckoutDetector::has_elementor_checkout(); } @@ -71,7 +78,7 @@ class CompatibilityChecker { * * @return bool Whether the checkout uses classic checkout. */ - protected function has_classic_checkout(): bool { + protected function has_classic_checkout() : bool { if ( $this->checkout_compatibility['has_classic_checkout'] === null ) { $this->checkout_compatibility['has_classic_checkout'] = CartCheckoutDetector::has_classic_checkout(); } @@ -84,7 +91,7 @@ class CompatibilityChecker { * * @return bool Whether the checkout uses block checkout. */ - protected function has_block_checkout(): bool { + protected function has_block_checkout() : bool { if ( $this->checkout_compatibility['has_block_checkout'] === null ) { $this->checkout_compatibility['has_block_checkout'] = CartCheckoutDetector::has_block_checkout(); } @@ -92,29 +99,12 @@ class CompatibilityChecker { return $this->checkout_compatibility['has_block_checkout']; } - /** - * Checks if DCC is enabled. - * - * @param Settings $settings The plugin settings container. - * @return bool Whether DCC is enabled. - */ - protected function is_dcc_enabled( Settings $settings ): bool { - if ( $this->is_dcc_enabled === null ) { - try { - $this->is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' ); - } catch ( NotFoundException $ignored ) { - $this->is_dcc_enabled = false; - } - } - - return $this->is_dcc_enabled; - } - /** * Generates the full HTML of the notification. * - * @param string $message HTML of the inner message contents. - * @param bool $is_error Whether the provided message is an error. Affects the notice color. + * @param string $message HTML of the inner message contents. + * @param bool $is_error Whether the provided message is an error. Affects the notice + * color. * @param bool $raw_message Whether to return raw message without HTML wrappers. * * @return string The full HTML code of the notification, or an empty string, or raw message. @@ -136,11 +126,12 @@ class CompatibilityChecker { } /** - * Check if there aren't any incompatibilities that would prevent Fastlane from working properly. + * Check if there aren't any incompatibilities that would prevent Fastlane from working + * properly. * * @return bool Whether the setup is compatible. */ - public function is_fastlane_compatible(): bool { + public function is_fastlane_compatible() : bool { // Check for incompatible plugins. if ( ! empty( $this->incompatible_plugin_names ) ) { return false; @@ -165,7 +156,7 @@ class CompatibilityChecker { * @param bool $raw_message Whether to return raw message without HTML wrappers. * @return string */ - public function generate_checkout_notice( bool $raw_message = false ): string { + public function generate_checkout_notice( bool $raw_message = false ) : string { $notice_content = ''; // Check for checkout incompatibilities. @@ -213,7 +204,7 @@ class CompatibilityChecker { * @param bool $raw_message Whether to return raw message without HTML wrappers. * @return string */ - public function generate_incompatible_plugins_notice( bool $raw_message = false ): string { + public function generate_incompatible_plugins_notice( bool $raw_message = false ) : string { if ( empty( $this->incompatible_plugin_names ) ) { return ''; } @@ -235,12 +226,11 @@ class CompatibilityChecker { /** * Generates a warning notice with instructions on conflicting plugin-internal settings. * - * @param Settings $settings The plugin settings container, which is checked for conflicting values. - * @param bool $raw_message Whether to return raw message without HTML wrappers. + * @param bool $raw_message Whether to return raw message without HTML wrappers. * @return string */ - public function generate_settings_conflict_notice( Settings $settings, bool $raw_message = false ) : string { - if ( $this->is_dcc_enabled( $settings ) ) { + public function generate_settings_conflict_notice( bool $raw_message = false ) : string { + if ( $this->dcc_configuration->is_enabled() ) { return ''; } diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js index 96196d0cd..15511de69 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js @@ -56,9 +56,9 @@ class CardFieldsRenderer { onApprove( data ) { return contextConfig.onApprove( data ); }, - onError( error ) { - console.error( error ); - this.spinner.unblock(); + onError: ( error ) => { + console.error( error ); + this.spinner.unblock(); }, } ); @@ -105,11 +105,13 @@ class CardFieldsRenderer { } cardFields.submit().catch( ( error ) => { - this.spinner.unblock(); - console.error( error ); - this.errorHandler.message( - this.defaultConfig.hosted_fields.labels.fields_not_valid - ); + this.spinner.unblock(); + if (!error.type || error.type !== 'create-order-error') { + console.error( error ); + this.errorHandler.message( + this.defaultConfig.hosted_fields.labels.fields_not_valid + ); + } } ); } ); } diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index 005ee6248..ab9da6a0b 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -54,7 +54,7 @@ class Renderer { const enabledSeparateGateways = Object.fromEntries( Object.entries( settings.separate_buttons ).filter( - ( [ s, data ] ) => document.querySelector( data.wrapper ) + ( [ , data ] ) => document.querySelector( data.wrapper ) ) ); const hasEnabledSeparateGateways = @@ -65,15 +65,16 @@ class Renderer { this.renderButtons( settings.button.wrapper, settings.button.style, - contextConfig, - hasEnabledSeparateGateways + contextConfig ); } } else { + const allFundingSources = paypal.getFundingSources(); + const separateFunding = allFundingSources.filter( + ( s ) => ! ( s in enabledSeparateGateways ) + ); // render each button separately - for ( const fundingSource of paypal - .getFundingSources() - .filter( ( s ) => ! ( s in enabledSeparateGateways ) ) ) { + for ( const fundingSource of separateFunding ) { const style = normalizeStyleForFundingSource( settings.button.style, fundingSource @@ -83,7 +84,6 @@ class Renderer { settings.button.wrapper, style, contextConfig, - hasEnabledSeparateGateways, fundingSource ); } @@ -103,26 +103,15 @@ class Renderer { data.wrapper, data.style, contextConfig, - hasEnabledSeparateGateways, fundingSource ); } } - renderButtons( - wrapper, - style, - contextConfig, - hasEnabledSeparateGateways, - fundingSource = null - ) { + renderButtons( wrapper, style, contextConfig, fundingSource = null ) { if ( ! document.querySelector( wrapper ) || - this.isAlreadyRendered( - wrapper, - fundingSource, - hasEnabledSeparateGateways - ) + this.isAlreadyRendered( wrapper, fundingSource ) ) { // Try to render registered buttons again in case they were removed from the DOM by an external source. widgetBuilder.renderButtons( [ wrapper, fundingSource ] ); @@ -144,10 +133,7 @@ class Renderer { this.onSmartButtonClick( data, actions ); } - venmoButtonClicked = false; - if ( data.fundingSource === 'venmo' ) { - venmoButtonClicked = true; - } + venmoButtonClicked = data.fundingSource === 'venmo'; }, onInit: ( data, actions ) => { if ( this.onSmartButtonsInit ) { @@ -161,29 +147,29 @@ class Renderer { if ( this.shouldEnableShippingCallback() ) { options.onShippingOptionsChange = ( data, actions ) => { const shippingOptionsChange = - ! this.isVenmoButtonClickedWhenVaultingIsEnabled( - venmoButtonClicked - ) - ? handleShippingOptionsChange( - data, - actions, - this.defaultSettings - ) - : null; + ! this.isVenmoButtonClickedWhenVaultingIsEnabled( + venmoButtonClicked + ) + ? handleShippingOptionsChange( + data, + actions, + this.defaultSettings + ) + : null; return shippingOptionsChange; }; options.onShippingAddressChange = ( data, actions ) => { const shippingAddressChange = - ! this.isVenmoButtonClickedWhenVaultingIsEnabled( - venmoButtonClicked - ) - ? handleShippingAddressChange( - data, - actions, - this.defaultSettings - ) - : null; + ! this.isVenmoButtonClickedWhenVaultingIsEnabled( + venmoButtonClicked + ) + ? handleShippingAddressChange( + data, + actions, + this.defaultSettings + ) + : null; return shippingAddressChange; }; @@ -228,12 +214,11 @@ class Renderer { } ); - this.renderedSources.add( wrapper + ( fundingSource ?? '' ) ); + this.renderedSources.add( + wrapper + ( fundingSource ? fundingSource : '' ) + ); - if ( - typeof paypal !== 'undefined' && - typeof paypal.Buttons !== 'undefined' - ) { + if ( window.paypal?.Buttons ) { widgetBuilder.registerButtons( [ wrapper, fundingSource ], buttonsOptions() @@ -246,15 +231,16 @@ class Renderer { return venmoButtonClicked && this.defaultSettings.vaultingEnabled; }; - shouldEnableShippingCallback = () => { + shouldEnableShippingCallback = () => { const needShipping = this.defaultSettings.needShipping || this.defaultSettings.context === 'product'; + return ( this.defaultSettings.should_handle_shipping_in_paypal && needShipping ); - }; + }; isAlreadyRendered( wrapper, fundingSource ) { return this.renderedSources.has( wrapper + ( fundingSource ?? '' ) ); @@ -272,6 +258,7 @@ class Renderer { this.onButtonsInitListeners[ wrapper ] = reset ? [] : this.onButtonsInitListeners[ wrapper ] || []; + this.onButtonsInitListeners[ wrapper ].push( handler ); } @@ -283,12 +270,11 @@ class Renderer { if ( this.onButtonsInitListeners[ wrapper ] ) { for ( const handler of this.onButtonsInitListeners[ wrapper ] ) { - if ( typeof handler === 'function' ) { - handler( { - wrapper, - ...this.buttonsOptions[ wrapper ], - } ); + if ( typeof handler !== 'function' ) { + continue; } + + handler( { wrapper, ...this.buttonsOptions[ wrapper ] } ); } } } @@ -297,10 +283,11 @@ class Renderer { if ( ! this.buttonsOptions[ wrapper ] ) { return; } + try { this.buttonsOptions[ wrapper ].actions.disable(); } catch ( err ) { - console.log( 'Failed to disable buttons: ' + err ); + console.warn( 'Failed to disable buttons: ' + err ); } } @@ -308,10 +295,11 @@ class Renderer { if ( ! this.buttonsOptions[ wrapper ] ) { return; } + try { this.buttonsOptions[ wrapper ].actions.enable(); } catch ( err ) { - console.log( 'Failed to enable buttons: ' + err ); + console.warn( 'Failed to enable buttons: ' + err ); } } } diff --git a/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js b/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js index baa031ca6..13d915df2 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js @@ -40,8 +40,9 @@ class WidgetBuilder { renderButtons( wrapper ) { wrapper = this.sanitizeWrapper( wrapper ); + const entryKey = this.toKey( wrapper ); - if ( ! this.buttons.has( this.toKey( wrapper ) ) ) { + if ( ! this.buttons.has( entryKey ) ) { return; } @@ -49,11 +50,11 @@ class WidgetBuilder { return; } - const entry = this.buttons.get( this.toKey( wrapper ) ); + const entry = this.buttons.get( entryKey ); const btn = this.paypal.Buttons( entry.options ); if ( ! btn.isEligible() ) { - this.buttons.delete( this.toKey( wrapper ) ); + this.buttons.delete( entryKey ); return; } @@ -67,7 +68,7 @@ class WidgetBuilder { } renderAllButtons() { - for ( const [ wrapper, entry ] of this.buttons ) { + for ( const [ wrapper ] of this.buttons ) { this.renderButtons( wrapper ); } } diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php index 44e9a52ac..a11516b82 100644 --- a/modules/ppcp-button/services.php +++ b/modules/ppcp-button/services.php @@ -37,7 +37,7 @@ use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply; use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; -use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration; return array( 'button.client_id' => static function ( ContainerInterface $container ): string { @@ -115,8 +115,8 @@ return array( } $no_smart_buttons = ! $settings_status->is_smart_button_enabled_for_location( $context ); - $dcc_configuration = $container->get( 'wcgateway.configuration.dcc' ); - assert( $dcc_configuration instanceof DCCGatewayConfiguration ); + $dcc_configuration = $container->get( 'wcgateway.configuration.card-configuration' ); + assert( $dcc_configuration instanceof CardPaymentsConfiguration ); if ( $no_smart_buttons && ! $dcc_configuration->is_enabled() ) { // Smart buttons disabled, and also not using advanced card payments. @@ -167,7 +167,8 @@ return array( $container->get( 'api.endpoint.payment-tokens' ), $container->get( 'woocommerce.logger.woocommerce' ), $container->get( 'button.handle-shipping-in-paypal' ), - $container->get( 'button.helper.disabled-funding-sources' ) + $container->get( 'button.helper.disabled-funding-sources' ), + $container->get( 'wcgateway.configuration.card-configuration' ) ); }, 'button.url' => static function ( ContainerInterface $container ): string { @@ -343,7 +344,8 @@ return array( 'button.helper.disabled-funding-sources' => static function ( ContainerInterface $container ): DisabledFundingSources { return new DisabledFundingSources( $container->get( 'wcgateway.settings' ), - $container->get( 'wcgateway.all-funding-sources' ) + $container->get( 'wcgateway.all-funding-sources' ), + $container->get( 'wcgateway.configuration.card-configuration' ) ); }, 'button.is-logged-in' => static function ( ContainerInterface $container ): bool { diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 81ced3a68..477f73cb8 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -54,6 +54,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WC_Shipping_Method; use WC_Cart; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration; /** * Class SmartButton @@ -237,33 +238,41 @@ class SmartButton implements SmartButtonInterface { */ private $disabled_funding_sources; + /** + * Provides details about the DCC configuration. + * + * @var CardPaymentsConfiguration + */ + private CardPaymentsConfiguration $dcc_configuration; + /** * SmartButton constructor. * - * @param string $module_url The URL to the module. - * @param string $version The assets version. - * @param SessionHandler $session_handler The Session handler. - * @param Settings $settings The Settings. - * @param PayerFactory $payer_factory The Payer factory. - * @param string $client_id The client ID. - * @param RequestData $request_data The Request Data helper. - * @param DccApplies $dcc_applies The DCC applies helper. - * @param SubscriptionHelper $subscription_helper The subscription helper. - * @param MessagesApply $messages_apply The Messages apply helper. - * @param Environment $environment The environment object. - * @param PaymentTokenRepository $payment_token_repository The payment token repository. - * @param SettingsStatus $settings_status The Settings status helper. - * @param CurrencyGetter $currency The getter of the 3-letter currency code of the shop. - * @param array $all_funding_sources All existing funding sources. - * @param bool $basic_checkout_validation_enabled Whether the basic JS validation of the form iss enabled. - * @param bool $early_validation_enabled Whether to execute WC validation of the checkout form. - * @param array $pay_now_contexts The contexts that should have the Pay Now button. - * @param string[] $funding_sources_without_redirect The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back. - * @param bool $vault_v3_enabled Whether Vault v3 module is enabled. - * @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint. - * @param LoggerInterface $logger The logger. - * @param bool $should_handle_shipping_in_paypal Whether the shipping should be handled in PayPal. - * @param DisabledFundingSources $disabled_funding_sources List of funding sources to be disabled. + * @param string $module_url The URL to the module. + * @param string $version The assets version. + * @param SessionHandler $session_handler The Session handler. + * @param Settings $settings The Settings. + * @param PayerFactory $payer_factory The Payer factory. + * @param string $client_id The client ID. + * @param RequestData $request_data The Request Data helper. + * @param DccApplies $dcc_applies The DCC applies helper. + * @param SubscriptionHelper $subscription_helper The subscription helper. + * @param MessagesApply $messages_apply The Messages apply helper. + * @param Environment $environment The environment object. + * @param PaymentTokenRepository $payment_token_repository The payment token repository. + * @param SettingsStatus $settings_status The Settings status helper. + * @param CurrencyGetter $currency The getter of the 3-letter currency code of the shop. + * @param array $all_funding_sources All existing funding sources. + * @param bool $basic_checkout_validation_enabled Whether the basic JS validation of the form iss enabled. + * @param bool $early_validation_enabled Whether to execute WC validation of the checkout form. + * @param array $pay_now_contexts The contexts that should have the Pay Now button. + * @param string[] $funding_sources_without_redirect The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back. + * @param bool $vault_v3_enabled Whether Vault v3 module is enabled. + * @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint. + * @param LoggerInterface $logger The logger. + * @param bool $should_handle_shipping_in_paypal Whether the shipping should be handled in PayPal. + * @param DisabledFundingSources $disabled_funding_sources List of funding sources to be disabled. + * @param CardPaymentsConfiguration $dcc_configuration The DCC Gateway Configuration. */ public function __construct( string $module_url, @@ -289,9 +298,9 @@ class SmartButton implements SmartButtonInterface { PaymentTokensEndpoint $payment_tokens_endpoint, LoggerInterface $logger, bool $should_handle_shipping_in_paypal, - DisabledFundingSources $disabled_funding_sources + DisabledFundingSources $disabled_funding_sources, + CardPaymentsConfiguration $dcc_configuration ) { - $this->module_url = $module_url; $this->version = $version; $this->session_handler = $session_handler; @@ -316,6 +325,7 @@ class SmartButton implements SmartButtonInterface { $this->payment_tokens_endpoint = $payment_tokens_endpoint; $this->should_handle_shipping_in_paypal = $should_handle_shipping_in_paypal; $this->disabled_funding_sources = $disabled_funding_sources; + $this->dcc_configuration = $dcc_configuration; } /** @@ -331,76 +341,8 @@ class SmartButton implements SmartButtonInterface { $this->render_message_wrapper_registrar(); } - if ( - $this->settings->has( 'dcc_enabled' ) - && $this->settings->get( 'dcc_enabled' ) - ) { - add_action( - $this->checkout_dcc_button_renderer_hook(), - array( - $this, - 'dcc_renderer', - ), - 11 - ); - - add_action( - $this->pay_order_renderer_hook(), - array( - $this, - 'dcc_renderer', - ), - 11 - ); - - $subscription_helper = $this->subscription_helper; - add_filter( - 'woocommerce_credit_card_form_fields', - function ( array $default_fields, $id ) use ( $subscription_helper ) : array { - if ( - is_user_logged_in() - && $this->settings->has( 'vault_enabled_dcc' ) - && $this->settings->get( 'vault_enabled_dcc' ) - && CreditCardGateway::ID === $id - && apply_filters( 'woocommerce_paypal_payments_should_render_card_custom_fields', true ) - ) { - - $default_fields['card-vault'] = sprintf( - '
', - 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( - ''; - - $default_fields['saved-credit-card'] = $output; - } - } - - return $default_fields; - }, - 10, - 2 - ); + if ( $this->dcc_configuration->is_enabled() ) { + $this->render_dcc_wrapper(); } if ( $this->is_free_trial_cart() ) { @@ -439,6 +381,74 @@ class SmartButton implements SmartButtonInterface { return true; } + /** + * Registers hooks and callbacks that are only relevant for DCC (ACDC) payments. + * + * @return void + */ + private function render_dcc_wrapper(): void { + add_action( + $this->checkout_dcc_button_renderer_hook(), + array( $this, 'dcc_renderer' ), + 11 + ); + + add_action( + $this->pay_order_renderer_hook(), + array( $this, 'dcc_renderer' ), + 11 + ); + + $subscription_helper = $this->subscription_helper; + add_filter( + 'woocommerce_credit_card_form_fields', + function ( array $default_fields, $id ) use ( $subscription_helper ) : array { + if ( + CreditCardGateway::ID === $id + && is_user_logged_in() + && $this->settings->has( 'vault_enabled_dcc' ) + && $this->settings->get( 'vault_enabled_dcc' ) + && apply_filters( 'woocommerce_paypal_payments_should_render_card_custom_fields', true ) + ) { + + $default_fields['card-vault'] = sprintf( + '', + 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( + ''; + + $default_fields['saved-credit-card'] = $output; + } + } + + return $default_fields; + }, + 10, + 2 + ); + } + /** * Registers the hooks to render the credit messaging HTML depending on the settings. * @@ -719,9 +729,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages * Whether DCC fields can be rendered. */ public function can_render_dcc() : bool { - return $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) - && $this->settings->has( 'client_id' ) && $this->settings->get( 'client_id' ) - && $this->dcc_applies->for_country_currency() + return $this->dcc_configuration->is_acdc_enabled() && in_array( $this->context(), apply_filters( 'woocommerce_paypal_payments_can_render_dcc_contexts', array( 'checkout', 'pay-now', 'add-payment-method' ) ), @@ -1114,6 +1122,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages */ public function script_data(): array { $is_free_trial_cart = $this->is_free_trial_cart(); + $is_acdc_enabled = $this->dcc_configuration->is_acdc_enabled(); $url_params = $this->url_params(); @@ -1125,7 +1134,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages 'client_id' => $this->client_id, 'currency' => $this->currency->get(), 'data_client_id' => array( - 'set_attribute' => ( is_checkout() && $this->dcc_is_enabled() ) || $this->can_save_vault_token(), + 'set_attribute' => ( is_checkout() && $is_acdc_enabled ) || $this->can_save_vault_token(), 'endpoint' => \WC_AJAX::get_endpoint( DataClientIdEndpoint::ENDPOINT ), 'nonce' => wp_create_nonce( DataClientIdEndpoint::nonce() ), 'user' => get_current_user_id(), @@ -1455,20 +1464,6 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages $disabled_funding_sources[] = 'paylater'; } - $disabled_funding_sources = array_filter( - $disabled_funding_sources, - /** - * Make sure paypal is not sent in disable funding. - * - * @param string $funding_source The funding_source. - * - * @psalm-suppress MissingClosureParamType - */ - function( $funding_source ) { - return $funding_source !== 'paypal'; - } - ); - if ( count( $disabled_funding_sources ) > 0 ) { $params['disable-funding'] = implode( ',', array_unique( $disabled_funding_sources ) ); } @@ -1574,7 +1569,6 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages * The JS SKD components we need to load. * * @return array - * @throws NotFoundException If a setting was not found. */ private function components(): array { $components = array(); @@ -1586,9 +1580,12 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages if ( $this->should_load_messages() ) { $components[] = 'messages'; } - if ( $this->dcc_is_enabled() ) { + + // Card payments are only available on a checkout page. + if ( is_checkout() && $this->dcc_configuration->is_bcdc_enabled() ) { $components[] = 'hosted-fields'; } + /** * Filter to add further components from the extensions. * @@ -1606,31 +1603,6 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages ); } - /** - * Whether DCC is enabled or not. - * - * @return bool - */ - private function dcc_is_enabled(): bool { - if ( ! is_checkout() ) { - return false; - } - if ( ! $this->dcc_applies->for_country_currency() ) { - return false; - } - $keys = array( - 'client_id', - 'client_secret', - 'dcc_enabled', - ); - foreach ( $keys as $key ) { - if ( ! $this->settings->has( $key ) || ! $this->settings->get( $key ) ) { - return false; - } - } - return true; - } - /** * Determines the style for a given property in a given context. * diff --git a/modules/ppcp-button/src/Helper/DisabledFundingSources.php b/modules/ppcp-button/src/Helper/DisabledFundingSources.php index 1f5c87afc..66fb8fc07 100644 --- a/modules/ppcp-button/src/Helper/DisabledFundingSources.php +++ b/modules/ppcp-button/src/Helper/DisabledFundingSources.php @@ -5,14 +5,14 @@ * @package WooCommerce\PayPalCommerce\Button\Helper */ -declare(strict_types=1); +declare( strict_types = 1 ); namespace WooCommerce\PayPalCommerce\Button\Helper; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; -use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration; /** * Class DisabledFundingSources @@ -26,84 +26,187 @@ class DisabledFundingSources { * * @var Settings */ - private $settings; + private Settings $settings; /** * All existing funding sources. * * @var array */ - private $all_funding_sources; + private array $all_funding_sources; + + /** + * Provides details about the DCC configuration. + * + * @var CardPaymentsConfiguration + */ + private CardPaymentsConfiguration $dcc_configuration; /** * DisabledFundingSources constructor. * - * @param Settings $settings The settings. - * @param array $all_funding_sources All existing funding sources. + * @param Settings $settings The settings. + * @param array $all_funding_sources All existing funding sources. + * @param CardPaymentsConfiguration $dcc_configuration DCC gateway configuration. */ - public function __construct( Settings $settings, array $all_funding_sources ) { + public function __construct( Settings $settings, array $all_funding_sources, CardPaymentsConfiguration $dcc_configuration ) { $this->settings = $settings; $this->all_funding_sources = $all_funding_sources; + $this->dcc_configuration = $dcc_configuration; } /** * Returns the list of funding sources to be disabled. * * @param string $context The context. - * @return array|int[]|mixed|string[] - * @throws NotFoundException When the setting is not found. + * @return string[] List of disabled sources */ - public function sources( string $context ) { - $disable_funding = $this->settings->has( 'disable_funding' ) - ? $this->settings->get( 'disable_funding' ) - : array(); + public function sources( string $context ) : array { + $block_contexts = array( 'checkout-block', 'cart-block' ); + $flags = array( + 'context' => $context, + 'is_block_context' => in_array( $context, $block_contexts, true ), + 'is_free_trial' => $this->is_free_trial_cart(), + ); - $is_dcc_enabled = $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ); + // Free trials have a shorter, special funding-source rule. + if ( $flags['is_free_trial'] ) { + $disable_funding = $this->get_sources_for_free_trial(); - if ( - ! is_checkout() - || ( $is_dcc_enabled && in_array( $context, array( 'checkout-block', 'cart-block' ), true ) ) - ) { - $disable_funding[] = 'card'; + return $this->sanitize_and_filter_sources( $disable_funding, $flags ); } - $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); - $is_separate_card_enabled = isset( $available_gateways[ CardButtonGateway::ID ] ); + $disable_funding = $this->get_sources_from_settings(); - if ( - ( - is_checkout() - && ! in_array( $context, array( 'checkout-block', 'cart-block' ), true ) - ) - && ( - $is_dcc_enabled - || $is_separate_card_enabled - ) - ) { - $key = array_search( 'card', $disable_funding, true ); - if ( false !== $key ) { - unset( $disable_funding[ $key ] ); - } + // Apply rules based on context and payment methods. + $disable_funding = $this->apply_context_rules( $disable_funding ); + + // Apply special rules for block checkout. + if ( $flags['is_block_context'] ) { + $disable_funding = $this->apply_block_checkout_rules( $disable_funding ); } - if ( in_array( $context, array( 'checkout-block', 'cart-block' ), true ) ) { - $disable_funding = array_merge( + return $this->sanitize_and_filter_sources( $disable_funding, $flags ); + } + + /** + * Gets disabled funding sources from settings. + * + * @return array + */ + private function get_sources_from_settings() : array { + try { + // Settings field present in the legacy UI. + $disabled_funding = $this->settings->get( 'disable_funding' ); + } catch ( NotFoundException $exception ) { + $disabled_funding = array(); + } + + /** + * Filters the list of disabled funding methods. In the legacy UI, this + * list was accessible via a settings field. + * + * This filter allows merchants to programmatically disable funding sources + * in the new UI. + */ + return (array) apply_filters( + 'woocommerce_paypal_payments_disabled_funding', + $disabled_funding + ); + } + + /** + * Gets disabled funding sources for free trial carts. + * + * Rule: Carts that include a free trial product can ONLY use the + * funding source "card" - all other sources are disabled. + * + * @return array + */ + private function get_sources_for_free_trial() : array { + // Disable all sources. + $disable_funding = array_keys( $this->all_funding_sources ); + + if ( is_checkout() && $this->dcc_configuration->is_bcdc_enabled() ) { + // If BCDC is used, re-enable card payments. + $disable_funding = array_filter( $disable_funding, - array_diff( - array_keys( $this->all_funding_sources ), - array( 'venmo', 'paylater', 'paypal', 'card' ) - ) + static fn( string $funding_source ) => $funding_source !== 'card' ); } - if ( $this->is_free_trial_cart() ) { - $all_sources = array_keys( $this->all_funding_sources ); - if ( $is_dcc_enabled || $is_separate_card_enabled ) { - $all_sources = array_diff( $all_sources, array( 'card' ) ); - } - $disable_funding = $all_sources; + return $disable_funding; + } + + /** + * Applies rules based on context and payment methods. + * + * @param array $disable_funding The current disabled funding sources. + * @return array + */ + private function apply_context_rules( array $disable_funding ) : array { + if ( ! is_checkout() || $this->dcc_configuration->use_acdc() ) { + // Non-checkout pages, or ACDC capability: Don't load card button. + $disable_funding[] = 'card'; + + return $disable_funding; } - return apply_filters( 'woocommerce_paypal_payments_disabled_funding_sources', $disable_funding ); + return $disable_funding; + } + + /** + * Applies special rules for block checkout. + * + * @param array $disable_funding The current disabled funding sources. + * @return array + */ + private function apply_block_checkout_rules( array $disable_funding ) : array { + /** + * Block checkout only supports the following funding methods: + * - PayPal + * - PayLater + * - Venmo + * - ACDC ("card", conditionally) + */ + $allowed_in_blocks = array( 'venmo', 'paylater', 'paypal', 'card' ); + + return array_merge( + $disable_funding, + array_diff( array_keys( $this->all_funding_sources ), $allowed_in_blocks ) + ); + } + + /** + * Filters the disabled "funding-sources" list and returns a sanitized array. + * + * @param array $disable_funding The disabled funding sources. + * @param array $flags Decision flags. + * @return string[] + */ + private function sanitize_and_filter_sources( array $disable_funding, array $flags ) : array { + /** + * Filters the final list of disabled funding sources. + * + * @param array $diabled_funding The filter value, funding sources to be disabled. + * @param array $flags Decision flags to provide more context to filters. + */ + $disable_funding = apply_filters( + 'woocommerce_paypal_payments_sdk_disabled_funding_hook', + $disable_funding, + array( + 'context' => (string) ( $flags['context'] ?? '' ), + 'is_block_context' => (bool) ( $flags['is_block_context'] ?? false ), + 'is_free_trial' => (bool) ( $flags['is_free_trial'] ?? false ), + ) + ); + + // Make sure "paypal" is never disabled in the funding-sources. + $disable_funding = array_filter( + $disable_funding, + static fn( string $funding_source ) => $funding_source !== 'paypal' + ); + + return array_unique( $disable_funding ); } } diff --git a/modules/ppcp-card-fields/src/CardFieldsModule.php b/modules/ppcp-card-fields/src/CardFieldsModule.php index f87655f72..3d5f41ce1 100644 --- a/modules/ppcp-card-fields/src/CardFieldsModule.php +++ b/modules/ppcp-card-fields/src/CardFieldsModule.php @@ -16,7 +16,7 @@ use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; -use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration; /** * Class CardFieldsModule @@ -46,29 +46,51 @@ class CardFieldsModule implements ServiceModule, ExtendingModule, ExecutableModu return true; } - /** - * Param types removed to avoid third-party issues. - * - * @psalm-suppress MissingClosureParamType - */ add_filter( 'woocommerce_paypal_payments_sdk_components_hook', - function( $components ) use ( $c ) { - if ( ! $c->get( 'wcgateway.configuration.dcc' )->is_enabled() ) { + static function( array $components ) use ( $c ) { + $dcc_config = $c->get( 'wcgateway.configuration.card-configuration' ); + assert( $dcc_config instanceof CardPaymentsConfiguration ); + + if ( ! $dcc_config->is_acdc_enabled() ) { return $components; } - if ( in_array( 'hosted-fields', $components, true ) ) { - $key = array_search( 'hosted-fields', $components, true ); - if ( $key !== false ) { - unset( $components[ $key ] ); - } - } + + // Enable the new "card-fields" component. $components[] = 'card-fields'; - return $components; + // Ensure the older "hosted-fields" component is not loaded. + return array_filter( + $components, + static fn( string $component ) => $component !== 'hosted-fields' + ); } ); + add_filter( + 'woocommerce_paypal_payments_sdk_disabled_funding_hook', + static function ( array $disable_funding, array $flags ) use ( $c ) { + if ( true === $flags['is_block_context'] ) { + return $disable_funding; + } + + $dcc_config = $c->get( 'wcgateway.configuration.card-configuration' ); + assert( $dcc_config instanceof CardPaymentsConfiguration ); + + if ( ! $dcc_config->is_acdc_enabled() ) { + return $disable_funding; + } + + // For ACDC payments we need the funding source "card"! + return array_filter( + $disable_funding, + static fn( string $funding_source ) => $funding_source !== 'card' + ); + }, + 10, + 2 + ); + add_filter( 'woocommerce_credit_card_form_fields', /** @@ -78,7 +100,7 @@ class CardFieldsModule implements ServiceModule, ExtendingModule, ExecutableModu * @psalm-suppress MissingClosureParamType */ function( $default_fields, $id ) use ( $c ) { - if ( ! $c->get( 'wcgateway.configuration.dcc' )->is_enabled() ) { + if ( ! $c->get( 'wcgateway.configuration.card-configuration' )->is_enabled() ) { return $default_fields; } if ( CreditCardGateway::ID === $id && apply_filters( 'woocommerce_paypal_payments_enable_cardholder_name_field', false ) ) { @@ -113,7 +135,7 @@ class CardFieldsModule implements ServiceModule, ExtendingModule, ExecutableModu add_filter( 'ppcp_create_order_request_body_data', function( array $data, string $payment_method ) use ( $c ): array { - if ( ! $c->get( 'wcgateway.configuration.dcc' )->is_enabled() ) { + if ( ! $c->get( 'wcgateway.configuration.card-configuration' )->is_enabled() ) { return $data; } // phpcs:ignore WordPress.Security.NonceVerification.Missing diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index c8fdc9d8e..81b603feb 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -10,7 +10,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Compat; use Exception; -use Psr\Log\LoggerInterface; use WC_Cart; use WC_Order; use WC_Order_Item_Product; @@ -89,6 +88,8 @@ class CompatModule implements ServiceModule, ExtendingModule, ExecutableModule { add_action( 'woocommerce_paypal_payments_gateway_migrate', static fn() => delete_transient( 'ppcp_has_ppec_subscriptions' ) ); + $this->legacy_ui_card_payment_mapping( $c ); + return true; } @@ -491,4 +492,34 @@ class CompatModule implements ServiceModule, ExtendingModule, ExecutableModule { 2 ); } + + /** + * Responsible to keep the credit card payment configuration backwards + * compatible with the legacy UI. + * + * This method can be removed with the #legacy-ui code. + * + * @param ContainerInterface $container DI container instance. + * @return void + */ + protected function legacy_ui_card_payment_mapping( ContainerInterface $container ) : void { + $new_ui = $container->get( 'wcgateway.settings.admin-settings-enabled' ); + if ( $new_ui ) { + return; + } + + add_filter( + 'woocommerce_paypal_payments_is_acdc_active', + static function ( bool $is_acdc ) use ( $container ) : bool { + $settings = $container->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); + + try { + return (bool) $settings->get( 'dcc_enabled' ); + } catch ( NotFoundException $exception ) { + return $is_acdc; + } + } + ); + } } diff --git a/modules/ppcp-onboarding/services.php b/modules/ppcp-onboarding/services.php index 64d136092..8a87e4288 100644 --- a/modules/ppcp-onboarding/services.php +++ b/modules/ppcp-onboarding/services.php @@ -19,6 +19,8 @@ use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingSendOnlyNoticeRendere use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer; use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; +use WooCommerce\PayPalCommerce\WcGateway\Helper\ConnectionState; +use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings; return array( 'api.paypal-host' => function( ContainerInterface $container ) : string { @@ -48,25 +50,57 @@ return array( return new State( $settings ); }, /** - * Checks if the onboarding process is completed and the merchant API can be used. - * This service is overwritten by the ppcp-settings module, when it's active. + * Merchant connection details, which includes the connection status + * (onboarding/connected) and connection-aware environment checks. + * This is the preferred solution to check environment and connection state. */ - 'settings.flag.is-connected' => static function ( ContainerInterface $container ) : bool { + 'settings.connection-state' => static function ( ContainerInterface $container ) : ConnectionState { $state = $container->get( 'onboarding.state' ); assert( $state instanceof State ); - return $state->current_state() >= State::STATE_ONBOARDED; - }, - 'settings.flag.is-sandbox' => static function ( ContainerInterface $container ) : bool { $settings = $container->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); - return $settings->has( 'sandbox_on' ) && $settings->get( 'sandbox_on' ); + $is_sandbox = $settings->has( 'sandbox_on' ) && $settings->get( 'sandbox_on' ); + $is_connected = $state->current_state() >= State::STATE_ONBOARDED; + $environment = new Environment( $is_sandbox ); + + return new ConnectionState( $is_connected, $environment ); }, + /** + * Checks if the onboarding process is completed and the merchant API can be used. + * This service only resolves the connection status once per request. + * + * @deprecated Use 'settings.connection-state' instead. + */ + 'settings.flag.is-connected' => static function ( ContainerInterface $container ) : bool { + $state = $container->get( 'settings.connection-state' ); + assert( $state instanceof ConnectionState ); + + return $state->is_connected(); + }, + /** + * Determines whether the merchant is connected to a sandbox account. + * This service only resolves the sandbox flag once per request. + * + * @deprecated Use 'settings.connection-state' instead. + */ + 'settings.flag.is-sandbox' => static function ( ContainerInterface $container ) : bool { + $state = $container->get( 'settings.connection-state' ); + assert( $state instanceof ConnectionState ); + + return $state->is_sandbox(); + }, + /** + * Returns details about the connected environment (production/sandbox). + * + * @deprecated Directly use 'settings.connection-state' instead of this. + */ 'settings.environment' => function ( ContainerInterface $container ) : Environment { - return new Environment( - $container->get( 'settings.flag.is-sandbox' ) - ); + $state = $container->get( 'settings.connection-state' ); + assert( $state instanceof ConnectionState ); + + return $state->get_environment(); }, 'onboarding.assets' => function( ContainerInterface $container ) : OnboardingAssets { diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js index 1b06eb222..ad72cbfe1 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Navigation.js @@ -16,9 +16,9 @@ const NOTIFICATION_ANIMATION_DURATION = 300; const SettingsNavigation = ( { canSave = true, - tabs, - activePanel, - setActivePanel, + tabs = [], + activePanel = '', + setActivePanel = () => {}, } ) => { const { persistAll } = useStoreManager(); diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Overview/Features/FeatureItem.js b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Overview/Features/FeatureItem.js index 4f3e375d3..85fe38f7c 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Overview/Features/FeatureItem.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Settings/Components/Overview/Features/FeatureItem.js @@ -32,7 +32,10 @@ const FeatureItem = ( { ); const handleClick = async ( feature ) => { if ( feature.action?.type === 'tab' ) { - const highlight = Boolean( feature.action?.highlight ); + const highlight = + feature.action?.highlight === undefined + ? true + : Boolean( feature.action.highlight ); const tabId = TAB_IDS[ feature.action.tab.toUpperCase() ]; await selectTab( tabId, feature.action.section, highlight ); } diff --git a/modules/ppcp-settings/resources/js/data/common/hooks.js b/modules/ppcp-settings/resources/js/data/common/hooks.js index 3f5b06fba..081db9c85 100644 --- a/modules/ppcp-settings/resources/js/data/common/hooks.js +++ b/modules/ppcp-settings/resources/js/data/common/hooks.js @@ -212,6 +212,7 @@ export const useMerchant = () => { clientSecret: merchant.clientSecret ?? '', isBusinessSeller: 'business' === merchant.sellerType, isCasualSeller: 'personal' === merchant.sellerType, + isSendOnlyCountry: merchant.isSendOnlyCountry ?? false, } ), // the merchant object is stable, so a new memo is only generated when a merchant prop changes. [ merchant ] diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php index 98d51ce1d..a920970c3 100644 --- a/modules/ppcp-settings/services.php +++ b/modules/ppcp-settings/services.php @@ -153,34 +153,36 @@ return array( return new ConnectionState( $is_connected, $environment ); }, + /** + * Returns details about the connected environment (production/sandbox). + * + * @deprecated Directly use 'settings.connection-state' instead of this. + */ 'settings.environment' => static function ( ContainerInterface $container ) : Environment { - // We should remove this service in favor of directly using `settings.connection-state`. $state = $container->get( 'settings.connection-state' ); assert( $state instanceof ConnectionState ); return $state->get_environment(); }, /** - * Checks if valid merchant connection details are stored in the DB. + * Checks if the onboarding process is completed and the merchant API can be used. + * This service only resolves the connection status once per request. + * + * @deprecated Use 'settings.connection-state' instead. */ 'settings.flag.is-connected' => static function ( ContainerInterface $container ) : bool { - /* - * This service only resolves the connection status once per request. - * We should remove this service in favor of directly using `settings.connection-state`. - */ $state = $container->get( 'settings.connection-state' ); assert( $state instanceof ConnectionState ); return $state->is_connected(); }, /** - * Checks if the merchant is connected to a sandbox environment. + * Determines whether the merchant is connected to a sandbox account. + * This service only resolves the sandbox flag once per request. + * + * @deprecated Use 'settings.connection-state' instead. */ 'settings.flag.is-sandbox' => static function ( ContainerInterface $container ) : bool { - /* - * This service only resolves the sandbox flag once per request. - * We should remove this service in favor of directly using `settings.connection-state`. - */ $state = $container->get( 'settings.connection-state' ); assert( $state instanceof ConnectionState ); diff --git a/modules/ppcp-settings/src/Data/Definition/FeaturesDefinition.php b/modules/ppcp-settings/src/Data/Definition/FeaturesDefinition.php index 1cc635422..0930ece52 100644 --- a/modules/ppcp-settings/src/Data/Definition/FeaturesDefinition.php +++ b/modules/ppcp-settings/src/Data/Definition/FeaturesDefinition.php @@ -150,11 +150,10 @@ class FeaturesDefinition { 'type' => 'secondary', 'text' => __( 'Configure', 'woocommerce-paypal-payments' ), 'action' => array( - 'type' => 'tab', - 'tab' => 'payment_methods', - 'section' => 'ppcp-credit-card-gateway', - 'highlight' => 'ppcp-credit-card-gateway', - 'modal' => 'ppcp-credit-card-gateway', + 'type' => 'tab', + 'tab' => 'payment_methods', + 'section' => 'ppcp-credit-card-gateway', + 'modal' => 'ppcp-credit-card-gateway', ), 'showWhen' => 'enabled', 'class' => 'small-button', @@ -189,7 +188,7 @@ class FeaturesDefinition { 'type' => 'tab', 'tab' => 'payment_methods', 'section' => 'ppcp-alternative-payments-card', - 'highlight' => 'ppcp-alternative-payments-card', + 'highlight' => false, ), 'showWhen' => 'enabled', 'class' => 'small-button', @@ -218,11 +217,10 @@ class FeaturesDefinition { 'type' => 'secondary', 'text' => __( 'Configure', 'woocommerce-paypal-payments' ), 'action' => array( - 'type' => 'tab', - 'tab' => 'payment_methods', - 'section' => 'ppcp-googlepay', - 'highlight' => 'ppcp-googlepay', - 'modal' => 'ppcp-googlepay', + 'type' => 'tab', + 'tab' => 'payment_methods', + 'section' => 'ppcp-googlepay', + 'modal' => 'ppcp-googlepay', ), 'showWhen' => 'enabled', 'class' => 'small-button', @@ -257,11 +255,10 @@ class FeaturesDefinition { 'type' => 'secondary', 'text' => __( 'Configure', 'woocommerce-paypal-payments' ), 'action' => array( - 'type' => 'tab', - 'tab' => 'payment_methods', - 'section' => 'ppcp-card-payments-card', - 'highlight' => 'ppcp-applepay', - 'modal' => 'ppcp-applepay', + 'type' => 'tab', + 'tab' => 'payment_methods', + 'section' => 'ppcp-applepay', + 'modal' => 'ppcp-applepay', ), 'showWhen' => 'enabled', 'class' => 'small-button', diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php index 46eca79dd..d24f02422 100644 --- a/modules/ppcp-settings/src/SettingsModule.php +++ b/modules/ppcp-settings/src/SettingsModule.php @@ -57,7 +57,7 @@ class SettingsModule implements ServiceModule, ExecutableModule { * Returns whether the old settings UI should be loaded. */ public static function should_use_the_old_ui() : bool { - // New merchants should never see the legacy UI. + // New merchants should never see the #legacy-ui. $show_new_ux = '1' === get_option( 'woocommerce-ppcp-is-new-merchant' ); if ( $show_new_ux ) { @@ -394,7 +394,7 @@ class SettingsModule implements ServiceModule, ExecutableModule { unset( $payment_methods[ OXXO::ID ] ); } - // Unset Pay Unon Invoice if merchant country is not Germany. + // Unset Pay Upon Invoice if merchant country is not Germany. if ( 'DE' !== $merchant_country ) { unset( $payment_methods[ PayUponInvoiceGateway::ID ] ); } @@ -577,35 +577,6 @@ class SettingsModule implements ServiceModule, ExecutableModule { ); } - /** - * Unsets the BCDC black button if merchant is eligible for ACDC. - */ - add_filter( - 'woocommerce_paypal_payments_disabled_funding_sources', - /** - * Unsets the BCDC black button if merchant is eligible for ACDC. - * - * @param int[]|string[]|mixed $disable_funding The disabled funding sources. - * @return int[]|string[]|mixed The disabled funding sources. - * - * @psalm-suppress MissingClosureParamType - */ - static function ( $disable_funding ) use ( $container ) { - if ( ! is_array( $disable_funding ) || in_array( 'card', $disable_funding, true ) ) { - return $disable_funding; - } - - $dcc_product_status = $container->get( 'wcgateway.helper.dcc-product-status' ); - assert( $dcc_product_status instanceof DCCProductStatus ); - - if ( $dcc_product_status->is_active() ) { - $disable_funding[] = 'card'; - } - - return $disable_funding; - } - ); - // Enable Fastlane after onboarding if the store is compatible. add_action( 'woocommerce_paypal_payments_apply_default_configuration', diff --git a/modules/ppcp-status-report/src/StatusReportModule.php b/modules/ppcp-status-report/src/StatusReportModule.php index 2baa411eb..10159f8c7 100644 --- a/modules/ppcp-status-report/src/StatusReportModule.php +++ b/modules/ppcp-status-report/src/StatusReportModule.php @@ -94,6 +94,17 @@ class StatusReportModule implements ServiceModule, ExtendingModule, ExecutableMo $this->onboarded( $bearer, $is_connected ) ), ), + array( + 'label' => esc_html__( 'New UI active', 'woocommerce-paypal-payments' ), + 'exported_label' => 'New UI active', + 'description' => esc_html__( 'Indicates whether the new Settings UI is enabled.', 'woocommerce-paypal-payments' ), + 'value' => $this->bool_to_html( + apply_filters( + 'woocommerce.feature-flags.woocommerce_paypal_payments.settings_enabled', + '1' === get_option( 'woocommerce-ppcp-is-new-merchant' ) || getenv( 'PCP_SETTINGS_ENABLED' ) === '1' + ) + ), + ), array( 'label' => esc_html__( 'Shop country code', 'woocommerce-paypal-payments' ), 'exported_label' => 'Shop country code', diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 9bb5ae0c9..e8d5238ab 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -83,7 +83,8 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; use WooCommerce\PayPalCommerce\Axo\Helper\PropertiesDictionary; use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway; -use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration; +use WooCommerce\PayPalCommerce\WcGateway\Helper\ConnectionState; return array( 'wcgateway.paypal-gateway' => static function ( ContainerInterface $container ): PayPalGateway { @@ -117,7 +118,7 @@ return array( $container->get( 'wcgateway.settings.render' ), $container->get( 'wcgateway.order-processor' ), $container->get( 'wcgateway.settings' ), - $container->get( 'wcgateway.configuration.dcc' ), + $container->get( 'wcgateway.configuration.card-configuration' ), $container->get( 'wcgateway.credit-card-icons' ), $container->get( 'wcgateway.url' ), $container->get( 'session.handler' ), @@ -333,7 +334,8 @@ return array( $container->get( 'settings.flag.is-connected' ), $container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.is-wc-payments-page' ), - $container->get( 'wcgateway.is-ppcp-settings-page' ) + $container->get( 'wcgateway.is-ppcp-settings-page' ), + $container->get( 'wcgateway.configuration.card-configuration' ) ); }, 'wcgateway.notice.card-button-without-paypal' => static function ( ContainerInterface $container ): GatewayWithoutPayPalAdminNotice { @@ -343,6 +345,7 @@ return array( $container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.is-wc-payments-page' ), $container->get( 'wcgateway.is-ppcp-settings-page' ), + $container->get( 'wcgateway.configuration.card-configuration' ), $container->get( 'wcgateway.settings.status' ) ); }, @@ -461,8 +464,8 @@ return array( assert( $settings instanceof Settings ); $axo_available = $container->has( 'axo.available' ) && $container->get( 'axo.available' ); - $dcc_configuration = $container->get( 'wcgateway.configuration.dcc' ); - assert( $dcc_configuration instanceof DCCGatewayConfiguration ); + $dcc_configuration = $container->get( 'wcgateway.configuration.card-configuration' ); + assert( $dcc_configuration instanceof CardPaymentsConfiguration ); if ( $axo_available && $dcc_configuration->use_fastlane() ) { return ''; @@ -684,8 +687,8 @@ return array( $subscription_helper = $container->get( 'wc-subscriptions.helper' ); assert( $subscription_helper instanceof SubscriptionHelper ); - $dcc_configuration = $container->get( 'wcgateway.configuration.dcc' ); - assert( $dcc_configuration instanceof DCCGatewayConfiguration ); + $dcc_configuration = $container->get( 'wcgateway.configuration.card-configuration' ); + assert( $dcc_configuration instanceof CardPaymentsConfiguration ); $fields = array( 'checkout_settings_heading' => array( @@ -1359,11 +1362,12 @@ return array( return new TransactionUrlProvider( $sandbox_url_base, $live_url_base ); }, - 'wcgateway.configuration.dcc' => static function ( ContainerInterface $container ) : DCCGatewayConfiguration { - $settings = $container->get( 'wcgateway.settings' ); - assert( $settings instanceof Settings ); - - return new DCCGatewayConfiguration( $settings ); + 'wcgateway.configuration.card-configuration' => static function ( ContainerInterface $container ) : CardPaymentsConfiguration { + return new CardPaymentsConfiguration( + $container->get( 'settings.connection-state' ), + $container->get( 'wcgateway.settings' ), + $container->get( 'api.helpers.dccapplies' ) + ); }, 'wcgateway.helper.dcc-product-status' => static function ( ContainerInterface $container ) : DCCProductStatus { diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index 872f9f240..9e91808ae 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -34,7 +34,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; -use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration; /** * Class CreditCardGateway @@ -76,9 +76,9 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { /** * The DCC Gateway Configuration. * - * @var DCCGatewayConfiguration + * @var CardPaymentsConfiguration */ - protected DCCGatewayConfiguration $dcc_configuration; + protected CardPaymentsConfiguration $dcc_configuration; /** * The vaulted credit card handler. @@ -188,31 +188,31 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { /** * CreditCardGateway constructor. * - * @param SettingsRenderer $settings_renderer The Settings Renderer. - * @param OrderProcessor $order_processor The Order processor. - * @param ContainerInterface $config The settings. - * @param DCCGatewayConfiguration $dcc_configuration The DCC Gateway Configuration. - * @param array $card_icons The card icons. - * @param string $module_url The URL to the module. - * @param SessionHandler $session_handler The Session Handler. - * @param RefundProcessor $refund_processor The refund processor. - * @param TransactionUrlProvider $transaction_url_provider Service able to provide view transaction url base. - * @param SubscriptionHelper $subscription_helper The subscription helper. - * @param PaymentsEndpoint $payments_endpoint The payments endpoint. - * @param VaultedCreditCardHandler $vaulted_credit_card_handler The vaulted credit card handler. - * @param Environment $environment The environment. - * @param OrderEndpoint $order_endpoint The order endpoint. - * @param CaptureCardPayment $capture_card_payment Capture card payment. - * @param string $prefix The prefix. - * @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint. - * @param WooCommercePaymentTokens $wc_payment_tokens WooCommerce payment tokens factory. - * @param LoggerInterface $logger The logger. + * @param SettingsRenderer $settings_renderer The Settings Renderer. + * @param OrderProcessor $order_processor The Order processor. + * @param ContainerInterface $config The settings. + * @param CardPaymentsConfiguration $dcc_configuration The DCC Gateway Configuration. + * @param array $card_icons The card icons. + * @param string $module_url The URL to the module. + * @param SessionHandler $session_handler The Session Handler. + * @param RefundProcessor $refund_processor The refund processor. + * @param TransactionUrlProvider $transaction_url_provider Service able to provide view transaction url base. + * @param SubscriptionHelper $subscription_helper The subscription helper. + * @param PaymentsEndpoint $payments_endpoint The payments endpoint. + * @param VaultedCreditCardHandler $vaulted_credit_card_handler The vaulted credit card handler. + * @param Environment $environment The environment. + * @param OrderEndpoint $order_endpoint The order endpoint. + * @param CaptureCardPayment $capture_card_payment Capture card payment. + * @param string $prefix The prefix. + * @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint. + * @param WooCommercePaymentTokens $wc_payment_tokens WooCommerce payment tokens factory. + * @param LoggerInterface $logger The logger. */ public function __construct( SettingsRenderer $settings_renderer, OrderProcessor $order_processor, ContainerInterface $config, - DCCGatewayConfiguration $dcc_configuration, + CardPaymentsConfiguration $dcc_configuration, array $card_icons, string $module_url, SessionHandler $session_handler, @@ -419,6 +419,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { foreach ( $tokens as $token ) { if ( $token->get_id() === (int) $card_payment_token_for_free_trial ) { $wc_order->payment_complete(); + $wc_order->add_payment_token( $token ); return $this->handle_payment_success( $wc_order ); } } @@ -464,6 +465,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { $order = $this->order_endpoint->order( $create_order->id ); $wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() ); + $wc_order->add_payment_token( $token ); if ( $order->intent() === 'AUTHORIZE' ) { $order = $this->order_endpoint->authorize( $order ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php index 10ec5ab2c..7ed9532e8 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php @@ -137,10 +137,6 @@ class PayUponInvoice { */ public function init(): void { if ( $this->pui_helper->is_pui_gateway_enabled() ) { - /* - * TODO new-ux: Check if we still support this setting, or if it's always enabled. - * If fraudnet is not configurable in new UI, we can ignore this. - */ $this->settings->set( 'fraudnet_enabled', true ); $this->settings->persist(); } diff --git a/modules/ppcp-wc-gateway/src/Helper/CardPaymentsConfiguration.php b/modules/ppcp-wc-gateway/src/Helper/CardPaymentsConfiguration.php new file mode 100644 index 000000000..8f1420428 --- /dev/null +++ b/modules/ppcp-wc-gateway/src/Helper/CardPaymentsConfiguration.php @@ -0,0 +1,386 @@ +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 PayPal’s 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; + } +} diff --git a/modules/ppcp-wc-gateway/src/Helper/ConnectionState.php b/modules/ppcp-wc-gateway/src/Helper/ConnectionState.php index 3ee969a84..0950af29a 100644 --- a/modules/ppcp-wc-gateway/src/Helper/ConnectionState.php +++ b/modules/ppcp-wc-gateway/src/Helper/ConnectionState.php @@ -14,6 +14,8 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Helper; * * Manages the merchants' connection status details and provides inspection * methods to describe the current state. + * + * DI service: 'settings.connection-state' */ class ConnectionState { /** diff --git a/modules/ppcp-wc-gateway/src/Helper/DCCGatewayConfiguration.php b/modules/ppcp-wc-gateway/src/Helper/DCCGatewayConfiguration.php deleted file mode 100644 index 19c31f56d..000000000 --- a/modules/ppcp-wc-gateway/src/Helper/DCCGatewayConfiguration.php +++ /dev/null @@ -1,215 +0,0 @@ -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 PayPal’s 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; - } -} diff --git a/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php b/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php index 627d23e97..61f9723f1 100644 --- a/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php +++ b/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php @@ -13,6 +13,7 @@ use WC_Payment_Gateway; use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration; /** * Creates the admin message about the gateway being enabled without the PayPal gateway. @@ -65,15 +66,23 @@ class GatewayWithoutPayPalAdminNotice { */ protected $settings_status; + /** + * Provides details about the DCC configuration. + * + * @var CardPaymentsConfiguration + */ + private CardPaymentsConfiguration $dcc_configuration; + /** * ConnectAdminNotice constructor. * - * @param string $id The gateway ID. - * @param bool $is_connected Whether onboading was completed. - * @param ContainerInterface $settings The settings. - * @param bool $is_payments_page Whether the current page is the WC payment page. - * @param bool $is_ppcp_settings_page Whether the current page is the PPCP settings page. - * @param SettingsStatus|null $settings_status The Settings status helper. + * @param string $id The gateway ID. + * @param bool $is_connected Whether onboarding was completed. + * @param ContainerInterface $settings The settings. + * @param bool $is_payments_page Whether the current page is the WC payment page. + * @param bool $is_ppcp_settings_page Whether the current page is the PPCP settings page. + * @param CardPaymentsConfiguration $dcc_configuration DCC gateway configuration. + * @param SettingsStatus|null $settings_status The Settings status helper. */ public function __construct( string $id, @@ -81,6 +90,7 @@ class GatewayWithoutPayPalAdminNotice { ContainerInterface $settings, bool $is_payments_page, bool $is_ppcp_settings_page, + CardPaymentsConfiguration $dcc_configuration, ?SettingsStatus $settings_status = null ) { $this->id = $id; @@ -88,6 +98,7 @@ class GatewayWithoutPayPalAdminNotice { $this->settings = $settings; $this->is_payments_page = $is_payments_page; $this->is_ppcp_settings_page = $is_ppcp_settings_page; + $this->dcc_configuration = $dcc_configuration; $this->settings_status = $settings_status; } @@ -181,7 +192,7 @@ class GatewayWithoutPayPalAdminNotice { return self::NOTICE_DISABLED_LOCATION; } - $is_dcc_enabled = $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) ?? false; + $is_dcc_enabled = $this->dcc_configuration->is_enabled(); $is_card_button_allowed = $this->settings->has( 'allow_card_button_gateway' ) && $this->settings->get( 'allow_card_button_gateway' ); if ( $is_dcc_enabled && $is_card_button_allowed ) { diff --git a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php index b612de369..9eb5990c2 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php +++ b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php @@ -263,7 +263,7 @@ class SettingsListener { // phpcs:enable WordPress.Security.NonceVerification.Missing // phpcs:enable WordPress.Security.NonceVerification.Recommended - // This method is only used for legacy UI, `settings->set` is valid here. + // This method is only used for #legacy-ui, `settings->set` is valid here. $this->settings->set( 'merchant_id', $merchant_id ); $this->settings->set( 'merchant_email', $merchant_email ); @@ -367,7 +367,7 @@ class SettingsListener { return; } - // This method is only used for legacy UI, `settings->set` is valid here. + // This method is only used for #legacy-ui, `settings->set` is valid here. try { $token = $this->bearer->bearer(); @@ -777,7 +777,7 @@ class SettingsListener { return; } - // This method is only used for legacy UI, `settings->set` is valid here. + // This method is only used for #legacy-ui, `settings->set` is valid here. $existing_setting_value = $this->settings->has( $setting_slug ) ? $this->settings->get( $setting_slug ) : null; diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 2f82c5636..828ee496e 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -58,7 +58,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Registrar\TaskRegistrarInterface; -use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCGatewayConfiguration; +use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration; use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\LocalApmProductStatus; /** @@ -195,8 +195,8 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); - $dcc_configuration = $c->get( 'wcgateway.configuration.dcc' ); - assert( $dcc_configuration instanceof DCCGatewayConfiguration ); + $dcc_configuration = $c->get( 'wcgateway.configuration.card-configuration' ); + assert( $dcc_configuration instanceof CardPaymentsConfiguration ); $assets = new SettingsPageAssets( $c->get( 'wcgateway.url' ), @@ -458,9 +458,14 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul ); if ( defined( 'WP_CLI' ) && WP_CLI ) { - \WP_CLI::add_command( - 'pcp settings', - $c->get( 'wcgateway.cli.settings.command' ) + add_action( + 'init', + function() use ( $c ) { + \WP_CLI::add_command( + 'pcp settings', + $c->get( 'wcgateway.cli.settings.command' ) + ); + } ); } @@ -614,8 +619,8 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul return $methods; } - $dcc_configuration = $container->get( 'wcgateway.configuration.dcc' ); - assert( $dcc_configuration instanceof DCCGatewayConfiguration ); + $dcc_configuration = $container->get( 'wcgateway.configuration.card-configuration' ); + assert( $dcc_configuration instanceof CardPaymentsConfiguration ); $standard_card_button = get_option( 'woocommerce_ppcp-card-button-gateway_settings' ); diff --git a/modules/ppcp-wc-subscriptions/src/Helper/SubscriptionHelper.php b/modules/ppcp-wc-subscriptions/src/Helper/SubscriptionHelper.php index 8bc1e40f5..7ec997d55 100644 --- a/modules/ppcp-wc-subscriptions/src/Helper/SubscriptionHelper.php +++ b/modules/ppcp-wc-subscriptions/src/Helper/SubscriptionHelper.php @@ -19,8 +19,6 @@ use WC_Subscriptions; use WC_Subscriptions_Product; use WCS_Manual_Renewal_Manager; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; -use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; -use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WP_Query; /** @@ -288,30 +286,36 @@ class SubscriptionHelper { * Returns previous order transaction from the given subscription. * * @param WC_Subscription $subscription WooCommerce Subscription. + * @param string $vault_token_id Vault token id. * @return string */ - public function previous_transaction( WC_Subscription $subscription ): string { + public function previous_transaction( WC_Subscription $subscription, string $vault_token_id ): string { $orders = $subscription->get_related_orders( 'ids', array( 'parent', 'renewal' ) ); - if ( ! $orders ) { + if ( ! $orders || ! $vault_token_id ) { return ''; } - // Sort orders by key descending. - krsort( $orders ); - - // Removes first order (the current processing order). - unset( $orders[ array_key_first( $orders ) ] ); + // Sort orders by oder ID descending. + rsort( $orders ); + $current_order = wc_get_order( array_shift( $orders ) ); + if ( ! $current_order instanceof WC_Order ) { + return ''; + } foreach ( $orders as $order_id ) { $order = wc_get_order( $order_id ); if ( is_a( $order, WC_Order::class ) && in_array( $order->get_status(), array( 'processing', 'completed' ), true ) - && in_array( $order->get_payment_method(), array( PayPalGateway::ID, CreditCardGateway::ID ), true ) + && $current_order->get_payment_method() === $order->get_payment_method() ) { $transaction_id = $order->get_transaction_id(); - if ( $transaction_id ) { - return $transaction_id; + $tokens = $order->get_payment_tokens(); + foreach ( $tokens as $token ) { + $wc_token = \WC_Payment_Tokens::get( $token ); + if ( $transaction_id && $wc_token instanceof \WC_Payment_Token && $wc_token->get_token() === $vault_token_id ) { + return $transaction_id; + } } } } diff --git a/modules/ppcp-wc-subscriptions/src/RenewalHandler.php b/modules/ppcp-wc-subscriptions/src/RenewalHandler.php index f0900881d..eea52071b 100644 --- a/modules/ppcp-wc-subscriptions/src/RenewalHandler.php +++ b/modules/ppcp-wc-subscriptions/src/RenewalHandler.php @@ -335,6 +335,7 @@ class RenewalHandler { $last_token = end( $wc_tokens ); if ( $last_token ) { $payment_source = $this->card_payment_source( $last_token->get_token(), $wc_order ); + $wc_order->add_payment_token( $last_token ); } } @@ -542,20 +543,20 @@ class RenewalHandler { */ private function card_payment_source( string $token, WC_Order $wc_order ): PaymentSource { $properties = array( - 'vault_id' => $token, + 'vault_id' => $token, + 'stored_credential' => array( + 'payment_initiator' => 'MERCHANT', + 'payment_type' => 'RECURRING', + 'usage' => 'SUBSEQUENT', + ), ); $subscriptions = wcs_get_subscriptions_for_renewal_order( $wc_order ); $subscription = end( $subscriptions ); if ( $subscription ) { - $transaction = $this->subscription_helper->previous_transaction( $subscription ); + $transaction = $this->subscription_helper->previous_transaction( $subscription, $token ); if ( $transaction ) { - $properties['stored_credential'] = array( - 'payment_initiator' => 'MERCHANT', - 'payment_type' => 'RECURRING', - 'usage' => 'SUBSEQUENT', - 'previous_transaction_reference' => $transaction, - ); + $properties['stored_credential']['previous_transaction_reference'] = $transaction; } } diff --git a/scoper.inc.php b/scoper.inc.php index dd73d2b36..66ae7017c 100644 --- a/scoper.inc.php +++ b/scoper.inc.php @@ -32,20 +32,20 @@ return array( 'prefix' => 'WooCommerce\\PayPalCommerce\\Vendor', 'finders' => $finders, 'patchers' => array(), - 'exclude-files' => array(), // list