diff --git a/changelog.txt b/changelog.txt
index cb7599d85b..3d02735e68 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,6 +1,7 @@
*** Changelog ***
= 9.7.0 - xxxx-xx-xx =
+* Update - Removes BNPL payment methods (Klarna and Affirm) when other official plugins are active
* Fix - Moves the existing order lock functionality earlier in the order processing flow to prevent duplicate processing requests
* Add - Adds two new safety filters to the subscriptions detached debug tool: `wc_stripe_detached_subscriptions_maximum_time` and `wc_stripe_detached_subscriptions_maximum_count`
* Add - Show a notice when editing an active subscription that has no payment method attached
diff --git a/client/blocks/upe/index.js b/client/blocks/upe/index.js
index 8ba2852327..12f8a438f6 100644
--- a/client/blocks/upe/index.js
+++ b/client/blocks/upe/index.js
@@ -3,9 +3,11 @@ import {
registerExpressPaymentMethod,
} from '@woocommerce/blocks-registry';
import {
+ PAYMENT_METHOD_AFFIRM,
PAYMENT_METHOD_AMAZON_PAY,
PAYMENT_METHOD_CARD,
PAYMENT_METHOD_GIROPAY,
+ PAYMENT_METHOD_KLARNA,
PAYMENT_METHOD_LINK,
} from '../../stripe-utils/constants';
import { updateTokenLabelsWhenLoaded } from './token-label-updater.js';
@@ -38,18 +40,26 @@ const api = new WCStripeAPI(
const paymentMethodsConfig =
getBlocksConfiguration()?.paymentMethodsConfig ?? {};
-const methodsToFilter = [
- PAYMENT_METHOD_AMAZON_PAY,
- PAYMENT_METHOD_LINK,
- PAYMENT_METHOD_GIROPAY, // Skip giropay as it was deprecated by Jun, 30th 2024.
-];
-
// Register UPE Elements.
if ( getBlocksConfiguration()?.isOCEnabled ) {
registerPaymentMethod(
upeElement( PAYMENT_METHOD_CARD, api, paymentMethodsConfig.card )
);
} else {
+ const methodsToFilter = [
+ PAYMENT_METHOD_AMAZON_PAY,
+ PAYMENT_METHOD_LINK,
+ PAYMENT_METHOD_GIROPAY, // Skip giropay as it was deprecated by Jun, 30th 2024.
+ ];
+
+ // Filter out some BNPLs when other official extensions are present.
+ if ( getBlocksConfiguration()?.hasAffirmGatewayPlugin ) {
+ methodsToFilter.push( PAYMENT_METHOD_AFFIRM );
+ }
+ if ( getBlocksConfiguration()?.hasKlarnaGatewayPlugin ) {
+ methodsToFilter.push( PAYMENT_METHOD_KLARNA );
+ }
+
Object.entries( paymentMethodsConfig )
.filter( ( [ method ] ) => ! methodsToFilter.includes( method ) )
.forEach( ( [ method, config ] ) => {
diff --git a/client/components/payment-method-unavailable-due-conflict-pill/__tests__/index.test.js b/client/components/payment-method-unavailable-due-conflict-pill/__tests__/index.test.js
new file mode 100644
index 0000000000..c59e69fad1
--- /dev/null
+++ b/client/components/payment-method-unavailable-due-conflict-pill/__tests__/index.test.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import { screen, render } from '@testing-library/react';
+import PaymentMethodUnavailableDueConflictPill from '..';
+import { PAYMENT_METHOD_AFFIRM } from 'wcstripe/stripe-utils/constants';
+
+describe( 'PaymentMethodUnavailableDueConflictPill', () => {
+ beforeEach( () => {
+ global.wc_stripe_settings_params = { has_affirm_gateway_plugin: false };
+ } );
+
+ it( 'should render the "Has plugin conflict" text', () => {
+ global.wc_stripe_settings_params = { has_affirm_gateway_plugin: true };
+
+ render(
+
+ );
+
+ expect(
+ screen.queryByText( 'Has plugin conflict' )
+ ).toBeInTheDocument();
+ } );
+
+ it( 'should not render when other extensions are not active', () => {
+ const { container } = render(
+
+ );
+
+ expect( container.firstChild ).toBeNull();
+ } );
+} );
diff --git a/client/components/payment-method-unavailable-due-conflict-pill/index.js b/client/components/payment-method-unavailable-due-conflict-pill/index.js
new file mode 100644
index 0000000000..7610b54346
--- /dev/null
+++ b/client/components/payment-method-unavailable-due-conflict-pill/index.js
@@ -0,0 +1,96 @@
+/* global wc_stripe_settings_params */
+import { __, sprintf } from '@wordpress/i18n';
+import React from 'react';
+import styled from '@emotion/styled';
+import interpolateComponents from 'interpolate-components';
+import { Icon, info } from '@wordpress/icons';
+import Popover from 'wcstripe/components/popover';
+import {
+ PAYMENT_METHOD_AFFIRM,
+ PAYMENT_METHOD_KLARNA,
+} from 'wcstripe/stripe-utils/constants';
+
+const StyledPill = styled.span`
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ padding: 4px 8px;
+ border: 1px solid #fcf9e8;
+ border-radius: 2px;
+ background-color: #fcf9e8;
+ color: #674600;
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 16px;
+ width: fit-content;
+`;
+
+const StyledLink = styled.a`
+ &:focus,
+ &:visited {
+ box-shadow: none;
+ }
+`;
+
+const IconWrapper = styled.span`
+ height: 16px;
+ cursor: pointer;
+`;
+
+const AlertIcon = styled( Icon )`
+ fill: #674600;
+`;
+
+const IconComponent = ( { children, ...props } ) => (
+
+
+ { children }
+
+);
+
+const PaymentMethodUnavailableDueConflictPill = ( { id, label } ) => {
+ if (
+ ( id === PAYMENT_METHOD_AFFIRM &&
+ // eslint-disable-next-line camelcase
+ wc_stripe_settings_params.has_affirm_gateway_plugin ) ||
+ ( id === PAYMENT_METHOD_KLARNA &&
+ // eslint-disable-next-line camelcase
+ wc_stripe_settings_params.has_klarna_gateway_plugin )
+ ) {
+ return (
+
+ { __( 'Has plugin conflict', 'woocommerce-gateway-stripe' ) }
+ {
+ // Stop propagation is necessary so it doesn't trigger the tooltip click event.
+ ev.stopPropagation();
+ } }
+ />
+ ),
+ },
+ } ) }
+ />
+
+ );
+ }
+
+ return null;
+};
+
+export default PaymentMethodUnavailableDueConflictPill;
diff --git a/client/settings/general-settings-section/payment-method-description.js b/client/settings/general-settings-section/payment-method-description.js
index 2c89f81ee9..42582b0132 100644
--- a/client/settings/general-settings-section/payment-method-description.js
+++ b/client/settings/general-settings-section/payment-method-description.js
@@ -4,6 +4,7 @@ import PaymentMethodMissingCurrencyPill from '../../components/payment-method-mi
import RecurringPaymentIcon from '../../components/recurring-payment-icon';
import PaymentMethodCapabilityStatusPill from 'wcstripe/components/payment-method-capability-status-pill';
import PaymentMethodDeprecationPill from 'wcstripe/components/payment-method-deprecation-pill';
+import PaymentMethodUnavailableDueConflictPill from 'wcstripe/components/payment-method-unavailable-due-conflict-pill';
const Wrapper = styled.div`
display: flex;
@@ -69,6 +70,10 @@ const PaymentMethodDescription = ( {
id={ id }
label={ label }
/>
+
>
) }
diff --git a/client/settings/general-settings-section/payment-method.js b/client/settings/general-settings-section/payment-method.js
index dda0e555e9..eccb367ac2 100644
--- a/client/settings/general-settings-section/payment-method.js
+++ b/client/settings/general-settings-section/payment-method.js
@@ -12,6 +12,7 @@ import {
PAYMENT_METHOD_AFFIRM,
PAYMENT_METHOD_AFTERPAY_CLEARPAY,
PAYMENT_METHOD_CARD,
+ PAYMENT_METHOD_KLARNA,
} from 'wcstripe/stripe-utils/constants';
import PaymentMethodFeesPill from 'wcstripe/components/payment-method-fees-pill';
import { usePaymentMethodCurrencies } from 'utils/use-payment-method-currencies';
@@ -127,8 +128,14 @@ const PaymentMethod = ( { method, data } ) => {
const storeCurrency = window?.wcSettings?.currency?.code;
const isDisabled =
- paymentMethodCurrencies.length &&
- ! paymentMethodCurrencies.includes( storeCurrency );
+ ( paymentMethodCurrencies.length &&
+ ! paymentMethodCurrencies.includes( storeCurrency ) ) ||
+ ( PAYMENT_METHOD_AFFIRM === method &&
+ // eslint-disable-next-line camelcase
+ wc_stripe_settings_params.has_affirm_gateway_plugin ) ||
+ ( PAYMENT_METHOD_KLARNA === method &&
+ // eslint-disable-next-line camelcase
+ wc_stripe_settings_params.has_klarna_gateway_plugin );
return (
diff --git a/includes/admin/class-wc-stripe-settings-controller.php b/includes/admin/class-wc-stripe-settings-controller.php
index 9a103dd751..937883d2e7 100644
--- a/includes/admin/class-wc-stripe-settings-controller.php
+++ b/includes/admin/class-wc-stripe-settings-controller.php
@@ -257,15 +257,6 @@ public function admin_scripts( $hook_suffix ) {
// Show the BNPL promotional banner only if no BNPL payment methods are enabled.
&& ! array_intersect( WC_Stripe_Payment_Methods::BNPL_PAYMENT_METHODS, $enabled_payment_methods );
- $has_other_bnpl_plugins_active = false;
- $available_payment_gateways = WC()->payment_gateways->payment_gateways;
- foreach ( $available_payment_gateways as $gateway ) {
- if ( ( 'affirm' === $gateway->id || 'klarna_payments' === $gateway->id ) && 'yes' === $gateway->enabled ) {
- $has_other_bnpl_plugins_active = true;
- break;
- }
- }
-
$params = [
'time' => time(),
'i18n_out_of_sync' => $message,
@@ -287,7 +278,9 @@ public function admin_scripts( $hook_suffix ) {
'is_oc_available' => WC_Stripe_Feature_Flags::is_oc_available(),
'oauth_nonce' => wp_create_nonce( 'wc_stripe_get_oauth_urls' ),
'is_sepa_tokens_enabled' => 'yes' === $this->gateway->get_option( 'sepa_tokens_for_other_methods', 'no' ),
- 'has_other_bnpl_plugins' => $has_other_bnpl_plugins_active,
+ 'has_affirm_gateway_plugin' => WC_Stripe_Helper::has_gateway_plugin_active( WC_Stripe_Helper::OFFICIAL_PLUGIN_ID_AFFIRM ),
+ 'has_klarna_gateway_plugin' => WC_Stripe_Helper::has_gateway_plugin_active( WC_Stripe_Helper::OFFICIAL_PLUGIN_ID_KLARNA ),
+ 'has_other_bnpl_plugins' => WC_Stripe_Helper::has_other_bnpl_plugins_active(),
'is_payments_onboarding_task_completed' => $this->is_payments_onboarding_task_completed(),
];
wp_localize_script(
diff --git a/includes/class-wc-stripe-helper.php b/includes/class-wc-stripe-helper.php
index 98d17456b0..d1620b843d 100644
--- a/includes/class-wc-stripe-helper.php
+++ b/includes/class-wc-stripe-helper.php
@@ -20,6 +20,20 @@ class WC_Stripe_Helper {
const META_NAME_STRIPE_CURRENCY = '_stripe_currency';
const PAYMENT_AWAITING_ACTION_META = '_stripe_payment_awaiting_action';
+ /**
+ * The identifier for the official Affirm gateway plugin.
+ *
+ * @var string
+ */
+ const OFFICIAL_PLUGIN_ID_AFFIRM = 'affirm';
+
+ /**
+ * The identifier for the official Klarna gateway plugin.
+ *
+ * @var string
+ */
+ const OFFICIAL_PLUGIN_ID_KLARNA = 'klarna_payments';
+
/**
* List of legacy Stripe gateways.
*
@@ -1851,4 +1865,35 @@ public static function get_refund_reason_description( $refund_reason_key ) {
return __( 'Unknown reason', 'woocommerce-gateway-stripe' );
}
}
+
+ /**
+ * Checks if there are other Buy Now Pay Later plugins active.
+ *
+ * @return bool
+ */
+ public static function has_other_bnpl_plugins_active() {
+ $other_bnpl_gateway_ids = [ self::OFFICIAL_PLUGIN_ID_AFFIRM, self::OFFICIAL_PLUGIN_ID_KLARNA ];
+ foreach ( $other_bnpl_gateway_ids as $bnpl_gateway_id ) {
+ if ( self::has_gateway_plugin_active( $bnpl_gateway_id ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if a given payment gateway plugin is active.
+ *
+ * @param string $plugin_id
+ * @return bool
+ */
+ public static function has_gateway_plugin_active( $plugin_id ) {
+ $available_payment_gateways = WC()->payment_gateways->payment_gateways ?? [];
+ foreach ( $available_payment_gateways as $available_payment_gateway ) {
+ if ( $plugin_id === $available_payment_gateway->id && 'yes' === $available_payment_gateway->enabled ) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/includes/notes/class-wc-stripe-bnpl-promotion-note.php b/includes/notes/class-wc-stripe-bnpl-promotion-note.php
index 13b2cc3178..5833972db0 100644
--- a/includes/notes/class-wc-stripe-bnpl-promotion-note.php
+++ b/includes/notes/class-wc-stripe-bnpl-promotion-note.php
@@ -93,16 +93,7 @@ public static function init( WC_Stripe_Payment_Gateway $gateway ) {
}
}
- $has_other_bnpl_plugins_active = false;
- $available_payment_gateways = WC()->payment_gateways->payment_gateways;
- $other_bnpl_gateway_ids = [ 'affirm', 'klarna_payments' ];
- foreach ( $available_payment_gateways as $available_payment_gateway ) {
- if ( in_array( $available_payment_gateway->id, $other_bnpl_gateway_ids, true ) && 'yes' === $available_payment_gateway->enabled ) {
- $has_other_bnpl_plugins_active = true;
- break;
- }
- }
- if ( $has_other_bnpl_plugins_active ) {
+ if ( WC_Stripe_Helper::has_other_bnpl_plugins_active() ) {
return;
}
diff --git a/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php b/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php
index 44eb735f60..de26369b1d 100644
--- a/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php
+++ b/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php
@@ -557,6 +557,10 @@ public function javascript_params() {
// Single Payment Element payment method parent configuration ID
$stripe_params['paymentMethodConfigurationParentId'] = WC_Stripe_Payment_Method_Configurations::get_parent_configuration_id();
+ // Checking for other BNPL extensions.
+ $stripe_params['hasAffirmGatewayPlugin'] = WC_Stripe_Helper::has_gateway_plugin_active( WC_Stripe_Helper::OFFICIAL_PLUGIN_ID_AFFIRM );
+ $stripe_params['hasKlarnaGatewayPlugin'] = WC_Stripe_Helper::has_gateway_plugin_active( WC_Stripe_Helper::OFFICIAL_PLUGIN_ID_KLARNA );
+
$cart_total = ( WC()->cart ? WC()->cart->get_total( '' ) : 0 );
$currency = get_woocommerce_currency();
diff --git a/includes/payment-methods/class-wc-stripe-upe-payment-method-affirm.php b/includes/payment-methods/class-wc-stripe-upe-payment-method-affirm.php
index 5553eeb8cb..617530251d 100644
--- a/includes/payment-methods/class-wc-stripe-upe-payment-method-affirm.php
+++ b/includes/payment-methods/class-wc-stripe-upe-payment-method-affirm.php
@@ -61,4 +61,18 @@ public function is_available_for_account_country() {
public function requires_automatic_capture() {
return false;
}
+
+ /**
+ * Returns true if the UPE method is available.
+ *
+ * @inheritDoc
+ */
+ public function is_available() {
+ // Affirm is only available if the official Affirm plugin is not active.
+ if ( WC_Stripe_Helper::has_gateway_plugin_active( WC_Stripe_Helper::OFFICIAL_PLUGIN_ID_AFFIRM ) ) {
+ return false;
+ }
+
+ return parent::is_available();
+ }
}
diff --git a/includes/payment-methods/class-wc-stripe-upe-payment-method-klarna.php b/includes/payment-methods/class-wc-stripe-upe-payment-method-klarna.php
index 460cbac7fb..dd8daa0ac2 100644
--- a/includes/payment-methods/class-wc-stripe-upe-payment-method-klarna.php
+++ b/includes/payment-methods/class-wc-stripe-upe-payment-method-klarna.php
@@ -156,4 +156,18 @@ public function is_available_for_account_country() {
public function requires_automatic_capture() {
return false;
}
+
+ /**
+ * Returns true if the UPE method is available.
+ *
+ * @inheritDoc
+ */
+ public function is_available() {
+ // Klarna is only available if the official Klarna plugin is not active.
+ if ( WC_Stripe_Helper::has_gateway_plugin_active( WC_Stripe_Helper::OFFICIAL_PLUGIN_ID_KLARNA ) ) {
+ return false;
+ }
+
+ return parent::is_available();
+ }
}
diff --git a/readme.txt b/readme.txt
index 1a08d7180d..58b392c075 100644
--- a/readme.txt
+++ b/readme.txt
@@ -111,6 +111,7 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o
== Changelog ==
= 9.7.0 - xxxx-xx-xx =
+* Update - Removes BNPL payment methods (Klarna and Affirm) when other official plugins are active
* Fix - Moves the existing order lock functionality earlier in the order processing flow to prevent duplicate processing requests
* Add - Adds two new safety filters to the subscriptions detached debug tool: `wc_stripe_detached_subscriptions_maximum_time` and `wc_stripe_detached_subscriptions_maximum_count`
* Add - Show a notice when editing an active subscription that has no payment method attached
diff --git a/tests/phpunit/WC_Stripe_Helper_Test.php b/tests/phpunit/WC_Stripe_Helper_Test.php
index 415e614edb..52dc076739 100644
--- a/tests/phpunit/WC_Stripe_Helper_Test.php
+++ b/tests/phpunit/WC_Stripe_Helper_Test.php
@@ -595,4 +595,107 @@ public function provide_test_get_refund_reason_description() {
],
];
}
+
+ /**
+ * Tests for `has_other_bnpl_plugins_active`.
+ *
+ * @param array $payment_gateways The available payment gateways.
+ * @param bool $expected The expected result.
+ * @dataProvider provide_test_has_other_bnpl_plugins_active
+ * @return void
+ */
+ public function test_has_other_bnpl_plugins_active( $payment_gateways, $expected ) {
+ $original_payment_gateways = WC()->payment_gateways->payment_gateways;
+
+ // Mock the available payment gateways.
+ WC()->payment_gateways->payment_gateways = $payment_gateways;
+
+ $actual = WC_Stripe_Helper::has_other_bnpl_plugins_active();
+
+ // Clean up.
+ WC()->payment_gateways->payment_gateways = $original_payment_gateways;
+
+ $this->assertSame( $expected, $actual );
+ }
+
+ /**
+ * Provider for `test_has_other_bnpl_plugins_active`.
+ *
+ * @return array
+ */
+ public function provide_test_has_other_bnpl_plugins_active() {
+ return [
+ 'has other plugins' => [
+ 'payment gateways' => [
+ 'klarna' => (object) [
+ 'id' => 'klarna_payments',
+ 'enabled' => 'yes',
+ ],
+ 'affirm' => (object) [
+ 'id' => 'affirm',
+ 'enabled' => 'yes',
+ ],
+ ],
+ 'expected' => true,
+ ],
+ 'does not have other plugins' => [
+ 'payment gateways' => [],
+ 'expected' => false,
+ ],
+ ];
+ }
+
+ /**
+ * Tests for `has_gateway_plugin_active`.
+ *
+ * @param string $plugin_id The plugin ID to evaluate.
+ * @param array $payment_gateways The available payment gateways.
+ * @param bool $expected The expected result.
+ * @return void
+ *
+ * @dataProvider provide_has_gateway_plugin_active
+ */
+ public function test_has_gateway_plugin_active( $plugin_id, $payment_gateways, $expected ) {
+ $original_payment_gateways = WC()->payment_gateways->payment_gateways;
+
+ // Mock the available payment gateways.
+ WC()->payment_gateways->payment_gateways = $payment_gateways;
+
+ $actual = WC_Stripe_Helper::has_gateway_plugin_active( $plugin_id );
+
+ // Clean up.
+ WC()->payment_gateways->payment_gateways = $original_payment_gateways;
+
+ $this->assertSame( $expected, $actual );
+ }
+
+ /**
+ * Provider for `test_has_gateway_plugin_active`.
+ *
+ * @return array
+ */
+ public function provide_has_gateway_plugin_active() {
+ return [
+ 'has Klarna official plugin active' => [
+ 'plugin id' => WC_Stripe_Helper::OFFICIAL_PLUGIN_ID_KLARNA,
+ 'payment gateways' => [
+ 'klarna' => (object) [
+ 'id' => 'klarna_payments',
+ 'enabled' => 'yes',
+ ],
+ ],
+ 'expected' => true,
+ ],
+ 'does not have Klarna official plugin active' => [
+ 'plugin id' => WC_Stripe_Helper::OFFICIAL_PLUGIN_ID_KLARNA,
+ 'payment gateways' => [
+ 'affirm' => (object) [
+ 'id' => 'affirm',
+ 'enabled' => 'yes',
+ ],
+ ],
+ 'expected' => false,
+ ],
+ ];
+ }
}