Commit 28c96bb1cd9 for woocommerce
commit 28c96bb1cd9a7160f05d8f916ca607571d738ece
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date: Tue Jul 7 10:56:43 2026 +0200
Fix is_vat_exempt being not correctly applied during block checkout for logged-in users (#66342)
diff --git a/plugins/woocommerce/changelog/fix-customer-vat-excempt-save-blocks-checkout b/plugins/woocommerce/changelog/fix-customer-vat-excempt-save-blocks-checkout
new file mode 100644
index 00000000000..8b2abfcf297
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-customer-vat-excempt-save-blocks-checkout
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix is_vat_exempt being not correctly applied during block checkout for logged-in users.
diff --git a/plugins/woocommerce/includes/class-wc-customer.php b/plugins/woocommerce/includes/class-wc-customer.php
index 1fed289435c..8b0d2f7f13f 100644
--- a/plugins/woocommerce/includes/class-wc-customer.php
+++ b/plugins/woocommerce/includes/class-wc-customer.php
@@ -1266,27 +1266,4 @@ class WC_Customer extends WC_Legacy_Customer {
public function set_is_paying_customer( $is_paying_customer ) {
$this->set_prop( 'is_paying_customer', (bool) $is_paying_customer );
}
-
- /**
- * Overrides the save method to guard against saves with no data changed.
- *
- * @since 10.9.0
- * @return int
- */
- public function save() {
- $customer_id = $this->get_id();
- if ( $customer_id ) {
- $meta_data = $this->meta_data ?? array();
- $props_changed = ! empty( $this->password ) || ! empty( $this->changes );
- $state_changed = $props_changed || ! empty( array_filter( $meta_data, static fn( $meta ) => ! $meta->id || ! empty( $meta->get_changes() ) ) );
- if ( ! $state_changed ) {
- // Backward compatibility: e.g. '( new WC_Customer( $customer_id ) )->save()' as means to trigger integrations.
- do_action( 'woocommerce_update_customer', $customer_id, $this ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.HookCommentWrongStyle
-
- return $this->get_id();
- }
- }
-
- return parent::save();
- }
}
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-customer-test.php b/plugins/woocommerce/tests/php/includes/class-wc-customer-test.php
index 7846e954445..39717ad937b 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-customer-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-customer-test.php
@@ -1,8 +1,66 @@
<?php
+/**
+ * Unit tests for WC_Customer class.
+ *
+ * @package WooCommerce\Tests.
+ */
+
+declare( strict_types = 1 );
+
/**
* Tests for WC_Customer class.
*/
class WC_Customer_Test extends \WC_Unit_Test_Case {
+ /**
+ * Data provider: setters that write directly to a class property instead of going through set_prop().
+ *
+ * @return array<string,array>
+ */
+ public function data_provider_set_prop_bypassing_setters(): array {
+ return array(
+ 'password' => array(
+ false,
+ fn( WC_Customer $customer ) => $customer->set_password( '***' ),
+ fn( int $id ) => wp_check_password( '***', get_user_by( 'id', $id )->user_pass ),
+ ),
+ 'is_vat_exempt' => array(
+ true,
+ fn( WC_Customer $customer ) => $customer->set_is_vat_exempt( true ),
+ fn( int $id ) => ( new WC_Customer( $id, true ) )->get_is_vat_exempt(),
+ ),
+ 'calculated_shipping' => array(
+ true,
+ fn( WC_Customer $customer ) => $customer->set_calculated_shipping( true ),
+ fn( int $id ) => ( new WC_Customer( $id, true ) )->get_calculated_shipping(),
+ ),
+ );
+ }
+
+ /**
+ * @testdox Setters that bypass set_prop() must still persist their value when save() is called on a logged-in customer with no other pending changes.
+ * @dataProvider data_provider_set_prop_bypassing_setters
+ *
+ * @param bool $use_session True = session data store (Block Checkout); false = DB data store.
+ * @param callable $set Mutates the customer (calls the setter under test).
+ * @param callable $verify Returns true when the value was persisted after save().
+ */
+ public function test_set_prop_bypass_is_persisted_on_save( bool $use_session, callable $set, callable $verify ): void {
+ $user_id = WC_Helper_Customer::create_customer()->get_id();
+
+ if ( $use_session ) {
+ WC()->session->init();
+ }
+ $customer = new WC_Customer( $user_id, $use_session );
+
+ // Precondition: no pending WC_Data changes — same state extensions see at checkout.
+ $this->assertEmpty( $customer->get_changes() );
+
+ $set( $customer );
+ $customer->save();
+
+ $this->assertTrue( $verify( $user_id ) );
+ }
+
/**
* Test that customer object can be initialized even if wc session is not available.
* There are cases when WC()->session is null but we are reading customer object with $session param set to true, for example, when calling methods from WC_Checkout object.