From 3836771dae1d85ec7ceb627b32e64ed9852bad26 Mon Sep 17 00:00:00 2001 From: "Alex P." Date: Wed, 27 Aug 2025 16:58:36 +0300 Subject: [PATCH 1/3] Use NO_SHIPPING when shipping not needed --- .../ppcp-api-client/src/Factory/ShippingPreferenceFactory.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php b/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php index 3c3cee0df..9cb71dedf 100644 --- a/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php +++ b/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php @@ -42,6 +42,10 @@ class ShippingPreferenceFactory { $needs_shipping = $cart && $cart->needs_shipping(); $shipping_address_is_fixed = $needs_shipping && 'checkout' === $context; + if ( ! $needs_shipping ) { + return ExperienceContext::SHIPPING_PREFERENCE_NO_SHIPPING; + } + if ( $shipping_address_is_fixed ) { // Checkout + no address given? Probably something weird happened, like no form validation? if ( ! $has_shipping ) { From 5d5775e02b68b8feb9d70a5e127ceeb5df1f89d2 Mon Sep 17 00:00:00 2001 From: "Alex P." Date: Wed, 27 Aug 2025 16:59:35 +0300 Subject: [PATCH 2/3] Use SET_PROVIDED_ADDRESS in Pay for order --- .../src/Factory/ShippingPreferenceFactory.php | 8 +++--- .../src/Endpoint/CreateOrderEndpoint.php | 3 ++- .../Factory/ShippingPreferenceFactoryTest.php | 27 +++++++++++++++---- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php b/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php index 9cb71dedf..8c688a33a 100644 --- a/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php +++ b/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient\Factory; use WC_Cart; +use WC_Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\ExperienceContext; use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; @@ -31,7 +32,8 @@ class ShippingPreferenceFactory { PurchaseUnit $purchase_unit, string $context, ?WC_Cart $cart = null, - string $funding_source = '' + string $funding_source = '', + ?WC_Order $wc_order = null ): string { $contains_physical_goods = $purchase_unit->contains_physical_goods(); if ( ! $contains_physical_goods ) { @@ -39,8 +41,8 @@ class ShippingPreferenceFactory { } $has_shipping = null !== $purchase_unit->shipping(); - $needs_shipping = $cart && $cart->needs_shipping(); - $shipping_address_is_fixed = $needs_shipping && 'checkout' === $context; + $needs_shipping = ( $wc_order && $wc_order->needs_shipping() ) || ( $cart && $cart->needs_shipping() ); + $shipping_address_is_fixed = $needs_shipping && in_array( $context, array( 'checkout', 'pay-now' ), true ); if ( ! $needs_shipping ) { return ExperienceContext::SHIPPING_PREFERENCE_NO_SHIPPING; diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index cd8b6c594..ec4ce107d 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -440,7 +440,8 @@ class CreateOrderEndpoint implements EndpointInterface { $this->purchase_unit, $this->parsed_request_data['context'], WC()->cart, - $funding_source + $funding_source, + $wc_order ); $action = in_array( $this->parsed_request_data['context'], $this->pay_now_contexts, true ) ? diff --git a/tests/PHPUnit/ApiClient/Factory/ShippingPreferenceFactoryTest.php b/tests/PHPUnit/ApiClient/Factory/ShippingPreferenceFactoryTest.php index d16dc2dac..4d3cbd0f4 100644 --- a/tests/PHPUnit/ApiClient/Factory/ShippingPreferenceFactoryTest.php +++ b/tests/PHPUnit/ApiClient/Factory/ShippingPreferenceFactoryTest.php @@ -5,6 +5,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Factory; use Mockery; use WC_Cart; +use WC_Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\ExperienceContext; use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping; @@ -29,9 +30,10 @@ class ShippingPreferenceFactoryTest extends TestCase string $context, ?WC_Cart $cart, string $funding_source, + ?WC_Order $wc_order, string $expected_result ) { - $result = $this->testee->from_state($purchase_unit, $context, $cart, $funding_source); + $result = $this->testee->from_state($purchase_unit, $context, $cart, $funding_source, $wc_order); self::assertEquals($expected_result, $result); } @@ -43,6 +45,7 @@ class ShippingPreferenceFactoryTest extends TestCase 'checkout', $this->createCart(true), '', + null, ExperienceContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS, ]; yield [ @@ -50,6 +53,7 @@ class ShippingPreferenceFactoryTest extends TestCase 'checkout', $this->createCart(false), '', + null, ExperienceContext::SHIPPING_PREFERENCE_NO_SHIPPING, ]; yield [ @@ -57,6 +61,7 @@ class ShippingPreferenceFactoryTest extends TestCase 'checkout', $this->createCart(true), '', + null, ExperienceContext::SHIPPING_PREFERENCE_NO_SHIPPING, ]; yield [ @@ -64,6 +69,7 @@ class ShippingPreferenceFactoryTest extends TestCase 'checkout', $this->createCart(true), 'card', + null, ExperienceContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS, ]; yield [ @@ -71,34 +77,39 @@ class ShippingPreferenceFactoryTest extends TestCase 'product', null, '', - ExperienceContext::SHIPPING_PREFERENCE_GET_FROM_FILE, + null, + ExperienceContext::SHIPPING_PREFERENCE_NO_SHIPPING ]; yield [ $this->createPurchaseUnit(true, null), 'pay-now', null, 'venmo', - ExperienceContext::SHIPPING_PREFERENCE_GET_FROM_FILE, + $this->createWcOrder(false), + ExperienceContext::SHIPPING_PREFERENCE_NO_SHIPPING ]; yield [ $this->createPurchaseUnit(true, Mockery::mock(Shipping::class)), 'pay-now', null, 'venmo', - ExperienceContext::SHIPPING_PREFERENCE_GET_FROM_FILE, + $this->createWcOrder(true), + ExperienceContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS ]; yield [ $this->createPurchaseUnit(true, Mockery::mock(Shipping::class)), 'pay-now', null, 'card', - ExperienceContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS, + $this->createWcOrder(true), + ExperienceContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS ]; yield [ $this->createPurchaseUnit(true, null), 'pay-now', null, 'card', + $this->createWcOrder(false), ExperienceContext::SHIPPING_PREFERENCE_NO_SHIPPING, ]; } @@ -115,4 +126,10 @@ class ShippingPreferenceFactoryTest extends TestCase $cart->shouldReceive('needs_shipping')->andReturn($needsShipping); return $cart; } + + private function createWcOrder(bool $needsShipping): WC_Order { + $wcOrder = Mockery::mock(WC_Order::class); + $wcOrder->shouldReceive('needs_shipping')->andReturn($needsShipping); + return $wcOrder; + } } From 049b31b6115e0a27780f82cf8e8012f78377c9fd Mon Sep 17 00:00:00 2001 From: "Alex P." Date: Wed, 27 Aug 2025 17:17:26 +0300 Subject: [PATCH 3/3] Fix older WC compatibility --- .../src/Factory/ShippingPreferenceFactory.php | 26 ++++++++++++++++++- .../Factory/ShippingPreferenceFactoryTest.php | 14 +++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php b/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php index 8c688a33a..7416560b3 100644 --- a/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php +++ b/modules/ppcp-api-client/src/Factory/ShippingPreferenceFactory.php @@ -11,6 +11,8 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Factory; use WC_Cart; use WC_Order; +use WC_Order_Item_Product; +use WC_Product; use WooCommerce\PayPalCommerce\ApiClient\Entity\ExperienceContext; use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; @@ -41,7 +43,7 @@ class ShippingPreferenceFactory { } $has_shipping = null !== $purchase_unit->shipping(); - $needs_shipping = ( $wc_order && $wc_order->needs_shipping() ) || ( $cart && $cart->needs_shipping() ); + $needs_shipping = ( $wc_order && $this->wc_order_needs_shipping( $wc_order ) ) || ( $cart && $cart->needs_shipping() ); $shipping_address_is_fixed = $needs_shipping && in_array( $context, array( 'checkout', 'pay-now' ), true ); if ( ! $needs_shipping ) { @@ -67,4 +69,26 @@ class ShippingPreferenceFactory { return ExperienceContext::SHIPPING_PREFERENCE_GET_FROM_FILE; } + + protected function wc_order_needs_shipping( WC_Order $wc_order ): bool { + // WC 9.9.0+. + if ( method_exists( $wc_order, 'needs_shipping' ) ) { + return $wc_order->needs_shipping(); + } + + if ( ! wc_shipping_enabled() || wc_get_shipping_method_count( true ) === 0 ) { + return false; + } + + foreach ( $wc_order->get_items() as $item ) { + if ( $item instanceof WC_Order_Item_Product ) { + $product = $item->get_product(); + if ( $product instanceof WC_Product && $product->needs_shipping() ) { + return true; + } + } + } + + return false; + } } diff --git a/tests/PHPUnit/ApiClient/Factory/ShippingPreferenceFactoryTest.php b/tests/PHPUnit/ApiClient/Factory/ShippingPreferenceFactoryTest.php index 4d3cbd0f4..99e9f5988 100644 --- a/tests/PHPUnit/ApiClient/Factory/ShippingPreferenceFactoryTest.php +++ b/tests/PHPUnit/ApiClient/Factory/ShippingPreferenceFactoryTest.php @@ -6,10 +6,13 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Factory; use Mockery; use WC_Cart; use WC_Order; +use WC_Order_Item_Product; +use WC_Product; use WooCommerce\PayPalCommerce\ApiClient\Entity\ExperienceContext; use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping; use WooCommerce\PayPalCommerce\TestCase; +use function Brain\Monkey\Functions\when; class ShippingPreferenceFactoryTest extends TestCase { @@ -19,6 +22,9 @@ class ShippingPreferenceFactoryTest extends TestCase { parent::setUp(); + when('wc_shipping_enabled')->justReturn(true); + when('wc_get_shipping_method_count')->justReturn(2); + $this->testee = new ShippingPreferenceFactory(); } @@ -128,8 +134,14 @@ class ShippingPreferenceFactoryTest extends TestCase } private function createWcOrder(bool $needsShipping): WC_Order { + $product = Mockery::mock(WC_Product::class); + $product->shouldReceive('needs_shipping')->andReturn($needsShipping); + + $item = Mockery::mock(WC_Order_Item_Product::class); + $item->shouldReceive('get_product')->andReturn($product); + $wcOrder = Mockery::mock(WC_Order::class); - $wcOrder->shouldReceive('needs_shipping')->andReturn($needsShipping); + $wcOrder->shouldReceive('get_items')->andReturn([$item]); return $wcOrder; } }