From d6f367c560939709c03b126b663266750878659a Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 20 Mar 2025 17:44:07 +0400 Subject: [PATCH 01/30] Add service for the BN Codes mapping. --- modules/ppcp-api-client/services.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index bfadb2cda..b5d8fe1ad 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -18,6 +18,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Factory\CardAuthenticationResultFactory; use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter; use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\CatalogProducts; @@ -963,4 +964,18 @@ return array( return CONNECT_WOO_URL; }, + /** + * BN Codes mapping for different installation paths. + * + * @returns array The map of installation paths to BN Codes. + */ + 'api.bn-codes' => static function (): array { + return array( + 'core-profiler' => 'WooPPCP_Ecom_PS_CoreProfiler', + 'payment-settings' => 'WooPPCP_Ecom_PS_PaymentSettings', + ); + }, + 'api.helper.partner-attribution' => static function ( ContainerInterface $container ) : PartnerAttribution { + return new PartnerAttribution( 'ppcp_bn_code', $container->get( 'api.bn-codes' ) ); + }, ); From da18306e13904cfdebcce404c0520ab01bfdcea0 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 20 Mar 2025 17:45:28 +0400 Subject: [PATCH 02/30] Create a class for partner attribution. This class handles the retrieval and persistence of the BN (Build Notation) Code,which is used to track and attribute transactions for PayPal partner integrations. The BN Code is set once and remains persistent, even after disconnecting or uninstalling the plugin. It is determined based on the installation path and stored as a WordPress option. --- .../src/Helper/PartnerAttribution.php | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 modules/ppcp-api-client/src/Helper/PartnerAttribution.php diff --git a/modules/ppcp-api-client/src/Helper/PartnerAttribution.php b/modules/ppcp-api-client/src/Helper/PartnerAttribution.php new file mode 100644 index 000000000..97b480aa9 --- /dev/null +++ b/modules/ppcp-api-client/src/Helper/PartnerAttribution.php @@ -0,0 +1,76 @@ + + */ + protected array $bn_codes; + + /** + * PartnerAttribution constructor. + * + * @param string $bn_code_option_name The BN code option name in DB. + * @param array $bn_codes BN Codes mapping for different installation paths. + */ + public function __construct( string $bn_code_option_name, array $bn_codes ) { + $this->bn_code_option_name = $bn_code_option_name; + $this->bn_codes = $bn_codes; + } + + /** + * Initializes the BN Code if not already set. + * + * This method ensures that the BN Code is only stored once during the initial setup. + * + * @param string $installation_path The installation path used to determine the BN Code. + */ + public function initialize_bn_code( string $installation_path ): void { + $selected_bn_code = $this->bn_codes[ $installation_path ] ?? false; + + if ( get_option( $this->bn_code_option_name ) || ! $selected_bn_code ) { + return; + } + + update_option( $this->bn_code_option_name, $selected_bn_code ); + } + + /** + * Retrieves the persisted BN Code. + * + * @return string The stored BN Code, or the empty string if no path is detected . + */ + public function get_bn_code(): string { + return get_option( $this->bn_code_option_name, '' ); + } +} From a1165b5e9c4b713f4942f4979455fe4f097a3daa Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 20 Mar 2025 17:45:39 +0400 Subject: [PATCH 03/30] Add the tests --- .../Helper/PartnerAttributionTest.php | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php diff --git a/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php b/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php new file mode 100644 index 000000000..40f55fd64 --- /dev/null +++ b/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php @@ -0,0 +1,115 @@ + 'WooPPCP_Ecom_PS_CoreProfiler', + 'payment-settings' => 'WooPPCP_Ecom_PS_PaymentSettings', + ]; + + /** + * Set up Brain Monkey before each test. + */ + protected function setUp(): void { + parent::setUp(); + setUp(); // ✅ REQUIRED for Brain Monkey to work + } + + /** + * Tear down Brain Monkey after each test. + */ + protected function tearDown(): void { + tearDown(); // ✅ REQUIRED to reset mocks after each test + parent::tearDown(); + } + + /** + * Tests initializing BN code when it's not already set. + */ + public function test_initialize_bn_code_sets_bn_code_if_not_present(): void { + $installation_path = 'core-profiler'; + $expected_bn_code = 'WooPPCP_Ecom_PS_CoreProfiler'; + + // Ensure get_option returns false to simulate "not set" state + when('get_option')->justReturn(false); + + // Expect update_option to be called once with the correct values + expect('update_option') + ->once() + ->with($this->bn_code_option_name, $expected_bn_code) + ->andReturn(true); + + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + $partner_attribution->initialize_bn_code( $installation_path ); + + $this->assertTrue(true); + } + + /** + * Tests that initialize_bn_code does nothing if the BN code is already set. + */ + public function test_initialize_bn_code_does_not_update_if_already_set(): void { + when('get_option')->justReturn('WooPPCP_Ecom_PS_CoreProfiler'); + expect( 'update_option' )->never(); + + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + $partner_attribution->initialize_bn_code( 'core-profiler' ); + + $this->assertTrue(true); + } + + /** + * Tests retrieving the BN Code. + */ + public function test_get_bn_code_returns_persisted_value(): void { + $expected_bn_code = 'WooPPCP_Ecom_PS_CoreProfiler'; + + expect( 'get_option' ) + ->once() + ->with( $this->bn_code_option_name, '' ) + ->andReturn( $expected_bn_code ); + + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + + $this->assertSame( $expected_bn_code, $partner_attribution->get_bn_code() ); + } + + /** + * Tests that get_bn_code returns an empty string if no value is stored. + */ + public function test_get_bn_code_returns_empty_string_when_not_set(): void { + expect( 'get_option' ) + ->once() + ->with( $this->bn_code_option_name, '' ) + ->andReturn( '' ); + + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + + $this->assertSame( '', $partner_attribution->get_bn_code() ); + } +} From ba894500464ce335ca918c2ab923621145dfa9c5 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 14:23:47 +0400 Subject: [PATCH 04/30] =?UTF-8?q?=F0=9F=94=A8=20:hammer:=20Refactor:=20Use?= =?UTF-8?q?=20only=20the=20`core-profiler`=20path,=20otherwise=20use=20the?= =?UTF-8?q?=20default=20BN=20code.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-api-client/services.php | 1 - modules/ppcp-api-client/src/Helper/PartnerAttribution.php | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index b5d8fe1ad..3306e70b7 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -972,7 +972,6 @@ return array( 'api.bn-codes' => static function (): array { return array( 'core-profiler' => 'WooPPCP_Ecom_PS_CoreProfiler', - 'payment-settings' => 'WooPPCP_Ecom_PS_PaymentSettings', ); }, 'api.helper.partner-attribution' => static function ( ContainerInterface $container ) : PartnerAttribution { diff --git a/modules/ppcp-api-client/src/Helper/PartnerAttribution.php b/modules/ppcp-api-client/src/Helper/PartnerAttribution.php index 97b480aa9..b75d2f934 100644 --- a/modules/ppcp-api-client/src/Helper/PartnerAttribution.php +++ b/modules/ppcp-api-client/src/Helper/PartnerAttribution.php @@ -68,9 +68,9 @@ class PartnerAttribution { /** * Retrieves the persisted BN Code. * - * @return string The stored BN Code, or the empty string if no path is detected . + * @return string The stored BN Code, or the default value if no path is detected. */ public function get_bn_code(): string { - return get_option( $this->bn_code_option_name, '' ); + return get_option( $this->bn_code_option_name, PPCP_PAYPAL_BN_CODE ); } } From 6df65cd33b6805e16d6ce4dede48e44de4e129cc Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 14:24:31 +0400 Subject: [PATCH 05/30] Fix the CS --- modules/ppcp-api-client/services.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 3306e70b7..cd0fb7c52 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -971,7 +971,7 @@ return array( */ 'api.bn-codes' => static function (): array { return array( - 'core-profiler' => 'WooPPCP_Ecom_PS_CoreProfiler', + 'core-profiler' => 'WooPPCP_Ecom_PS_CoreProfiler', ); }, 'api.helper.partner-attribution' => static function ( ContainerInterface $container ) : PartnerAttribution { From c15d429e7a801f58c655d3334aeec7924ee80113 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 14:46:31 +0400 Subject: [PATCH 06/30] :rotating_light: Fix the test --- modules/ppcp-api-client/services.php | 6 ++++- .../src/Helper/PartnerAttribution.php | 13 ++++++++-- .../Helper/PartnerAttributionTest.php | 24 ++++++++++++------- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index cd0fb7c52..6cc7d2382 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -975,6 +975,10 @@ return array( ); }, 'api.helper.partner-attribution' => static function ( ContainerInterface $container ) : PartnerAttribution { - return new PartnerAttribution( 'ppcp_bn_code', $container->get( 'api.bn-codes' ) ); + return new PartnerAttribution( + 'ppcp_bn_code', + $container->get( 'api.bn-codes' ), + PPCP_PAYPAL_BN_CODE + ); }, ); diff --git a/modules/ppcp-api-client/src/Helper/PartnerAttribution.php b/modules/ppcp-api-client/src/Helper/PartnerAttribution.php index b75d2f934..486c579a6 100644 --- a/modules/ppcp-api-client/src/Helper/PartnerAttribution.php +++ b/modules/ppcp-api-client/src/Helper/PartnerAttribution.php @@ -37,15 +37,24 @@ class PartnerAttribution { */ protected array $bn_codes; + /** + * The default BN code. + * + * @var string + */ + protected string $default_bn_code; + /** * PartnerAttribution constructor. * * @param string $bn_code_option_name The BN code option name in DB. * @param array $bn_codes BN Codes mapping for different installation paths. + * @param string $default_bn_code The default BN code. */ - public function __construct( string $bn_code_option_name, array $bn_codes ) { + public function __construct( string $bn_code_option_name, array $bn_codes, string $default_bn_code ) { $this->bn_code_option_name = $bn_code_option_name; $this->bn_codes = $bn_codes; + $this->default_bn_code = $default_bn_code; } /** @@ -71,6 +80,6 @@ class PartnerAttribution { * @return string The stored BN Code, or the default value if no path is detected. */ public function get_bn_code(): string { - return get_option( $this->bn_code_option_name, PPCP_PAYPAL_BN_CODE ); + return get_option( $this->bn_code_option_name, $this->default_bn_code ) ?? $this->default_bn_code; } } diff --git a/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php b/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php index 40f55fd64..504526638 100644 --- a/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php +++ b/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php @@ -29,9 +29,15 @@ class PartnerAttributionTest extends TestCase { */ private array $bn_codes = [ 'core-profiler' => 'WooPPCP_Ecom_PS_CoreProfiler', - 'payment-settings' => 'WooPPCP_Ecom_PS_PaymentSettings', ]; + /** + * The default BN code. + * + * @var string + */ + private string $default_bn_code = 'Woo_PPCP'; + /** * Set up Brain Monkey before each test. */ @@ -64,7 +70,7 @@ class PartnerAttributionTest extends TestCase { ->with($this->bn_code_option_name, $expected_bn_code) ->andReturn(true); - $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes, $this->default_bn_code ); $partner_attribution->initialize_bn_code( $installation_path ); $this->assertTrue(true); @@ -77,7 +83,7 @@ class PartnerAttributionTest extends TestCase { when('get_option')->justReturn('WooPPCP_Ecom_PS_CoreProfiler'); expect( 'update_option' )->never(); - $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes, $this->default_bn_code ); $partner_attribution->initialize_bn_code( 'core-profiler' ); $this->assertTrue(true); @@ -91,10 +97,10 @@ class PartnerAttributionTest extends TestCase { expect( 'get_option' ) ->once() - ->with( $this->bn_code_option_name, '' ) + ->with( $this->bn_code_option_name, $this->default_bn_code ) ->andReturn( $expected_bn_code ); - $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes, $this->default_bn_code ); $this->assertSame( $expected_bn_code, $partner_attribution->get_bn_code() ); } @@ -105,11 +111,11 @@ class PartnerAttributionTest extends TestCase { public function test_get_bn_code_returns_empty_string_when_not_set(): void { expect( 'get_option' ) ->once() - ->with( $this->bn_code_option_name, '' ) - ->andReturn( '' ); + ->with( $this->bn_code_option_name, $this->default_bn_code ) + ->andReturn( $this->default_bn_code ); - $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes, $this->default_bn_code ); - $this->assertSame( '', $partner_attribution->get_bn_code() ); + $this->assertSame( $this->default_bn_code, $partner_attribution->get_bn_code() ); } } From f434505fbcb6744754e4d8571c7d8f713f444c91 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 16:04:13 +0400 Subject: [PATCH 07/30] Refactor to use the new partner attribution helper --- .../ppcp-button/src/Assets/SmartButton.php | 24 ++++++++++++------- .../src/PayLaterBlockRenderer.php | 6 ++++- .../src/PayLaterConfiguratorModule.php | 6 +++-- .../src/PayLaterWCBlocksRenderer.php | 6 ++++- modules/ppcp-settings/src/SettingsModule.php | 6 ++++- 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 81ced3a68..cf51afda0 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -21,6 +21,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint; @@ -237,6 +238,13 @@ class SmartButton implements SmartButtonInterface { */ private $disabled_funding_sources; + /** + * The PayPal Partner Attribution Helper. + * + * @var PartnerAttribution + */ + protected PartnerAttribution $partner_attribution; + /** * SmartButton constructor. * @@ -264,6 +272,7 @@ class SmartButton implements SmartButtonInterface { * @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 PartnerAttribution $partner_attribution The PayPal Partner Attribution Helper. */ public function __construct( string $module_url, @@ -289,7 +298,8 @@ class SmartButton implements SmartButtonInterface { PaymentTokensEndpoint $payment_tokens_endpoint, LoggerInterface $logger, bool $should_handle_shipping_in_paypal, - DisabledFundingSources $disabled_funding_sources + DisabledFundingSources $disabled_funding_sources, + PartnerAttribution $partner_attribution ) { $this->module_url = $module_url; @@ -316,6 +326,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->partner_attribution = $partner_attribution; } /** @@ -833,9 +844,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages */ do_action( "ppcp_before_{$location_hook}_message_wrapper" ); - $bn_code = PPCP_PAYPAL_BN_CODE; - - $messages_placeholder = '
'; + $messages_placeholder = '
'; if ( is_array( $block_params ) && ( $block_params['blockName'] ?? false ) ) { $this->render_after_block( @@ -1545,12 +1554,9 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages * @return string */ private function bn_code_for_context( string $context ): string { - $codes = $this->bn_codes(); - $bn_code = PPCP_PAYPAL_BN_CODE; - - return ( isset( $codes[ $context ] ) ) ? $codes[ $context ] : $bn_code; + return ( isset( $codes[ $context ] ) ) ? $codes[ $context ] : $this->partner_attribution->get_bn_code(); } /** @@ -1560,7 +1566,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages */ private function bn_codes() : array { - $bn_code = PPCP_PAYPAL_BN_CODE; + $bn_code = $this->partner_attribution->get_bn_code(); return array( 'checkout' => $bn_code, diff --git a/modules/ppcp-paylater-block/src/PayLaterBlockRenderer.php b/modules/ppcp-paylater-block/src/PayLaterBlockRenderer.php index 74e8586b3..2408ba091 100644 --- a/modules/ppcp-paylater-block/src/PayLaterBlockRenderer.php +++ b/modules/ppcp-paylater-block/src/PayLaterBlockRenderer.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\PayLaterBlock; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** @@ -28,7 +29,10 @@ class PayLaterBlockRenderer { public function render( array $attributes, ContainerInterface $c ): string { if ( PayLaterBlockModule::is_block_enabled( $c->get( 'wcgateway.settings.status' ) ) ) { - $bn_code = PPCP_PAYPAL_BN_CODE; + $partner_attribution = $c->get( 'api.helper.partner-attribution' ); + assert( $partner_attribution instanceof PartnerAttribution ); + + $bn_code = $partner_attribution->get_bn_code(); $html = '
'; diff --git a/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php b/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php index 71f33e1da..b877d9545 100644 --- a/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php +++ b/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\PayLaterConfigurator; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; use WooCommerce\PayPalCommerce\PayLaterConfigurator\Endpoint\GetConfig; use WooCommerce\PayPalCommerce\PayLaterConfigurator\Endpoint\SaveConfig; use WooCommerce\PayPalCommerce\PayLaterConfigurator\Factory\ConfigFactory; @@ -126,7 +127,8 @@ class PayLaterConfiguratorModule implements ServiceModule, ExtendingModule, Exec $config_factory = $c->get( 'paylater-configurator.factory.config' ); assert( $config_factory instanceof ConfigFactory ); - $bn_code = PPCP_PAYPAL_BN_CODE; + $partner_attribution = $c->get( 'api.helper.partner-attribution' ); + assert( $partner_attribution instanceof PartnerAttribution ); wp_localize_script( 'ppcp-paylater-configurator', @@ -145,7 +147,7 @@ class PayLaterConfiguratorModule implements ServiceModule, ExtendingModule, Exec 'config' => $config_factory->from_settings( $settings ), 'merchantClientId' => $settings->get( 'client_id' ), 'partnerClientId' => $c->get( 'api.partner_merchant_id' ), - 'bnCode' => $bn_code, + 'bnCode' => $partner_attribution->get_bn_code(), 'publishButtonClassName' => 'ppcp-paylater-configurator-publishButton', 'headerClassName' => 'ppcp-paylater-configurator-header', 'subheaderClassName' => 'ppcp-paylater-configurator-subheader', diff --git a/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksRenderer.php b/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksRenderer.php index f0720300b..8292f312f 100644 --- a/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksRenderer.php +++ b/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksRenderer.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\PayLaterWCBlocks; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** @@ -95,7 +96,10 @@ class PayLaterWCBlocksRenderer { ) { if ( PayLaterWCBlocksModule::is_placement_enabled( $c->get( 'wcgateway.settings.status' ), $location ) ) { - $bn_code = PPCP_PAYPAL_BN_CODE; + $partner_attribution = $c->get( 'api.helper.partner-attribution' ); + assert( $partner_attribution instanceof PartnerAttribution ); + + $bn_code = $partner_attribution->get_bn_code(); $html = '
'; diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php index 46eca79dd..d07bdbb5a 100644 --- a/modules/ppcp-settings/src/SettingsModule.php +++ b/modules/ppcp-settings/src/SettingsModule.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\Settings; use WC_Payment_Gateway; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus; @@ -220,6 +221,9 @@ class SettingsModule implements ServiceModule, ExecutableModule { ); if ( $is_pay_later_configurator_available ) { + $partner_attribution = $container->get( 'api.helper.partner-attribution' ); + assert( $partner_attribution instanceof PartnerAttribution ); + wp_enqueue_script( 'ppcp-paylater-configurator-lib', 'https://www.paypalobjects.com/merchant-library/merchant-configurator.js', @@ -235,7 +239,7 @@ class SettingsModule implements ServiceModule, ExecutableModule { 'config' => array(), 'merchantClientId' => $settings->get( 'client_id' ), 'partnerClientId' => $container->get( 'api.partner_merchant_id' ), - 'bnCode' => PPCP_PAYPAL_BN_CODE, + 'bnCode' => $partner_attribution->get_bn_code(), ); } From 7c361350ae3eab5996a11c51b56cd027749c5e56 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 16:15:48 +0400 Subject: [PATCH 08/30] Pass the partner attribution helper to a smart button --- modules/ppcp-button/services.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php index 44e9a52ac..4d1b084e3 100644 --- a/modules/ppcp-button/services.php +++ b/modules/ppcp-button/services.php @@ -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( 'api.helper.partner-attribution' ) ); }, 'button.url' => static function ( ContainerInterface $container ): string { From 65b7794624bda268ce91d5cf8e9beca1320c9a67 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 16:22:43 +0400 Subject: [PATCH 09/30] `BillingAgreementsEndpoint`: Add the `'PayPal-Partner-Attribution-Id'` header --- modules/ppcp-api-client/services.php | 1 + .../Endpoint/BillingAgreementsEndpoint.php | 28 +++++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 6cc7d2382..06e78e29e 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -279,6 +279,7 @@ return array( return new BillingAgreementsEndpoint( $container->get( 'api.host' ), $container->get( 'api.bearer' ), + $container->get( 'api.helper.partner-attribution' ), $container->get( 'woocommerce.logger.woocommerce' ) ); }, diff --git a/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php b/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php index 954be01b5..729c87436 100644 --- a/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php @@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use Psr\Log\LoggerInterface; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; /** * Class BillingAgreementsEndpoint @@ -36,6 +37,13 @@ class BillingAgreementsEndpoint { */ private $bearer; + /** + * The PayPal Partner Attribution Helper. + * + * @var PartnerAttribution + */ + protected PartnerAttribution $partner_attribution; + /** * The logger. * @@ -46,18 +54,21 @@ class BillingAgreementsEndpoint { /** * BillingAgreementsEndpoint constructor. * - * @param string $host The host. - * @param Bearer $bearer The bearer. - * @param LoggerInterface $logger The logger. + * @param string $host The host. + * @param Bearer $bearer The bearer. + * @param PartnerAttribution $partner_attribution The PayPal Partner Attribution Helper. + * @param LoggerInterface $logger The logger. */ public function __construct( string $host, Bearer $bearer, + PartnerAttribution $partner_attribution, LoggerInterface $logger ) { - $this->host = $host; - $this->bearer = $bearer; - $this->logger = $logger; + $this->host = $host; + $this->bearer = $bearer; + $this->partner_attribution = $partner_attribution; + $this->logger = $logger; } /** @@ -91,8 +102,9 @@ class BillingAgreementsEndpoint { $args = array( 'method' => 'POST', 'headers' => array( - 'Authorization' => 'Bearer ' . $bearer->token(), - 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer ' . $bearer->token(), + 'Content-Type' => 'application/json', + 'PayPal-Partner-Attribution-Id' => $this->partner_attribution->get_bn_code(), ), 'body' => wp_json_encode( $data ), ); From 986e1fe5caf9a8a2ac70aa78cd3371d26cd05c37 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 16:25:28 +0400 Subject: [PATCH 10/30] Revert the changes. WIll use the filter to add it everywhere at once --- modules/ppcp-api-client/services.php | 1 - .../Endpoint/BillingAgreementsEndpoint.php | 28 ++++++------------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 06e78e29e..6cc7d2382 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -279,7 +279,6 @@ return array( return new BillingAgreementsEndpoint( $container->get( 'api.host' ), $container->get( 'api.bearer' ), - $container->get( 'api.helper.partner-attribution' ), $container->get( 'woocommerce.logger.woocommerce' ) ); }, diff --git a/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php b/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php index 729c87436..954be01b5 100644 --- a/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php @@ -15,7 +15,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use Psr\Log\LoggerInterface; -use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; /** * Class BillingAgreementsEndpoint @@ -37,13 +36,6 @@ class BillingAgreementsEndpoint { */ private $bearer; - /** - * The PayPal Partner Attribution Helper. - * - * @var PartnerAttribution - */ - protected PartnerAttribution $partner_attribution; - /** * The logger. * @@ -54,21 +46,18 @@ class BillingAgreementsEndpoint { /** * BillingAgreementsEndpoint constructor. * - * @param string $host The host. - * @param Bearer $bearer The bearer. - * @param PartnerAttribution $partner_attribution The PayPal Partner Attribution Helper. - * @param LoggerInterface $logger The logger. + * @param string $host The host. + * @param Bearer $bearer The bearer. + * @param LoggerInterface $logger The logger. */ public function __construct( string $host, Bearer $bearer, - PartnerAttribution $partner_attribution, LoggerInterface $logger ) { - $this->host = $host; - $this->bearer = $bearer; - $this->partner_attribution = $partner_attribution; - $this->logger = $logger; + $this->host = $host; + $this->bearer = $bearer; + $this->logger = $logger; } /** @@ -102,9 +91,8 @@ class BillingAgreementsEndpoint { $args = array( 'method' => 'POST', 'headers' => array( - 'Authorization' => 'Bearer ' . $bearer->token(), - 'Content-Type' => 'application/json', - 'PayPal-Partner-Attribution-Id' => $this->partner_attribution->get_bn_code(), + 'Authorization' => 'Bearer ' . $bearer->token(), + 'Content-Type' => 'application/json', ), 'body' => wp_json_encode( $data ), ); From fa51443c8f7def6337d553f826c9da995da35978 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 17:27:52 +0400 Subject: [PATCH 11/30] Use a filter that ensures that all API requests include the appropriate BN code retrieved from the `PartnerAttribution` helper. Using this approach avoids the need for extensive refactoring of existing classes that use the `RequestTrait`. --- modules/ppcp-api-client/src/ApiModule.php | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/modules/ppcp-api-client/src/ApiModule.php b/modules/ppcp-api-client/src/ApiModule.php index a2880761c..21c17caca 100644 --- a/modules/ppcp-api-client/src/ApiModule.php +++ b/modules/ppcp-api-client/src/ApiModule.php @@ -14,6 +14,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; @@ -133,6 +134,34 @@ class ApiModule implements ServiceModule, ExtendingModule, ExecutableModule { } ); + /** + * Filters the request arguments to add the `'PayPal-Partner-Attribution-Id'` header. + * + * This ensures that all API requests include the appropriate BN code retrieved + * from the `PartnerAttribution` helper. Using this approach avoids the need + * for extensive refactoring of existing classes that use the `RequestTrait`. + * + * The filter is applied in {@see RequestTrait::request()} before making an API request. + * + * @see PartnerAttribution::get_bn_code() Retrieves the BN code dynamically. + * @see RequestTrait::request() Where the filter `ppcp_request_args` is applied. + */ + add_filter( + 'ppcp_request_args', + static function ( array $args ) use ( $c ) { + if ( isset( $args['headers']['PayPal-Partner-Attribution-Id'] ) ) { + return $args; + } + + $partner_attribution = $c->get( 'api.helper.partner-attribution' ); + assert( $partner_attribution instanceof PartnerAttribution ); + + $args['headers']['PayPal-Partner-Attribution-Id'] = $partner_attribution->get_bn_code(); + + return $args; + } + ); + return true; } } From 1de81784c1698496db1af5f7b559539c1346cf31 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 17:28:37 +0400 Subject: [PATCH 12/30] the `'PayPal-Partner-Attribution-Id'` is not added from here anymore --- modules/ppcp-api-client/src/Endpoint/RequestTrait.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/RequestTrait.php b/modules/ppcp-api-client/src/Endpoint/RequestTrait.php index cb5f3c543..6d7a8ee4b 100644 --- a/modules/ppcp-api-client/src/Endpoint/RequestTrait.php +++ b/modules/ppcp-api-client/src/Endpoint/RequestTrait.php @@ -41,9 +41,6 @@ trait RequestTrait { * added here. */ $args = apply_filters( 'ppcp_request_args', $args, $url ); - if ( ! isset( $args['headers']['PayPal-Partner-Attribution-Id'] ) ) { - $args['headers']['PayPal-Partner-Attribution-Id'] = PPCP_PAYPAL_BN_CODE; - } $response = wp_remote_get( $url, $args ); if ( $this->is_request_logging_enabled ) { From 6da8e267cf663c16815d847ac902625ef8fefcfd Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 20 Mar 2025 17:44:07 +0400 Subject: [PATCH 13/30] Add service for the BN Codes mapping. --- modules/ppcp-api-client/services.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index bfadb2cda..b5d8fe1ad 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -18,6 +18,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Factory\CardAuthenticationResultFactory; use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter; use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\CatalogProducts; @@ -963,4 +964,18 @@ return array( return CONNECT_WOO_URL; }, + /** + * BN Codes mapping for different installation paths. + * + * @returns array The map of installation paths to BN Codes. + */ + 'api.bn-codes' => static function (): array { + return array( + 'core-profiler' => 'WooPPCP_Ecom_PS_CoreProfiler', + 'payment-settings' => 'WooPPCP_Ecom_PS_PaymentSettings', + ); + }, + 'api.helper.partner-attribution' => static function ( ContainerInterface $container ) : PartnerAttribution { + return new PartnerAttribution( 'ppcp_bn_code', $container->get( 'api.bn-codes' ) ); + }, ); From 8e837458f79ad0a56e114b3239cd849bad18e383 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 20 Mar 2025 17:45:28 +0400 Subject: [PATCH 14/30] Create a class for partner attribution. This class handles the retrieval and persistence of the BN (Build Notation) Code,which is used to track and attribute transactions for PayPal partner integrations. The BN Code is set once and remains persistent, even after disconnecting or uninstalling the plugin. It is determined based on the installation path and stored as a WordPress option. --- .../src/Helper/PartnerAttribution.php | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 modules/ppcp-api-client/src/Helper/PartnerAttribution.php diff --git a/modules/ppcp-api-client/src/Helper/PartnerAttribution.php b/modules/ppcp-api-client/src/Helper/PartnerAttribution.php new file mode 100644 index 000000000..97b480aa9 --- /dev/null +++ b/modules/ppcp-api-client/src/Helper/PartnerAttribution.php @@ -0,0 +1,76 @@ + + */ + protected array $bn_codes; + + /** + * PartnerAttribution constructor. + * + * @param string $bn_code_option_name The BN code option name in DB. + * @param array $bn_codes BN Codes mapping for different installation paths. + */ + public function __construct( string $bn_code_option_name, array $bn_codes ) { + $this->bn_code_option_name = $bn_code_option_name; + $this->bn_codes = $bn_codes; + } + + /** + * Initializes the BN Code if not already set. + * + * This method ensures that the BN Code is only stored once during the initial setup. + * + * @param string $installation_path The installation path used to determine the BN Code. + */ + public function initialize_bn_code( string $installation_path ): void { + $selected_bn_code = $this->bn_codes[ $installation_path ] ?? false; + + if ( get_option( $this->bn_code_option_name ) || ! $selected_bn_code ) { + return; + } + + update_option( $this->bn_code_option_name, $selected_bn_code ); + } + + /** + * Retrieves the persisted BN Code. + * + * @return string The stored BN Code, or the empty string if no path is detected . + */ + public function get_bn_code(): string { + return get_option( $this->bn_code_option_name, '' ); + } +} From 9eda2ccc35be319eca9a1c51b8cd239001d7040b Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 20 Mar 2025 17:45:39 +0400 Subject: [PATCH 15/30] Add the tests --- .../Helper/PartnerAttributionTest.php | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php diff --git a/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php b/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php new file mode 100644 index 000000000..40f55fd64 --- /dev/null +++ b/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php @@ -0,0 +1,115 @@ + 'WooPPCP_Ecom_PS_CoreProfiler', + 'payment-settings' => 'WooPPCP_Ecom_PS_PaymentSettings', + ]; + + /** + * Set up Brain Monkey before each test. + */ + protected function setUp(): void { + parent::setUp(); + setUp(); // ✅ REQUIRED for Brain Monkey to work + } + + /** + * Tear down Brain Monkey after each test. + */ + protected function tearDown(): void { + tearDown(); // ✅ REQUIRED to reset mocks after each test + parent::tearDown(); + } + + /** + * Tests initializing BN code when it's not already set. + */ + public function test_initialize_bn_code_sets_bn_code_if_not_present(): void { + $installation_path = 'core-profiler'; + $expected_bn_code = 'WooPPCP_Ecom_PS_CoreProfiler'; + + // Ensure get_option returns false to simulate "not set" state + when('get_option')->justReturn(false); + + // Expect update_option to be called once with the correct values + expect('update_option') + ->once() + ->with($this->bn_code_option_name, $expected_bn_code) + ->andReturn(true); + + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + $partner_attribution->initialize_bn_code( $installation_path ); + + $this->assertTrue(true); + } + + /** + * Tests that initialize_bn_code does nothing if the BN code is already set. + */ + public function test_initialize_bn_code_does_not_update_if_already_set(): void { + when('get_option')->justReturn('WooPPCP_Ecom_PS_CoreProfiler'); + expect( 'update_option' )->never(); + + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + $partner_attribution->initialize_bn_code( 'core-profiler' ); + + $this->assertTrue(true); + } + + /** + * Tests retrieving the BN Code. + */ + public function test_get_bn_code_returns_persisted_value(): void { + $expected_bn_code = 'WooPPCP_Ecom_PS_CoreProfiler'; + + expect( 'get_option' ) + ->once() + ->with( $this->bn_code_option_name, '' ) + ->andReturn( $expected_bn_code ); + + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + + $this->assertSame( $expected_bn_code, $partner_attribution->get_bn_code() ); + } + + /** + * Tests that get_bn_code returns an empty string if no value is stored. + */ + public function test_get_bn_code_returns_empty_string_when_not_set(): void { + expect( 'get_option' ) + ->once() + ->with( $this->bn_code_option_name, '' ) + ->andReturn( '' ); + + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + + $this->assertSame( '', $partner_attribution->get_bn_code() ); + } +} From 2066bb4968af112bc95c013b51bcd4dbf50fb8e8 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 14:23:47 +0400 Subject: [PATCH 16/30] =?UTF-8?q?=F0=9F=94=A8=20:hammer:=20Refactor:=20Use?= =?UTF-8?q?=20only=20the=20`core-profiler`=20path,=20otherwise=20use=20the?= =?UTF-8?q?=20default=20BN=20code.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-api-client/services.php | 1 - modules/ppcp-api-client/src/Helper/PartnerAttribution.php | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index b5d8fe1ad..3306e70b7 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -972,7 +972,6 @@ return array( 'api.bn-codes' => static function (): array { return array( 'core-profiler' => 'WooPPCP_Ecom_PS_CoreProfiler', - 'payment-settings' => 'WooPPCP_Ecom_PS_PaymentSettings', ); }, 'api.helper.partner-attribution' => static function ( ContainerInterface $container ) : PartnerAttribution { diff --git a/modules/ppcp-api-client/src/Helper/PartnerAttribution.php b/modules/ppcp-api-client/src/Helper/PartnerAttribution.php index 97b480aa9..b75d2f934 100644 --- a/modules/ppcp-api-client/src/Helper/PartnerAttribution.php +++ b/modules/ppcp-api-client/src/Helper/PartnerAttribution.php @@ -68,9 +68,9 @@ class PartnerAttribution { /** * Retrieves the persisted BN Code. * - * @return string The stored BN Code, or the empty string if no path is detected . + * @return string The stored BN Code, or the default value if no path is detected. */ public function get_bn_code(): string { - return get_option( $this->bn_code_option_name, '' ); + return get_option( $this->bn_code_option_name, PPCP_PAYPAL_BN_CODE ); } } From 5a4126095471f33ab9c189487e47c5d40f3cd8fb Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 14:24:31 +0400 Subject: [PATCH 17/30] Fix the CS --- modules/ppcp-api-client/services.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 3306e70b7..cd0fb7c52 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -971,7 +971,7 @@ return array( */ 'api.bn-codes' => static function (): array { return array( - 'core-profiler' => 'WooPPCP_Ecom_PS_CoreProfiler', + 'core-profiler' => 'WooPPCP_Ecom_PS_CoreProfiler', ); }, 'api.helper.partner-attribution' => static function ( ContainerInterface $container ) : PartnerAttribution { From 55c19d6173ead0e840e4cedac477ae5dc152eb26 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 14:46:31 +0400 Subject: [PATCH 18/30] :rotating_light: Fix the test --- modules/ppcp-api-client/services.php | 6 ++++- .../src/Helper/PartnerAttribution.php | 13 ++++++++-- .../Helper/PartnerAttributionTest.php | 24 ++++++++++++------- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index cd0fb7c52..6cc7d2382 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -975,6 +975,10 @@ return array( ); }, 'api.helper.partner-attribution' => static function ( ContainerInterface $container ) : PartnerAttribution { - return new PartnerAttribution( 'ppcp_bn_code', $container->get( 'api.bn-codes' ) ); + return new PartnerAttribution( + 'ppcp_bn_code', + $container->get( 'api.bn-codes' ), + PPCP_PAYPAL_BN_CODE + ); }, ); diff --git a/modules/ppcp-api-client/src/Helper/PartnerAttribution.php b/modules/ppcp-api-client/src/Helper/PartnerAttribution.php index b75d2f934..486c579a6 100644 --- a/modules/ppcp-api-client/src/Helper/PartnerAttribution.php +++ b/modules/ppcp-api-client/src/Helper/PartnerAttribution.php @@ -37,15 +37,24 @@ class PartnerAttribution { */ protected array $bn_codes; + /** + * The default BN code. + * + * @var string + */ + protected string $default_bn_code; + /** * PartnerAttribution constructor. * * @param string $bn_code_option_name The BN code option name in DB. * @param array $bn_codes BN Codes mapping for different installation paths. + * @param string $default_bn_code The default BN code. */ - public function __construct( string $bn_code_option_name, array $bn_codes ) { + public function __construct( string $bn_code_option_name, array $bn_codes, string $default_bn_code ) { $this->bn_code_option_name = $bn_code_option_name; $this->bn_codes = $bn_codes; + $this->default_bn_code = $default_bn_code; } /** @@ -71,6 +80,6 @@ class PartnerAttribution { * @return string The stored BN Code, or the default value if no path is detected. */ public function get_bn_code(): string { - return get_option( $this->bn_code_option_name, PPCP_PAYPAL_BN_CODE ); + return get_option( $this->bn_code_option_name, $this->default_bn_code ) ?? $this->default_bn_code; } } diff --git a/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php b/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php index 40f55fd64..504526638 100644 --- a/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php +++ b/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php @@ -29,9 +29,15 @@ class PartnerAttributionTest extends TestCase { */ private array $bn_codes = [ 'core-profiler' => 'WooPPCP_Ecom_PS_CoreProfiler', - 'payment-settings' => 'WooPPCP_Ecom_PS_PaymentSettings', ]; + /** + * The default BN code. + * + * @var string + */ + private string $default_bn_code = 'Woo_PPCP'; + /** * Set up Brain Monkey before each test. */ @@ -64,7 +70,7 @@ class PartnerAttributionTest extends TestCase { ->with($this->bn_code_option_name, $expected_bn_code) ->andReturn(true); - $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes, $this->default_bn_code ); $partner_attribution->initialize_bn_code( $installation_path ); $this->assertTrue(true); @@ -77,7 +83,7 @@ class PartnerAttributionTest extends TestCase { when('get_option')->justReturn('WooPPCP_Ecom_PS_CoreProfiler'); expect( 'update_option' )->never(); - $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes, $this->default_bn_code ); $partner_attribution->initialize_bn_code( 'core-profiler' ); $this->assertTrue(true); @@ -91,10 +97,10 @@ class PartnerAttributionTest extends TestCase { expect( 'get_option' ) ->once() - ->with( $this->bn_code_option_name, '' ) + ->with( $this->bn_code_option_name, $this->default_bn_code ) ->andReturn( $expected_bn_code ); - $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes, $this->default_bn_code ); $this->assertSame( $expected_bn_code, $partner_attribution->get_bn_code() ); } @@ -105,11 +111,11 @@ class PartnerAttributionTest extends TestCase { public function test_get_bn_code_returns_empty_string_when_not_set(): void { expect( 'get_option' ) ->once() - ->with( $this->bn_code_option_name, '' ) - ->andReturn( '' ); + ->with( $this->bn_code_option_name, $this->default_bn_code ) + ->andReturn( $this->default_bn_code ); - $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes ); + $partner_attribution = new PartnerAttribution( $this->bn_code_option_name, $this->bn_codes, $this->default_bn_code ); - $this->assertSame( '', $partner_attribution->get_bn_code() ); + $this->assertSame( $this->default_bn_code, $partner_attribution->get_bn_code() ); } } From 431a8e5cf776941d3b349f412504439bf305584d Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 18:06:50 +0400 Subject: [PATCH 19/30] Rebase --- .../ppcp-button/src/Assets/SmartButton.php | 24 ++++++++++++------- .../src/PayLaterBlockRenderer.php | 6 ++++- .../src/PayLaterConfiguratorModule.php | 6 +++-- .../src/PayLaterWCBlocksRenderer.php | 6 ++++- modules/ppcp-settings/src/SettingsModule.php | 6 ++++- 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 477f73cb8..5cd26fcbe 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -21,6 +21,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint; @@ -245,6 +246,13 @@ class SmartButton implements SmartButtonInterface { */ private CardPaymentsConfiguration $dcc_configuration; + /** + * The PayPal Partner Attribution Helper. + * + * @var PartnerAttribution + */ + protected PartnerAttribution $partner_attribution; + /** * SmartButton constructor. * @@ -273,6 +281,7 @@ class SmartButton implements SmartButtonInterface { * @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. + * @param PartnerAttribution $partner_attribution The PayPal Partner Attribution Helper. */ public function __construct( string $module_url, @@ -299,7 +308,8 @@ class SmartButton implements SmartButtonInterface { LoggerInterface $logger, bool $should_handle_shipping_in_paypal, DisabledFundingSources $disabled_funding_sources, - CardPaymentsConfiguration $dcc_configuration + CardPaymentsConfiguration $dcc_configuration, + PartnerAttribution $partner_attribution ) { $this->module_url = $module_url; $this->version = $version; @@ -326,6 +336,7 @@ class SmartButton implements SmartButtonInterface { $this->should_handle_shipping_in_paypal = $should_handle_shipping_in_paypal; $this->disabled_funding_sources = $disabled_funding_sources; $this->dcc_configuration = $dcc_configuration; + $this->partner_attribution = $partner_attribution; } /** @@ -841,9 +852,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages */ do_action( "ppcp_before_{$location_hook}_message_wrapper" ); - $bn_code = PPCP_PAYPAL_BN_CODE; - - $messages_placeholder = '
'; + $messages_placeholder = '
'; if ( is_array( $block_params ) && ( $block_params['blockName'] ?? false ) ) { $this->render_after_block( @@ -1540,12 +1549,9 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages * @return string */ private function bn_code_for_context( string $context ): string { - $codes = $this->bn_codes(); - $bn_code = PPCP_PAYPAL_BN_CODE; - - return ( isset( $codes[ $context ] ) ) ? $codes[ $context ] : $bn_code; + return ( isset( $codes[ $context ] ) ) ? $codes[ $context ] : $this->partner_attribution->get_bn_code(); } /** @@ -1555,7 +1561,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages */ private function bn_codes() : array { - $bn_code = PPCP_PAYPAL_BN_CODE; + $bn_code = $this->partner_attribution->get_bn_code(); return array( 'checkout' => $bn_code, diff --git a/modules/ppcp-paylater-block/src/PayLaterBlockRenderer.php b/modules/ppcp-paylater-block/src/PayLaterBlockRenderer.php index 74e8586b3..2408ba091 100644 --- a/modules/ppcp-paylater-block/src/PayLaterBlockRenderer.php +++ b/modules/ppcp-paylater-block/src/PayLaterBlockRenderer.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\PayLaterBlock; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** @@ -28,7 +29,10 @@ class PayLaterBlockRenderer { public function render( array $attributes, ContainerInterface $c ): string { if ( PayLaterBlockModule::is_block_enabled( $c->get( 'wcgateway.settings.status' ) ) ) { - $bn_code = PPCP_PAYPAL_BN_CODE; + $partner_attribution = $c->get( 'api.helper.partner-attribution' ); + assert( $partner_attribution instanceof PartnerAttribution ); + + $bn_code = $partner_attribution->get_bn_code(); $html = '
'; diff --git a/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php b/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php index 71f33e1da..b877d9545 100644 --- a/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php +++ b/modules/ppcp-paylater-configurator/src/PayLaterConfiguratorModule.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\PayLaterConfigurator; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; use WooCommerce\PayPalCommerce\PayLaterConfigurator\Endpoint\GetConfig; use WooCommerce\PayPalCommerce\PayLaterConfigurator\Endpoint\SaveConfig; use WooCommerce\PayPalCommerce\PayLaterConfigurator\Factory\ConfigFactory; @@ -126,7 +127,8 @@ class PayLaterConfiguratorModule implements ServiceModule, ExtendingModule, Exec $config_factory = $c->get( 'paylater-configurator.factory.config' ); assert( $config_factory instanceof ConfigFactory ); - $bn_code = PPCP_PAYPAL_BN_CODE; + $partner_attribution = $c->get( 'api.helper.partner-attribution' ); + assert( $partner_attribution instanceof PartnerAttribution ); wp_localize_script( 'ppcp-paylater-configurator', @@ -145,7 +147,7 @@ class PayLaterConfiguratorModule implements ServiceModule, ExtendingModule, Exec 'config' => $config_factory->from_settings( $settings ), 'merchantClientId' => $settings->get( 'client_id' ), 'partnerClientId' => $c->get( 'api.partner_merchant_id' ), - 'bnCode' => $bn_code, + 'bnCode' => $partner_attribution->get_bn_code(), 'publishButtonClassName' => 'ppcp-paylater-configurator-publishButton', 'headerClassName' => 'ppcp-paylater-configurator-header', 'subheaderClassName' => 'ppcp-paylater-configurator-subheader', diff --git a/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksRenderer.php b/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksRenderer.php index f0720300b..8292f312f 100644 --- a/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksRenderer.php +++ b/modules/ppcp-paylater-wc-blocks/src/PayLaterWCBlocksRenderer.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\PayLaterWCBlocks; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** @@ -95,7 +96,10 @@ class PayLaterWCBlocksRenderer { ) { if ( PayLaterWCBlocksModule::is_placement_enabled( $c->get( 'wcgateway.settings.status' ), $location ) ) { - $bn_code = PPCP_PAYPAL_BN_CODE; + $partner_attribution = $c->get( 'api.helper.partner-attribution' ); + assert( $partner_attribution instanceof PartnerAttribution ); + + $bn_code = $partner_attribution->get_bn_code(); $html = '
'; diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php index 9be213423..9b07939a7 100644 --- a/modules/ppcp-settings/src/SettingsModule.php +++ b/modules/ppcp-settings/src/SettingsModule.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\Settings; use WC_Payment_Gateway; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus; @@ -236,6 +237,9 @@ class SettingsModule implements ServiceModule, ExecutableModule { ); if ( $is_pay_later_configurator_available ) { + $partner_attribution = $container->get( 'api.helper.partner-attribution' ); + assert( $partner_attribution instanceof PartnerAttribution ); + wp_enqueue_script( 'ppcp-paylater-configurator-lib', 'https://www.paypalobjects.com/merchant-library/merchant-configurator.js', @@ -251,7 +255,7 @@ class SettingsModule implements ServiceModule, ExecutableModule { 'config' => array(), 'merchantClientId' => $settings->get( 'client_id' ), 'partnerClientId' => $container->get( 'api.partner_merchant_id' ), - 'bnCode' => PPCP_PAYPAL_BN_CODE, + 'bnCode' => $partner_attribution->get_bn_code(), ); } From 0aecc5aa32371b9b693bb5a949bcf5ba34b8b847 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 16:15:48 +0400 Subject: [PATCH 20/30] Pass the partner attribution helper to a smart button --- modules/ppcp-button/services.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php index a11516b82..18d9e27e4 100644 --- a/modules/ppcp-button/services.php +++ b/modules/ppcp-button/services.php @@ -168,7 +168,8 @@ return array( $container->get( 'woocommerce.logger.woocommerce' ), $container->get( 'button.handle-shipping-in-paypal' ), $container->get( 'button.helper.disabled-funding-sources' ), - $container->get( 'wcgateway.configuration.card-configuration' ) + $container->get( 'wcgateway.configuration.card-configuration' ), + $container->get( 'api.helper.partner-attribution' ) ); }, 'button.url' => static function ( ContainerInterface $container ): string { From c7818412655a69a8ad9dd61f4bc8899e862b4d43 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 16:22:43 +0400 Subject: [PATCH 21/30] `BillingAgreementsEndpoint`: Add the `'PayPal-Partner-Attribution-Id'` header --- modules/ppcp-api-client/services.php | 1 + .../Endpoint/BillingAgreementsEndpoint.php | 28 +++++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 6cc7d2382..06e78e29e 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -279,6 +279,7 @@ return array( return new BillingAgreementsEndpoint( $container->get( 'api.host' ), $container->get( 'api.bearer' ), + $container->get( 'api.helper.partner-attribution' ), $container->get( 'woocommerce.logger.woocommerce' ) ); }, diff --git a/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php b/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php index 954be01b5..729c87436 100644 --- a/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php @@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use Psr\Log\LoggerInterface; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; /** * Class BillingAgreementsEndpoint @@ -36,6 +37,13 @@ class BillingAgreementsEndpoint { */ private $bearer; + /** + * The PayPal Partner Attribution Helper. + * + * @var PartnerAttribution + */ + protected PartnerAttribution $partner_attribution; + /** * The logger. * @@ -46,18 +54,21 @@ class BillingAgreementsEndpoint { /** * BillingAgreementsEndpoint constructor. * - * @param string $host The host. - * @param Bearer $bearer The bearer. - * @param LoggerInterface $logger The logger. + * @param string $host The host. + * @param Bearer $bearer The bearer. + * @param PartnerAttribution $partner_attribution The PayPal Partner Attribution Helper. + * @param LoggerInterface $logger The logger. */ public function __construct( string $host, Bearer $bearer, + PartnerAttribution $partner_attribution, LoggerInterface $logger ) { - $this->host = $host; - $this->bearer = $bearer; - $this->logger = $logger; + $this->host = $host; + $this->bearer = $bearer; + $this->partner_attribution = $partner_attribution; + $this->logger = $logger; } /** @@ -91,8 +102,9 @@ class BillingAgreementsEndpoint { $args = array( 'method' => 'POST', 'headers' => array( - 'Authorization' => 'Bearer ' . $bearer->token(), - 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer ' . $bearer->token(), + 'Content-Type' => 'application/json', + 'PayPal-Partner-Attribution-Id' => $this->partner_attribution->get_bn_code(), ), 'body' => wp_json_encode( $data ), ); From 00c630ea5e6414c393b97d8a23178f714b141f08 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 16:25:28 +0400 Subject: [PATCH 22/30] Revert the changes. WIll use the filter to add it everywhere at once --- modules/ppcp-api-client/services.php | 1 - .../Endpoint/BillingAgreementsEndpoint.php | 28 ++++++------------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 06e78e29e..6cc7d2382 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -279,7 +279,6 @@ return array( return new BillingAgreementsEndpoint( $container->get( 'api.host' ), $container->get( 'api.bearer' ), - $container->get( 'api.helper.partner-attribution' ), $container->get( 'woocommerce.logger.woocommerce' ) ); }, diff --git a/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php b/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php index 729c87436..954be01b5 100644 --- a/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php @@ -15,7 +15,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use Psr\Log\LoggerInterface; -use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; /** * Class BillingAgreementsEndpoint @@ -37,13 +36,6 @@ class BillingAgreementsEndpoint { */ private $bearer; - /** - * The PayPal Partner Attribution Helper. - * - * @var PartnerAttribution - */ - protected PartnerAttribution $partner_attribution; - /** * The logger. * @@ -54,21 +46,18 @@ class BillingAgreementsEndpoint { /** * BillingAgreementsEndpoint constructor. * - * @param string $host The host. - * @param Bearer $bearer The bearer. - * @param PartnerAttribution $partner_attribution The PayPal Partner Attribution Helper. - * @param LoggerInterface $logger The logger. + * @param string $host The host. + * @param Bearer $bearer The bearer. + * @param LoggerInterface $logger The logger. */ public function __construct( string $host, Bearer $bearer, - PartnerAttribution $partner_attribution, LoggerInterface $logger ) { - $this->host = $host; - $this->bearer = $bearer; - $this->partner_attribution = $partner_attribution; - $this->logger = $logger; + $this->host = $host; + $this->bearer = $bearer; + $this->logger = $logger; } /** @@ -102,9 +91,8 @@ class BillingAgreementsEndpoint { $args = array( 'method' => 'POST', 'headers' => array( - 'Authorization' => 'Bearer ' . $bearer->token(), - 'Content-Type' => 'application/json', - 'PayPal-Partner-Attribution-Id' => $this->partner_attribution->get_bn_code(), + 'Authorization' => 'Bearer ' . $bearer->token(), + 'Content-Type' => 'application/json', ), 'body' => wp_json_encode( $data ), ); From fefcd25a48522056e19532f53e7cfc8626451fce Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 17:27:52 +0400 Subject: [PATCH 23/30] Use a filter that ensures that all API requests include the appropriate BN code retrieved from the `PartnerAttribution` helper. Using this approach avoids the need for extensive refactoring of existing classes that use the `RequestTrait`. --- modules/ppcp-api-client/src/ApiModule.php | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/modules/ppcp-api-client/src/ApiModule.php b/modules/ppcp-api-client/src/ApiModule.php index a2880761c..21c17caca 100644 --- a/modules/ppcp-api-client/src/ApiModule.php +++ b/modules/ppcp-api-client/src/ApiModule.php @@ -14,6 +14,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient; +use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; @@ -133,6 +134,34 @@ class ApiModule implements ServiceModule, ExtendingModule, ExecutableModule { } ); + /** + * Filters the request arguments to add the `'PayPal-Partner-Attribution-Id'` header. + * + * This ensures that all API requests include the appropriate BN code retrieved + * from the `PartnerAttribution` helper. Using this approach avoids the need + * for extensive refactoring of existing classes that use the `RequestTrait`. + * + * The filter is applied in {@see RequestTrait::request()} before making an API request. + * + * @see PartnerAttribution::get_bn_code() Retrieves the BN code dynamically. + * @see RequestTrait::request() Where the filter `ppcp_request_args` is applied. + */ + add_filter( + 'ppcp_request_args', + static function ( array $args ) use ( $c ) { + if ( isset( $args['headers']['PayPal-Partner-Attribution-Id'] ) ) { + return $args; + } + + $partner_attribution = $c->get( 'api.helper.partner-attribution' ); + assert( $partner_attribution instanceof PartnerAttribution ); + + $args['headers']['PayPal-Partner-Attribution-Id'] = $partner_attribution->get_bn_code(); + + return $args; + } + ); + return true; } } From 9d3e5c96906a03238a6e87576432d41995535412 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 17:28:37 +0400 Subject: [PATCH 24/30] the `'PayPal-Partner-Attribution-Id'` is not added from here anymore --- modules/ppcp-api-client/src/Endpoint/RequestTrait.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/RequestTrait.php b/modules/ppcp-api-client/src/Endpoint/RequestTrait.php index cb5f3c543..6d7a8ee4b 100644 --- a/modules/ppcp-api-client/src/Endpoint/RequestTrait.php +++ b/modules/ppcp-api-client/src/Endpoint/RequestTrait.php @@ -41,9 +41,6 @@ trait RequestTrait { * added here. */ $args = apply_filters( 'ppcp_request_args', $args, $url ); - if ( ! isset( $args['headers']['PayPal-Partner-Attribution-Id'] ) ) { - $args['headers']['PayPal-Partner-Attribution-Id'] = PPCP_PAYPAL_BN_CODE; - } $response = wp_remote_get( $url, $args ); if ( $this->is_request_logging_enabled ) { From ce5039e9c877717b7ca65633f92ed2396a79b380 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 18:36:33 +0400 Subject: [PATCH 25/30] Init the BN code --- modules/ppcp-settings/src/SettingsModule.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php index 9b07939a7..1a9b5b08f 100644 --- a/modules/ppcp-settings/src/SettingsModule.php +++ b/modules/ppcp-settings/src/SettingsModule.php @@ -163,7 +163,14 @@ class SettingsModule implements ServiceModule, ExecutableModule { $path_repository = $container->get( 'settings.service.branded-experience.path-repository' ); assert( $path_repository instanceof PathRepository ); + $partner_attribution = $container->get( 'api.helper.partner-attribution' ); + assert( $partner_attribution instanceof PartnerAttribution ); + + $general_settings = $container->get( 'settings.data.general' ); + assert( $general_settings instanceof GeneralSettings ); + $path_repository->persist(); + $partner_attribution->initialize_bn_code( $general_settings->get_installation_path() ); } ); From 8ea71593d6fcf120626b68467d1cea2d2f3728e8 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 18:59:08 +0400 Subject: [PATCH 26/30] Adjust the mapping of installation paths to BN codes --- modules/ppcp-api-client/services.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 6cc7d2382..a3a7030a2 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -32,6 +32,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\RefundPayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\SellerPayableBreakdownFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingOptionFactory; use WooCommerce\PayPalCommerce\Session\SessionHandler; +use WooCommerce\PayPalCommerce\Settings\Service\BrandedExperience\ActivationDetector; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer; @@ -971,7 +972,8 @@ return array( */ 'api.bn-codes' => static function (): array { return array( - 'core-profiler' => 'WooPPCP_Ecom_PS_CoreProfiler', + ActivationDetector::CORE_PROFILER => 'WooPPCP_Ecom_PS_CoreProfiler', + ActivationDetector::PAYMENT_SETTINGS => 'WooPPCP_Ecom_PS_CoreProfiler', ); }, 'api.helper.partner-attribution' => static function ( ContainerInterface $container ) : PartnerAttribution { From 95af76b8fb6430b973115fea48dbb856604fa85d Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 19:02:19 +0400 Subject: [PATCH 27/30] Remove the mapping service and path the array directly --- modules/ppcp-api-client/services.php | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index a3a7030a2..a06afe8dd 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -965,21 +965,13 @@ return array( return CONNECT_WOO_URL; }, - /** - * BN Codes mapping for different installation paths. - * - * @returns array The map of installation paths to BN Codes. - */ - 'api.bn-codes' => static function (): array { - return array( - ActivationDetector::CORE_PROFILER => 'WooPPCP_Ecom_PS_CoreProfiler', - ActivationDetector::PAYMENT_SETTINGS => 'WooPPCP_Ecom_PS_CoreProfiler', - ); - }, 'api.helper.partner-attribution' => static function ( ContainerInterface $container ) : PartnerAttribution { return new PartnerAttribution( 'ppcp_bn_code', - $container->get( 'api.bn-codes' ), + array( + ActivationDetector::CORE_PROFILER => 'WooPPCP_Ecom_PS_CoreProfiler', + ActivationDetector::PAYMENT_SETTINGS => 'WooPPCP_Ecom_PS_CoreProfiler', + ), PPCP_PAYPAL_BN_CODE ); }, From 2616974c123c7b066716d73cec26a66b2d602cdc Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 19:06:09 +0400 Subject: [PATCH 28/30] Restrict the BN code to return only the white listed values --- modules/ppcp-api-client/src/Helper/PartnerAttribution.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-api-client/src/Helper/PartnerAttribution.php b/modules/ppcp-api-client/src/Helper/PartnerAttribution.php index 486c579a6..f83aeb292 100644 --- a/modules/ppcp-api-client/src/Helper/PartnerAttribution.php +++ b/modules/ppcp-api-client/src/Helper/PartnerAttribution.php @@ -80,6 +80,12 @@ class PartnerAttribution { * @return string The stored BN Code, or the default value if no path is detected. */ public function get_bn_code(): string { - return get_option( $this->bn_code_option_name, $this->default_bn_code ) ?? $this->default_bn_code; + $bn_code = get_option( $this->bn_code_option_name, $this->default_bn_code ) ?? $this->default_bn_code; + + if ( ! in_array( $bn_code, $this->bn_codes, true ) && $bn_code !== $this->default_bn_code ) { + return $this->default_bn_code; + } + + return $bn_code; } } From 84909725139ddd580ac593d976ff40ec83525299 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 19:09:37 +0400 Subject: [PATCH 29/30] Check if the `headers` exist in the request --- modules/ppcp-api-client/src/ApiModule.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ppcp-api-client/src/ApiModule.php b/modules/ppcp-api-client/src/ApiModule.php index 21c17caca..12cdd12a6 100644 --- a/modules/ppcp-api-client/src/ApiModule.php +++ b/modules/ppcp-api-client/src/ApiModule.php @@ -156,6 +156,10 @@ class ApiModule implements ServiceModule, ExtendingModule, ExecutableModule { $partner_attribution = $c->get( 'api.helper.partner-attribution' ); assert( $partner_attribution instanceof PartnerAttribution ); + if ( ! isset( $args['headers'] ) || ! is_array( $args['headers'] ) ) { + $args['headers'] = array(); + } + $args['headers']['PayPal-Partner-Attribution-Id'] = $partner_attribution->get_bn_code(); return $args; From af44af281807500268f8da9c0de355082bde7581 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 21 Mar 2025 19:15:21 +0400 Subject: [PATCH 30/30] Use the constant in tests --- tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php b/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php index 504526638..bf99e29e0 100644 --- a/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php +++ b/tests/PHPUnit/ApiClient/Helper/PartnerAttributionTest.php @@ -5,6 +5,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Helper; use PHPUnit\Framework\TestCase; +use WooCommerce\PayPalCommerce\Settings\Service\BrandedExperience\ActivationDetector; use function Brain\Monkey\Functions\when; use function Brain\Monkey\Functions\expect; use function Brain\Monkey\setUp; @@ -27,9 +28,10 @@ class PartnerAttributionTest extends TestCase { * * @var array */ - private array $bn_codes = [ - 'core-profiler' => 'WooPPCP_Ecom_PS_CoreProfiler', - ]; + private array $bn_codes = array( + ActivationDetector::CORE_PROFILER => 'WooPPCP_Ecom_PS_CoreProfiler', + ActivationDetector::PAYMENT_SETTINGS => 'WooPPCP_Ecom_PS_CoreProfiler', + ); /** * The default BN code.