Commit 5f5cf784350 for woocommerce

commit 5f5cf784350ed40d55d845d6b2f387190c81c829
Author: Abdalsalaam Halawa <abdalsalaamnafez@gmail.com>
Date:   Wed Jul 1 10:15:40 2026 +0300

    Store API: defer payment gateway resolution until a payment method is supplied (#65931)

diff --git a/plugins/woocommerce/changelog/defer-payment-gateway-init b/plugins/woocommerce/changelog/defer-payment-gateway-init
new file mode 100644
index 00000000000..26743d5b685
--- /dev/null
+++ b/plugins/woocommerce/changelog/defer-payment-gateway-init
@@ -0,0 +1,4 @@
+Significance: patch
+Type: performance
+
+Store API: skip initializing the available payment gateways on checkout requests that do not provide a payment method.
diff --git a/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php b/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php
index 3a07b7605cb..d8baf18b91b 100644
--- a/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php
+++ b/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php
@@ -946,13 +946,14 @@ class Checkout extends AbstractCartRoute {
 	 * @return \WC_Payment_Gateway|null
 	 */
 	private function get_request_payment_method( \WP_REST_Request $request ) {
-		$available_gateways     = WC()->payment_gateways->get_available_payment_gateways();
 		$request_payment_method = wc_clean( wp_unslash( $request['payment_method'] ?? '' ) );

 		if ( empty( $request_payment_method ) ) {
 			return null;
 		}

+		$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
+
 		if ( ! isset( $available_gateways[ $request_payment_method ] ) ) {
 			$all_payment_gateways = WC()->payment_gateways->payment_gateways();
 			$gateway_title        = isset( $all_payment_gateways[ $request_payment_method ] ) ? $all_payment_gateways[ $request_payment_method ]->get_title() : $request_payment_method;
diff --git a/plugins/woocommerce/src/StoreApi/Routes/V1/CheckoutOrder.php b/plugins/woocommerce/src/StoreApi/Routes/V1/CheckoutOrder.php
index 354a7280d66..edadfde730e 100644
--- a/plugins/woocommerce/src/StoreApi/Routes/V1/CheckoutOrder.php
+++ b/plugins/woocommerce/src/StoreApi/Routes/V1/CheckoutOrder.php
@@ -245,12 +245,10 @@ class CheckoutOrder extends AbstractCartRoute {
 	 * @return \WC_Payment_Gateway|null
 	 */
 	private function get_request_payment_method( \WP_REST_Request $request ) {
-		$available_gateways      = WC()->payment_gateways->get_available_payment_gateways();
-		$request_payment_method  = wc_clean( wp_unslash( $request['payment_method'] ?? '' ) );
-		$requires_payment_method = $this->order->needs_payment();
+		$request_payment_method = wc_clean( wp_unslash( $request['payment_method'] ?? '' ) );

 		if ( empty( $request_payment_method ) ) {
-			if ( $requires_payment_method ) {
+			if ( $this->order->needs_payment() ) {
 				throw new RouteException(
 					'woocommerce_rest_checkout_missing_payment_method',
 					__( 'No payment method provided.', 'woocommerce' ),
@@ -260,6 +258,8 @@ class CheckoutOrder extends AbstractCartRoute {
 			return null;
 		}

+		$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
+
 		if ( ! isset( $available_gateways[ $request_payment_method ] ) ) {
 			throw new RouteException(
 				'woocommerce_rest_checkout_payment_method_disabled',
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Checkout.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Checkout.php
index 3f4e9080b03..0ed4ac56e70 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Checkout.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Checkout.php
@@ -2367,4 +2367,73 @@ class Checkout extends MockeryTestCase {
 		);
 		return $request;
 	}
+
+	/**
+	 * @testdox Checkout route does not resolve the available payment gateways when the request carries no payment method.
+	 */
+	public function test_get_request_payment_method_skips_gateway_resolution_when_missing() {
+		$schema_controller = new SchemaController( $this->mock_extend );
+		$sut               = new CheckoutRoute( $schema_controller, $schema_controller->get( 'checkout' ) );
+
+		$gateway_resolution_count = 0;
+		$counter                  = function ( $gateways ) use ( &$gateway_resolution_count ) {
+			++$gateway_resolution_count;
+			return $gateways;
+		};
+		add_filter( 'woocommerce_available_payment_gateways', $counter );
+
+		$request = new \WP_REST_Request( 'POST', '/wc/store/v1/checkout' );
+
+		$method = new \ReflectionMethod( CheckoutRoute::class, 'get_request_payment_method' );
+		$method->setAccessible( true );
+
+		try {
+			$result = $method->invoke( $sut, $request );
+		} finally {
+			remove_filter( 'woocommerce_available_payment_gateways', $counter );
+		}
+
+		$this->assertNull( $result, 'No payment method should resolve to a null gateway.' );
+		$this->assertSame( 0, $gateway_resolution_count, 'Available payment gateways must not be resolved when no payment method is supplied.' );
+	}
+
+	/**
+	 * @testdox Checkout-order route does not resolve the available payment gateways when the request carries no payment method and the order needs no payment.
+	 */
+	public function test_order_get_request_payment_method_skips_gateway_resolution_when_missing() {
+		$schema_controller = new SchemaController( $this->mock_extend );
+		$sut               = new CheckoutOrderRoute( $schema_controller, $schema_controller->get( 'checkout-order' ) );
+
+		$order = new \WC_Order();
+		$order->save();
+
+		// This scenario depends on the order not needing payment, so the empty-method branch returns null instead of throwing.
+		$this->assertFalse( $order->needs_payment(), 'A zero-total order should not need payment in this scenario.' );
+
+		$order_property = new \ReflectionProperty( CheckoutOrderRoute::class, 'order' );
+		$order_property->setAccessible( true );
+		$order_property->setValue( $sut, $order );
+
+		$gateway_resolution_count = 0;
+		$counter                  = function ( $gateways ) use ( &$gateway_resolution_count ) {
+			++$gateway_resolution_count;
+			return $gateways;
+		};
+		add_filter( 'woocommerce_available_payment_gateways', $counter );
+
+		$request = new \WP_REST_Request( 'POST', '/wc/store/v1/checkout/' . $order->get_id() );
+
+		$method = new \ReflectionMethod( CheckoutOrderRoute::class, 'get_request_payment_method' );
+		$method->setAccessible( true );
+
+		try {
+			$result = $method->invoke( $sut, $request );
+		} finally {
+			remove_filter( 'woocommerce_available_payment_gateways', $counter );
+			$order->delete( true );
+		}
+
+		$this->assertNull( $result, 'No payment method on a zero-total order should resolve to a null gateway.' );
+		$this->assertSame( 0, $gateway_resolution_count, 'Available payment gateways must not be resolved when no payment method is supplied.' );
+	}
 }