Commit be715831a46 for woocommerce
commit be715831a461ed726f9b18e668cf21cf95634e9a
Author: Tom Cafferkey <tjcafferkey@gmail.com>
Date: Tue Sep 1 10:55:27 2026 +0100
Fix Store API cart recovery after redirect payments (#68102)
* Fix Store API checkout payment session tracking
* Add changelog entry for Store API cart recovery
* Use persisted session in test
* Check is_callable per PHPStan
diff --git a/plugins/woocommerce/changelog/fix-store-api-order-awaiting-payment b/plugins/woocommerce/changelog/fix-store-api-order-awaiting-payment
new file mode 100644
index 00000000000..cc36a3d0b0d
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-store-api-order-awaiting-payment
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Ensure Store API checkout carts clear after redirect payments.
diff --git a/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php b/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php
index d876951ab6e..5a5f8f9f4de 100644
--- a/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php
+++ b/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php
@@ -88,6 +88,13 @@ trait CheckoutTrait {
private function process_payment( \WP_REST_Request $request, PaymentResult $payment_result ) {
$order = $this->get_order_or_throw();
+ $session = WC()->session;
+ $session->set( 'order_awaiting_payment', $order->get_id() );
+ // Persist before invoking gateways because redirects or stalled requests may prevent the session from being saved on shutdown.
+ if ( is_callable( array( $session, 'save_data' ) ) ) {
+ $session->save_data();
+ }
+
try {
// Prepare the payment context object to pass through payment hooks.
$context = new PaymentContext();
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 3065948c43e..abbb79f9a44 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Checkout.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Checkout.php
@@ -3098,6 +3098,37 @@ class Checkout extends \WP_Test_REST_TestCase {
$this->assertEmpty( WC()->session->get( 'store_api_draft_order' ), 'Successful checkout should clear the draft order pointer when the cart is emptied.' );
}
+ /**
+ * @testdox Store API checkout stores the order awaiting payment before invoking the payment gateway.
+ */
+ public function test_post_sets_order_awaiting_payment_before_processing_payment(): void {
+ WC()->session->set_customer_session_cookie( true );
+ WC()->session->save_data();
+
+ $order_id_during_payment = 0;
+ $payment_handler = function ( $context, $payment_result ) use ( &$order_id_during_payment ) {
+ unset( $context );
+ $order_id_during_payment = (int) WC()->session->get( 'order_awaiting_payment' );
+ $payment_result->set_status( 'success' );
+ };
+
+ add_action( 'woocommerce_rest_checkout_process_payment_with_context', $payment_handler, 1, 2 );
+
+ try {
+ $response = rest_get_server()->dispatch( $this->build_valid_post_request() );
+ } finally {
+ remove_action( 'woocommerce_rest_checkout_process_payment_with_context', $payment_handler, 1 );
+ }
+
+ $this->assertEquals( 200, $response->get_status(), print_r( $response->get_data(), true ) );
+ $order_id = (int) $response->get_data()['order_id'];
+ $this->assertGreaterThan( 0, $order_id, 'Checkout should create an order.' );
+ $this->assertSame( $order_id, $order_id_during_payment, 'The order should be linked to the session before payment processing starts.' );
+ $persisted_session_data = WC()->session->get_session_data();
+ $this->assertArrayHasKey( 'order_awaiting_payment', $persisted_session_data, 'Redirect payments should persist the order link in the shopper session.' );
+ $this->assertSame( $order_id, (int) $persisted_session_data['order_awaiting_payment'], 'Redirect payments should leave the persisted order linked to the shopper session.' );
+ }
+
/**
* Build a valid checkout POST request body for use by the sample-extension tests.
*