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.' );
+ }
}