diff --git a/composer.json b/composer.json index a6583c20..07e34300 100644 --- a/composer.json +++ b/composer.json @@ -75,7 +75,7 @@ "stan": "phpstan analyze --ansi --memory-limit=4G", "strauss": [ "test -f ./bin/strauss.phar || curl -o bin/strauss.phar -L -C - https://github.com/BrianHenryIE/strauss/releases/download/0.14.0/strauss.phar", - "@php bin/strauss.phar", + "env -u COMPOSER_AUTH COMPOSER_HOME=$(mktemp -d) php bin/strauss.phar", "composer dump-autoload --optimize" ], "cghooks": [ diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index 21ee1392..359e333e 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -124,14 +124,36 @@ class WooCommerce { * @return boolean */ public static function should_load_session_handler() { + // Any request carrying either the Store-API Cart-Token header or the + // legacy `woocommerce-session` (filterable) header is a headless + // caller driving session state through the token, regardless of + // which WP endpoint it lands on (admin-ajax, REST, the front-end, + // etc.). We need QL_Session_Handler here too so the session is + // bootstrapped from the token instead of the (absent) WC session + // cookie. + $legacy_header_key = 'HTTP_' . strtoupper( + preg_replace( + '#[^A-z0-9]#', + '_', + apply_filters( 'graphql_woocommerce_cart_session_http_header', 'woocommerce-session' ) + ) + ); + $has_session_header = ! empty( $_SERVER['HTTP_CART_TOKEN'] ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + || ! empty( $_SERVER[ $legacy_header_key ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash + switch ( true ) { case \WPGraphQL\Router::is_graphql_http_request(): //phpcs:disable case 'on' === woographql_setting( 'enable_ql_session_handler_on_ajax', 'off' ) - && ( ! empty( $_GET['wc-ajax'] ) || defined( 'WC_DOING_AJAX' ) ): + && ( + ! empty( $_GET['wc-ajax'] ) + || defined( 'WC_DOING_AJAX' ) + || wp_doing_ajax() + || $has_session_header + ): //phpcs:enable case 'on' === woographql_setting( 'enable_ql_session_handler_on_rest', 'off' ) - && ( defined( 'REST_REQUEST' ) && REST_REQUEST ): + && ( ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || $has_session_header ): return true; default: return false; diff --git a/includes/data/mutation/class-checkout-mutation.php b/includes/data/mutation/class-checkout-mutation.php index 8498543c..16ce19a9 100644 --- a/includes/data/mutation/class-checkout-mutation.php +++ b/includes/data/mutation/class-checkout-mutation.php @@ -164,6 +164,7 @@ class Checkout_Mutation { 'postcode' => 'postcode', 'state' => 'state', 'country' => 'country', + 'phone' => 'phone', ], 'account' => [ 'username' => 'username', diff --git a/tests/wpunit/CheckoutMutationTest.php b/tests/wpunit/CheckoutMutationTest.php index c22eb2d9..aaa1a5e6 100644 --- a/tests/wpunit/CheckoutMutationTest.php +++ b/tests/wpunit/CheckoutMutationTest.php @@ -133,6 +133,7 @@ class CheckoutMutationTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGrap state postcode country + phone } paymentMethod paymentMethodTitle @@ -293,6 +294,7 @@ class CheckoutMutationTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGrap 'state' => 'NY', 'postcode' => '12345', 'country' => 'US', + 'phone' => '555-555-6789', ], 'metaData' => [ [ @@ -446,6 +448,8 @@ class CheckoutMutationTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGrap 'checkout.customer.id', $this->toRelayId( 'user', $this->customer ) ), + $this->expectedField( 'checkout.order.billing.phone', '555-555-1234' ), + $this->expectedField( 'checkout.order.shipping.phone', '555-555-6789' ), $this->expectedField( 'checkout.result', 'success' ), $this->expectedField( 'checkout.redirect', static::NOT_NULL ), ]; @@ -463,6 +467,44 @@ class CheckoutMutationTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGrap $this->assertQuerySuccessful( $response, $expected ); } + public function testCheckoutMutationPersistsShippingPhone() { + $this->loginAsCustomer(); + + $product_id = $this->factory->product->createSimple(); + WC()->cart->add_to_cart( $product_id, 1 ); + + $input = [ + 'shipping' => [ + 'firstName' => 'May', + 'lastName' => 'Parker', + 'address1' => '20 Ingram St', + 'city' => 'New York City', + 'state' => 'NY', + 'postcode' => '12345', + 'country' => 'US', + 'phone' => '555-555-6789', + ], + ]; + $variables = [ 'input' => $this->getCheckoutInput( $input ) ]; + $query = $this->getCheckoutMutation(); + + $response = $this->graphql( compact( 'query', 'variables' ) ); + + // The shipping phone is returned on the order and is distinct from the billing phone. + $this->assertQuerySuccessful( + $response, + [ + $this->expectedField( 'checkout.order.billing.phone', '555-555-1234' ), + $this->expectedField( 'checkout.order.shipping.phone', '555-555-6789' ), + ] + ); + + // And it is saved on the underlying WC_Order object. + $order_id = $response['data']['checkout']['order']['databaseId']; + $order = \wc_get_order( $order_id ); + $this->assertEquals( '555-555-6789', $order->get_shipping_phone() ); + } + public function testCheckoutMutationWithNewAccount() { $variable = $this->factory->product_variation->createSome(); $product_ids = [