Commit b3ce23d8aa2 for woocommerce

commit b3ce23d8aa2e5b41d7df18187082f446a766f2d2
Author: Seghir Nadir <nadir.seghir@gmail.com>
Date:   Wed Oct 7 12:58:27 2026 +0200

    Revert "Fix conditional checkout field rules for fields with no value yet" (#69521)

    * Revert "Fix conditional checkout field rules for fields with no value yet (#68220)"

    This reverts commit a0a5fdd5dce661de88a8520908019c811086d722.

    * Keep PHPStan baseline unchanged in CheckoutOrder revert

diff --git a/plugins/woocommerce/changelog/revert-68220-conditional-checkout-fields b/plugins/woocommerce/changelog/revert-68220-conditional-checkout-fields
new file mode 100644
index 00000000000..91d2b0227fd
--- /dev/null
+++ b/plugins/woocommerce/changelog/revert-68220-conditional-checkout-fields
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Revert recent changes to how conditional checkout field rules handle missing values.
diff --git a/plugins/woocommerce/client/blocks/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md b/plugins/woocommerce/client/blocks/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md
index ef5d5dd77ab..70fec879e22 100644
--- a/plugins/woocommerce/client/blocks/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md
+++ b/plugins/woocommerce/client/blocks/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md
@@ -15,7 +15,7 @@ WooCommerce currently uses two feature-flag systems in Blocks:

 | Flag | Defaults | Current Blocks usage |
 | --- | --- | --- |
-| `experimental-blocks` | Enabled in [development](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/admin/config/development.json) and disabled in [core builds](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/admin/config/core.json). | Exposed to the editor through [`isExperimentalBlocksEnabled()`](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/blocks/assets/js/settings/blocks/feature-flags.ts). It currently gates the **Disable product descriptions** editor control in Checkout Order Summary Cart Items. It does not determine which block scripts webpack builds or which general block types are registered. |
+| `experimental-blocks` | Enabled in [development](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/admin/config/development.json) and disabled in [core builds](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/admin/config/core.json). | Exposed to the editor through [`isExperimentalBlocksEnabled()`](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/blocks/assets/js/settings/blocks/feature-flags.ts). It currently gates the **Disable product descriptions** editor control in Checkout Order Summary Cart Items and conditional checkout-field processing in the Store API. It does not determine which block scripts webpack builds or which general block types are registered. |
 | `rest-api-v4` | Disabled in both development and core build configurations. | Exposed through [`isExperimentalWcRestApiV4Enabled()`](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/client/blocks/assets/js/settings/blocks/feature-flags.ts). It switches product entities from `/wc/v3/products` to `/wc/v4/products`, registers the settings entity, and enables the v4 product-data paths used by Product Price and Product Button in the editor. |

 ### Runtime feature flags
diff --git a/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php b/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php
index 013d662ef3e..cb53a67a90d 100644
--- a/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php
+++ b/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php
@@ -280,11 +280,10 @@ class Checkout extends AbstractCartRoute {
 		$invalid_details = [];
 		$is_partial      = in_array( $request->get_method(), [ 'PUT', 'PATCH' ], true );

-		$document_object = $this->get_document_object_from_rest_request( $request );
-
 		foreach ( $validate_contexts as $context => $context_data ) {
 			$errors = new \WP_Error();

+			$document_object = $this->get_document_object_from_rest_request( $request );
 			$document_object->set_context( $context );
 			$additional_fields = $this->additional_fields_controller->get_contextual_fields_for_location( $context_data['location'], $document_object );

@@ -926,10 +925,9 @@ class Checkout extends AbstractCartRoute {
 			],
 		];

-		$document_object = $this->get_document_object_from_rest_request( $request );
-
 		foreach ( $additional_field_contexts as $context => $context_data ) {

+			$document_object = $this->get_document_object_from_rest_request( $request );
 			$document_object->set_context( $context );
 			$additional_fields = $this->additional_fields_controller->get_contextual_fields_for_location( $context_data['location'], $document_object );

diff --git a/plugins/woocommerce/src/StoreApi/Routes/V1/CheckoutOrder.php b/plugins/woocommerce/src/StoreApi/Routes/V1/CheckoutOrder.php
index 87868393ce3..c798e747130 100644
--- a/plugins/woocommerce/src/StoreApi/Routes/V1/CheckoutOrder.php
+++ b/plugins/woocommerce/src/StoreApi/Routes/V1/CheckoutOrder.php
@@ -31,7 +31,7 @@ class CheckoutOrder extends AbstractCartRoute {
 	/**
 	 * Holds the current order being processed.
 	 *
-	 * @var \WC_Order|null
+	 * @var \WC_Order
 	 */
 	private $order = null;

@@ -201,7 +201,6 @@ class CheckoutOrder extends AbstractCartRoute {
 	 * @param \WP_REST_Request $request Full details about the request.
 	 */
 	private function update_billing_address( \WP_REST_Request $request ) {
-		$order    = $this->get_order_or_throw();
 		$customer = wc()->customer;

 		// Billing address is a required field.
@@ -210,9 +209,9 @@ class CheckoutOrder extends AbstractCartRoute {
 		// If shipping address (optional field) was not provided, set it to the given billing address (required field).
 		$shipping = $request['shipping_address'] ?? $billing;

-		$order->set_billing_address( $billing );
-		$order->set_shipping_address( $shipping );
-		$this->order_controller->validate_existing_order_before_update( $order );
+		$this->order->set_billing_address( $billing );
+		$this->order->set_shipping_address( $shipping );
+		$this->order_controller->validate_existing_order_before_update( $this->order );

 		// Update customer object with validated order addresses.
 		foreach ( $billing as $key => $value ) {
@@ -238,8 +237,8 @@ class CheckoutOrder extends AbstractCartRoute {
 		do_action( 'woocommerce_store_api_checkout_update_customer_from_request', $customer, $request );

 		$customer->save();
-		$order->save();
-		$order->calculate_totals();
+		$this->order->save();
+		$this->order->calculate_totals();
 	}

 	/**
@@ -253,7 +252,7 @@ class CheckoutOrder extends AbstractCartRoute {
 		$request_payment_method = wc_clean( wp_unslash( $request['payment_method'] ?? '' ) );

 		if ( empty( $request_payment_method ) ) {
-			if ( $this->get_order_or_throw()->needs_payment() ) {
+			if ( $this->order->needs_payment() ) {
 				throw new RouteException(
 					'woocommerce_rest_checkout_missing_payment_method',
 					__( 'No payment method provided.', 'woocommerce' ),
@@ -287,6 +286,6 @@ class CheckoutOrder extends AbstractCartRoute {
 	 * @param \WP_REST_Request $request Request object.
 	 */
 	private function process_customer( \WP_REST_Request $request ) {
-		$this->order_controller->sync_customer_data_with_order( $this->get_order_or_throw() );
+		$this->order_controller->sync_customer_data_with_order( $this->order );
 	}
 }
diff --git a/plugins/woocommerce/src/StoreApi/Schemas/V1/CheckoutSchema.php b/plugins/woocommerce/src/StoreApi/Schemas/V1/CheckoutSchema.php
index e79e79634c3..427dfc13910 100644
--- a/plugins/woocommerce/src/StoreApi/Schemas/V1/CheckoutSchema.php
+++ b/plugins/woocommerce/src/StoreApi/Schemas/V1/CheckoutSchema.php
@@ -324,17 +324,21 @@ class CheckoutSchema extends AbstractSchema {
 			)
 			: $this->additional_fields_controller->get_all_fields_from_object( $wc_object, 'other' );

-		$response = [];
-		foreach ( $this->get_additional_fields_schema() as $key => $field_schema ) {
+		$additional_field_schema = $this->get_additional_fields_schema();
+		foreach ( $fields as $key => $value ) {
+			if ( ! isset( $additional_field_schema[ $key ] ) ) {
+				unset( $fields[ $key ] );
+				continue;
+			}
 			// This makes sure we're casting checkboxes from "1" and "0" to boolean. In the frontend, "0" is treated as truthy.
-			if ( isset( $field_schema['type'] ) && 'boolean' === $field_schema['type'] ) {
-				$response[ $key ] = (bool) ( $fields[ $key ] ?? false );
+			if ( isset( $additional_field_schema[ $key ]['type'] ) && 'boolean' === $additional_field_schema[ $key ]['type'] ) {
+				$fields[ $key ] = (bool) $value;
 			} else {
-				$response[ $key ] = $this->prepare_html_response( $fields[ $key ] ?? '' );
+				$fields[ $key ] = $this->prepare_html_response( $value );
 			}
 		}

-		return (object) $response;
+		return (object) $fields;
 	}

 	/**
diff --git a/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php b/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php
index b2cf9d6ca57..bc5400652f8 100644
--- a/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php
+++ b/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php
@@ -7,6 +7,7 @@ use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
 use Automattic\WooCommerce\StoreApi\Payments\PaymentContext;
 use Automattic\WooCommerce\StoreApi\Payments\PaymentResult;
 use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFieldsSchema\DocumentObject;
+use Automattic\WooCommerce\Admin\Features\Features;
 use Automattic\WooCommerce\Enums\OrderStatus;
 use WC_Customer;

@@ -483,12 +484,19 @@ trait CheckoutTrait {
 	 * @param callable         $persist Callback invoked as `$persist( string $key, mixed $value )` for each field.
 	 */
 	private function resolve_and_persist_additional_fields( \WP_REST_Request $request, callable $persist ): void {
-		$document_object = $this->get_document_object_from_rest_request( $request );
-		$document_object->set_context( 'order' );
-		$additional_fields = array_merge(
-			$this->additional_fields_controller->get_contextual_fields_for_location( 'order', $document_object ),
-			$this->additional_fields_controller->get_contextual_fields_for_location( 'contact', $document_object )
-		);
+		if ( Features::is_enabled( 'experimental-blocks' ) ) {
+			$document_object = $this->get_document_object_from_rest_request( $request );
+			$document_object->set_context( 'order' );
+			$additional_fields = array_merge(
+				$this->additional_fields_controller->get_contextual_fields_for_location( 'order', $document_object ),
+				$this->additional_fields_controller->get_contextual_fields_for_location( 'contact', $document_object )
+			);
+		} else {
+			$additional_fields = array_merge(
+				$this->additional_fields_controller->get_fields_for_location( 'order' ),
+				$this->additional_fields_controller->get_fields_for_location( 'contact' )
+			);
+		}

 		$field_values = isset( $request['additional_fields'] ) ? (array) $request['additional_fields'] : array();

@@ -510,47 +518,27 @@ trait CheckoutTrait {
 	 * Returns a document object from a REST request.
 	 *
 	 * @param \WP_REST_Request $request The REST request.
-	 * @return DocumentObject The document object.
+	 * @return DocumentObject The document object or null if experimental blocks are not enabled.
 	 */
 	public function get_document_object_from_rest_request( \WP_REST_Request $request ) {
-		// Keep the order local so validation errors do not release its stock or coupon holds.
-		$order        = $this->order ?? $this->get_draft_order();
-		$saved_fields = wc()->customer instanceof WC_Customer
-			? $this->additional_fields_controller->get_all_fields_from_object( wc()->customer, 'other' )
-			: [];
-		if ( $order instanceof \WC_Order ) {
-			$saved_fields = wp_parse_args(
-				$this->additional_fields_controller->get_all_fields_from_object( $order, 'other' ),
-				$saved_fields
-			);
-		}
-
-		$field_values      = wp_parse_args( $request['additional_fields'] ?? [], $saved_fields );
-		$additional_fields = [];
-		$registered_fields = array_merge(
-			$this->additional_fields_controller->get_fields_for_location( 'contact' ),
-			$this->additional_fields_controller->get_fields_for_location( 'order' )
-		);
-
-		// Conditions need raw values and explicit empty values for missing fields.
-		foreach ( $registered_fields as $key => $field ) {
-			$additional_fields[ $key ] = 'checkbox' === $field['type']
-				? (bool) ( $field_values[ $key ] ?? false )
-				: ( $field_values[ $key ] ?? '' );
-		}
-
 		return new DocumentObject(
 			[
 				'customer' => [
 					'billing_address'   => $request['billing_address'],
 					'shipping_address'  => $request['shipping_address'],
-					'additional_fields' => $this->additional_fields_controller->filter_fields_for_location( $additional_fields, 'contact' ),
+					'additional_fields' => array_intersect_key(
+						$request['additional_fields'] ?? [],
+						array_flip( $this->additional_fields_controller->get_contact_fields_keys() )
+					),
 				],
 				'checkout' => [
 					'payment_method'    => $request['payment_method'],
 					'create_account'    => $request['create_account'],
 					'customer_note'     => $request['customer_note'],
-					'additional_fields' => $this->additional_fields_controller->filter_fields_for_location( $additional_fields, 'order' ),
+					'additional_fields' => array_intersect_key(
+						$request['additional_fields'] ?? [],
+						array_flip( $this->additional_fields_controller->get_order_fields_keys() )
+					),
 				],
 			]
 		);
diff --git a/plugins/woocommerce/tests/e2e/test-plugins/blocks/additional-checkout-fields.php b/plugins/woocommerce/tests/e2e/test-plugins/blocks/additional-checkout-fields.php
index 0b98bd7f5b9..ae2bfcf214d 100644
--- a/plugins/woocommerce/tests/e2e/test-plugins/blocks/additional-checkout-fields.php
+++ b/plugins/woocommerce/tests/e2e/test-plugins/blocks/additional-checkout-fields.php
@@ -325,38 +325,6 @@ class Additional_Checkout_Fields_Test_Helper {
 				),
 			)
 		);
-
-		// Only shown once "How did you hear about us?" is set to Facebook. Covers a rule that
-		// reads another additional field, which needs that field to have a value from the start.
-		woocommerce_register_additional_checkout_field(
-			array(
-				'id'       => 'third-plugin-namespace/facebook-page',
-				'label'    => 'Which Facebook page did you see us on?',
-				'location' => 'order',
-				'type'     => 'text',
-				'hidden'   => array(
-					'not' => array(
-						'type'       => 'object',
-						'properties' => array(
-							'checkout' => array(
-								'type'       => 'object',
-								'properties' => array(
-									'additional_fields' => array(
-										'type'       => 'object',
-										'properties' => array(
-											'third-plugin-namespace/how-did-you-hear-about-us' => array(
-												'type'  => 'string',
-												'const' => 'facebook',
-											),
-										),
-									),
-								),
-							),
-						),
-					),
-				),
-			)
-		);
 	}
 }

diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/checkout/additional-fields.guest-shopper.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/checkout/additional-fields.guest-shopper.block_theme.spec.ts
index cf45a55174d..626908309ce 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/checkout/additional-fields.guest-shopper.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/checkout/additional-fields.guest-shopper.block_theme.spec.ts
@@ -368,32 +368,6 @@ test.describe( 'Shopper → Additional Checkout Fields', () => {
 			).toBeHidden();
 		} );

-		test( 'Conditional field stays hidden while the field it depends on has no value', async ( {
-			checkoutPageObject,
-			frontendUtils,
-		} ) => {
-			await frontendUtils.goToShop();
-			await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-			await frontendUtils.goToCheckout();
-
-			const facebookPage = checkoutPageObject.page.getByLabel(
-				'Which Facebook page did you see us on?'
-			);
-			const howDidYouHear = checkoutPageObject.page.getByLabel(
-				'How did you hear about us?'
-			);
-
-			// Nothing has been picked yet, so the rule must not match.
-			await expect( howDidYouHear ).toHaveValue( '' );
-			await expect( facebookPage ).toBeHidden();
-
-			await howDidYouHear.selectOption( 'facebook' );
-			await expect( facebookPage ).toBeVisible();
-
-			await howDidYouHear.selectOption( 'google' );
-			await expect( facebookPage ).toBeHidden();
-		} );
-
 		test( 'Conditional fields are shown/hidden based on cart state', async ( {
 			checkoutPageObject,
 			frontendUtils,
diff --git a/plugins/woocommerce/tests/php/src/Blocks/Domain/Services/CheckoutFieldsSchema/DocumentObjectTests.php b/plugins/woocommerce/tests/php/src/Blocks/Domain/Services/CheckoutFieldsSchema/DocumentObjectTests.php
index 9d6448583c7..061771273b0 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/Domain/Services/CheckoutFieldsSchema/DocumentObjectTests.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/Domain/Services/CheckoutFieldsSchema/DocumentObjectTests.php
@@ -6,7 +6,6 @@ namespace Automattic\WooCommerce\Tests\Blocks\Domain\Services\CheckoutFieldsSche
 use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFieldsSchema\DocumentObject;
 use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
 use Automattic\WooCommerce\StoreApi\Utilities\CheckoutTrait;
-use Automattic\WooCommerce\StoreApi\Utilities\DraftOrderTrait;
 use Automattic\WooCommerce\Tests\Blocks\Helpers\FixtureData;
 use Automattic\WooCommerce\Blocks\Package;
 use Opis\JsonSchema\{
@@ -28,7 +27,6 @@ class DocumentObjectTests extends \WC_Unit_Test_Case {
 	 * to test the DocumentObject class.
 	 */
 	use CheckoutTrait;
-	use DraftOrderTrait;

 	/**
 	 * Checkout fields controller.
@@ -36,12 +34,6 @@ class DocumentObjectTests extends \WC_Unit_Test_Case {
 	 */
 	protected $additional_fields_controller;

-	/**
-	 * Current order, needed for the trait.
-	 * @var \WC_Order|null
-	 */
-	private $order = null;
-
 	/**
 	 * Fixture data.
 	 * @var FixtureData
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/AdditionalFields.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/AdditionalFields.php
index 1257a1e2122..b481075eebb 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/AdditionalFields.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/AdditionalFields.php
@@ -10,7 +10,6 @@ use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
 use Automattic\WooCommerce\Blocks\Package;
 use WC_Gateway_BACS;
 use Automattic\WooCommerce\Enums\ProductStockStatus;
-use Automattic\WooCommerce\Enums\OrderStatus;
 use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;

 /**
@@ -2461,7 +2460,7 @@ class AdditionalFields extends \WP_Test_REST_TestCase {
 		$this->assertEquals( 'billing-saved-gov-id', ( (array) $data['billing_address'] )['plugin-namespace/gov-id'], print_r( $data, true ) );
 		$this->assertEquals( 'shipping-saved-gov-id', ( (array) $data['shipping_address'] )['plugin-namespace/gov-id'], print_r( $data, true ) );
 		$this->assertEquals( 'engineering', $additional_fields['plugin-namespace/job-function'], print_r( $data, true ) );
-		$this->assertFalse( $additional_fields['plugin-namespace/leave-on-porch'], print_r( $data, true ) );
+		$this->assertArrayNotHasKey( 'plugin-namespace/leave-on-porch', $additional_fields, print_r( $data, true ) );
 	}

 	/**
@@ -2953,310 +2952,4 @@ class AdditionalFields extends \WP_Test_REST_TestCase {
 		\__internal_woocommerce_blocks_deregister_checkout_field( $id );
 		$this->assertFalse( $this->controller->is_field( $id ), sprintf( '%s is still registered', $id ) );
 	}
-
-	/**
-	 * Returns a rule matching the referral-source value at the given path.
-	 *
-	 * @param string[] $path Property path to the object holding the field (e.g. [ 'checkout', 'additional_fields' ]).
-	 * @param string   $value The value to match.
-	 * @return array The rule schema.
-	 */
-	private function get_referral_source_rule( array $path = array( 'checkout', 'additional_fields' ), string $value = 'other' ) {
-		$rule = array(
-			'type'       => 'object',
-			'properties' => array(
-				'plugin-namespace/referral-source' => array(
-					'type'  => 'string',
-					'const' => $value,
-				),
-			),
-		);
-		foreach ( array_reverse( $path ) as $segment ) {
-			$rule = array(
-				'type'       => 'object',
-				'properties' => array( $segment => $rule ),
-			);
-		}
-		return $rule;
-	}
-
-	/**
-	 * Registers a referral-source select field and a referral-detail field with a conditional rule on it.
-	 *
-	 * @param string $rule_prop Which conditional rule to give the detail field (hidden|required).
-	 * @param string $location The location to register both fields in (order|contact|address).
-	 */
-	private function register_conditional_referral_fields( $rule_prop, $location = 'order' ) {
-		$rule_paths = array(
-			'order'   => array( 'checkout', 'additional_fields' ),
-			'contact' => array( 'customer', 'additional_fields' ),
-			'address' => array( 'customer', 'billing_address' ),
-		);
-		$rule       = $this->get_referral_source_rule( $rule_paths[ $location ] );
-
-		\woocommerce_register_additional_checkout_field(
-			array(
-				'id'       => 'plugin-namespace/referral-source',
-				'label'    => 'How did you find us?',
-				'location' => $location,
-				'type'     => 'select',
-				'options'  => array(
-					array(
-						'value' => 'search',
-						'label' => 'Search engine',
-					),
-					array(
-						'value' => 'other',
-						'label' => 'Other',
-					),
-				),
-			)
-		);
-		\woocommerce_register_additional_checkout_field(
-			array(
-				'id'       => 'plugin-namespace/referral-detail',
-				'label'    => 'Please specify',
-				'location' => $location,
-				'type'     => 'text',
-				$rule_prop => 'hidden' === $rule_prop
-					? array( 'not' => $rule )
-					: $rule,
-			)
-		);
-	}
-
-	/**
-	 * Builds a checkout request with a valid address and the given additional field values.
-	 *
-	 * @param string $method The request method (POST|PUT|PATCH).
-	 * @param array  $additional_fields The additional field values to send.
-	 * @param array  $address_fields Additional address field values, merged into both addresses.
-	 * @return \WP_REST_Request The request.
-	 */
-	private function get_checkout_request_with_fields( $method, array $additional_fields, array $address_fields = array() ) {
-		$address = array_merge(
-			array(
-				'first_name' => 'test',
-				'last_name'  => 'test',
-				'address_1'  => 'test',
-				'city'       => 'test',
-				'state'      => '',
-				'postcode'   => 'cb241ab',
-				'country'    => 'GB',
-				'email'      => 'testaccount@test.com',
-			),
-			$address_fields
-		);
-		$request = new \WP_REST_Request( $method, '/wc/store/v1/checkout' );
-		$request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) );
-		$request->set_body_params(
-			array(
-				'billing_address'   => (object) $address,
-				'shipping_address'  => (object) $address,
-				'payment_method'    => WC_Gateway_BACS::ID,
-				'additional_fields' => $additional_fields,
-			)
-		);
-		return $request;
-	}
-
-	/**
-	 * @testdox A conditionally hidden field stays hidden while the field it depends on has no value, so its posted value is cleared.
-	 */
-	public function test_conditional_hidden_field_cleared_when_dependency_has_no_value() {
-		$this->unregister_fields();
-		$this->register_conditional_referral_fields( 'hidden' );
-
-		$request  = $this->get_checkout_request_with_fields( 'POST', array( 'plugin-namespace/referral-detail' => 'posted while hidden' ) );
-		$response = rest_get_server()->dispatch( $request );
-		$data     = $response->get_data();
-
-		$this->assertEquals( 200, $response->get_status(), print_r( $data, true ) );
-		$fields = (array) $data['additional_fields'];
-		$this->assertSame( '', $fields['plugin-namespace/referral-detail'], 'The detail field is hidden while referral-source has no value, so its posted value must be cleared' );
-	}
-
-	/**
-	 * @testdox A conditionally required field is not required while the field it depends on has no value, even when that field's key is absent from the request.
-	 */
-	public function test_conditional_required_field_ignores_missing_dependency_key() {
-		$this->unregister_fields();
-		$this->register_conditional_referral_fields( 'required' );
-
-		// Dependency never given a value: the detail field must not be required.
-		$response = rest_get_server()->dispatch( $this->get_checkout_request_with_fields( 'POST', array() ) );
-		$this->assertEquals( 200, $response->get_status(), print_r( $response->get_data(), true ) );
-		$this->reset_session();
-
-		// Dependency set to the trigger value: the detail field is required.
-		$response = rest_get_server()->dispatch( $this->get_checkout_request_with_fields( 'POST', array( 'plugin-namespace/referral-source' => 'other' ) ) );
-		$this->assertEquals( 400, $response->get_status(), print_r( $response->get_data(), true ) );
-		$this->reset_session();
-
-		$response = rest_get_server()->dispatch(
-			$this->get_checkout_request_with_fields(
-				'POST',
-				array(
-					'plugin-namespace/referral-source' => 'other',
-					'plugin-namespace/referral-detail' => 'a friend',
-				)
-			)
-		);
-		$this->assertEquals( 200, $response->get_status(), print_r( $response->get_data(), true ) );
-	}
-
-	/**
-	 * @testdox A conditional rule reads the persisted dependency value when the request omits its key, so omitting the trigger field does not bypass a required field.
-	 */
-	public function test_conditional_required_field_uses_persisted_dependency_value() {
-		$this->unregister_fields();
-		$this->register_conditional_referral_fields( 'required' );
-
-		$response = rest_get_server()->dispatch( $this->get_checkout_request_with_fields( 'PUT', array( 'plugin-namespace/referral-source' => 'other' ) ) );
-		$this->assertEquals( 200, $response->get_status(), print_r( $response->get_data(), true ) );
-
-		// The POST omits referral-source, but its persisted value still makes the detail field required.
-		$response = rest_get_server()->dispatch( $this->get_checkout_request_with_fields( 'POST', array() ) );
-		$this->assertEquals( 400, $response->get_status(), print_r( $response->get_data(), true ) );
-
-		// The customer object survives reset_session, so clear the value this test persisted.
-		$this->controller->persist_field_for_customer( 'plugin-namespace/referral-source', '', wc()->customer, 'other' );
-		wc()->customer->save();
-	}
-
-	/**
-	 * @testdox A payment retry cannot clear a field required by a value saved on the order.
-	 * @testWith ["POST"]
-	 *           ["PUT"]
-	 *           ["PATCH"]
-	 * @param string $method The request method.
-	 */
-	public function test_conditional_required_field_uses_pending_order_value( string $method ): void {
-		$this->unregister_fields();
-		$this->register_conditional_referral_fields( 'required' );
-		update_option( 'woocommerce_manage_stock', 'yes' );
-		$this->products[0]->set_manage_stock( true );
-		$this->products[0]->set_stock_quantity( 10 );
-		$this->products[0]->save();
-
-		$order = wc_create_order();
-		$order->add_product( $this->products[0], 1 );
-		$order->set_status( OrderStatus::PENDING );
-		$order->set_total( 30 );
-		$order->set_cart_hash( wc()->cart->get_cart_hash() );
-		$this->controller->persist_field_for_order( 'plugin-namespace/referral-source', 'other', $order, 'other', false );
-		$this->controller->persist_field_for_order( 'plugin-namespace/referral-detail', 'a friend', $order, 'other', false );
-		$order->save();
-		wc_reserve_stock_for_order( $order );
-		$this->assertSame( 1, wc_get_held_stock_quantity( $this->products[0] ), 'The pending order must hold stock before the retry.' );
-		wc()->session->set( 'store_api_draft_order', $order->get_id() );
-
-		$request  = $this->get_checkout_request_with_fields( $method, array( 'plugin-namespace/referral-detail' => '' ) );
-		$response = rest_get_server()->dispatch( $request );
-
-		$this->assertSame( 400, $response->get_status(), print_r( $response->get_data(), true ) );
-		$this->assertSame( 'a friend', $this->controller->get_field_from_object( 'plugin-namespace/referral-detail', wc_get_order( $order->get_id() ) ), 'Rejected requests must leave the saved field unchanged.' );
-		$this->assertSame( 1, wc_get_held_stock_quantity( $this->products[0] ), 'Field validation errors must leave the existing stock hold unchanged.' );
-	}
-
-	/**
-	 * @testdox A partial update matches saved text without changing its punctuation.
-	 * @testWith ["order", "checkout"]
-	 *           ["contact", "customer"]
-	 * @param string $location The field location.
-	 * @param string $context The document object property holding the fields.
-	 */
-	public function test_conditional_hidden_field_uses_raw_saved_text( string $location, string $context ): void {
-		$this->unregister_fields();
-		\woocommerce_register_additional_checkout_field(
-			array(
-				'id'       => 'plugin-namespace/referral-source',
-				'label'    => 'Source',
-				'location' => $location,
-				'type'     => 'text',
-			)
-		);
-		\woocommerce_register_additional_checkout_field(
-			array(
-				'id'       => 'plugin-namespace/referral-detail',
-				'label'    => 'Please specify',
-				'location' => $location,
-				'type'     => 'text',
-				'hidden'   => array( 'not' => $this->get_referral_source_rule( array( $context, 'additional_fields' ), "John's" ) ),
-			)
-		);
-
-		$request  = $this->get_checkout_request_with_fields(
-			'PUT',
-			array(
-				'plugin-namespace/referral-source' => "John's",
-				'plugin-namespace/referral-detail' => 'first value',
-			)
-		);
-		$response = rest_get_server()->dispatch( $request );
-		$this->assertSame( 200, $response->get_status(), print_r( $response->get_data(), true ) );
-		$this->assertSame( 'first value', ( (array) $response->get_data()['additional_fields'] )['plugin-namespace/referral-detail'] );
-
-		$request  = $this->get_checkout_request_with_fields( 'PUT', array( 'plugin-namespace/referral-detail' => 'second value' ) );
-		$response = rest_get_server()->dispatch( $request );
-		$this->assertSame( 200, $response->get_status(), print_r( $response->get_data(), true ) );
-		$this->assertSame( 'second value', ( (array) $response->get_data()['additional_fields'] )['plugin-namespace/referral-detail'], 'The saved dependency must match the same rule as its posted value.' );
-	}
-
-	/**
-	 * @testdox A conditionally required contact field follows the same rules as order fields when its dependency has no value.
-	 */
-	public function test_conditional_required_contact_field_ignores_missing_dependency_key() {
-		$this->unregister_fields();
-		$this->register_conditional_referral_fields( 'required', 'contact' );
-
-		$response = rest_get_server()->dispatch( $this->get_checkout_request_with_fields( 'POST', array() ) );
-		$this->assertEquals( 200, $response->get_status(), print_r( $response->get_data(), true ) );
-		$this->reset_session();
-
-		$response = rest_get_server()->dispatch( $this->get_checkout_request_with_fields( 'POST', array( 'plugin-namespace/referral-source' => 'other' ) ) );
-		$this->assertEquals( 400, $response->get_status(), print_r( $response->get_data(), true ) );
-		$this->reset_session();
-
-		$response = rest_get_server()->dispatch(
-			$this->get_checkout_request_with_fields(
-				'POST',
-				array(
-					'plugin-namespace/referral-source' => 'other',
-					'plugin-namespace/referral-detail' => 'a friend',
-				)
-			)
-		);
-		$this->assertEquals( 200, $response->get_status(), print_r( $response->get_data(), true ) );
-	}
-
-	/**
-	 * @testdox A conditionally required address field follows the same rules as order fields when its dependency has no value.
-	 */
-	public function test_conditional_required_address_field_ignores_missing_dependency_key() {
-		$this->unregister_fields();
-		$this->register_conditional_referral_fields( 'required', 'address' );
-
-		$response = rest_get_server()->dispatch( $this->get_checkout_request_with_fields( 'POST', array() ) );
-		$this->assertEquals( 200, $response->get_status(), print_r( $response->get_data(), true ) );
-		$this->reset_session();
-
-		$response = rest_get_server()->dispatch(
-			$this->get_checkout_request_with_fields( 'POST', array(), array( 'plugin-namespace/referral-source' => 'other' ) )
-		);
-		$this->assertEquals( 400, $response->get_status(), print_r( $response->get_data(), true ) );
-		$this->reset_session();
-
-		$response = rest_get_server()->dispatch(
-			$this->get_checkout_request_with_fields(
-				'POST',
-				array(),
-				array(
-					'plugin-namespace/referral-source' => 'other',
-					'plugin-namespace/referral-detail' => 'a friend',
-				)
-			)
-		);
-		$this->assertEquals( 200, $response->get_status(), print_r( $response->get_data(), true ) );
-	}
 }