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.