From f8e1560270228db5a9dc3d70c032aeec0f94a535 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 21 Dec 2021 13:11:51 +0000 Subject: [PATCH] Sync order data with cart data when cart is updated from any route (#5379) * Link order controller to cart routes * Remove order controller from checkout route * Fix PHP warnings in abstract schema * Fix PHP warnings in abstract route * Update shipping phone handling * Includes are handled in core now * Remove maybe_update_order_from_customer * Add cart_updated routine to all cart routes * Remove abstract method * Remove test for woocommerce_blocks_cart_update_order_from_customer_request * Remove do_order_callback --- bin/hook-docs/data/actions.json | 6 +-- docs/extensibility/actions.md | 10 ++-- src/StoreApi/Routes/AbstractCartRoute.php | 53 ++++++++++++++++++---- src/StoreApi/Routes/AbstractRoute.php | 4 +- src/StoreApi/Routes/CartUpdateCustomer.php | 53 ---------------------- src/StoreApi/Routes/Checkout.php | 24 ---------- src/StoreApi/RoutesController.php | 26 +++++------ src/StoreApi/Schemas/AbstractSchema.php | 11 ++++- src/StoreApi/Utilities/CartController.php | 2 - src/StoreApi/Utilities/OrderController.php | 9 +--- tests/php/StoreApi/Routes/Cart.php | 3 -- 11 files changed, 78 insertions(+), 123 deletions(-) diff --git a/bin/hook-docs/data/actions.json b/bin/hook-docs/data/actions.json index da3aa45c15d..b5b8ac0faab 100644 --- a/bin/hook-docs/data/actions.json +++ b/bin/hook-docs/data/actions.json @@ -268,11 +268,11 @@ } }, { - "name": "woocommerce_blocks_cart_update_order_from_customer_request", - "file": "StoreApi/Routes/CartUpdateCustomer.php", + "name": "woocommerce_blocks_cart_update_order_from_request", + "file": "StoreApi/Routes/AbstractCartRoute.php", "type": "action", "doc": { - "description": "Fires when the Checkout Block/Store API updates an existing draft order from customer data.", + "description": "Fires when the order is synced with cart data from a cart route.", "long_description": "", "tags": [ { diff --git a/docs/extensibility/actions.md b/docs/extensibility/actions.md index 9980b461663..9262a2fe5c5 100644 --- a/docs/extensibility/actions.md +++ b/docs/extensibility/actions.md @@ -19,7 +19,7 @@ - [woocommerce_blocks_cart_enqueue_data](#woocommerce_blocks_cart_enqueue_data) - [woocommerce_blocks_cart_enqueue_data](#woocommerce_blocks_cart_enqueue_data-1) - [woocommerce_blocks_cart_update_customer_from_request](#woocommerce_blocks_cart_update_customer_from_request) - - [woocommerce_blocks_cart_update_order_from_customer_request](#woocommerce_blocks_cart_update_order_from_customer_request) + - [woocommerce_blocks_cart_update_order_from_request](#woocommerce_blocks_cart_update_order_from_request) - [woocommerce_blocks_checkout_enqueue_data](#woocommerce_blocks_checkout_enqueue_data) - [woocommerce_blocks_checkout_order_processed](#woocommerce_blocks_checkout_order_processed) - [woocommerce_blocks_checkout_update_order_from_request](#woocommerce_blocks_checkout_update_order_from_request) @@ -261,13 +261,13 @@ File: [StoreApi/Routes/CartUpdateCustomer.php](../src/StoreApi/Routes/CartUpdate --- -## woocommerce_blocks_cart_update_order_from_customer_request +## woocommerce_blocks_cart_update_order_from_request -Fires when the Checkout Block/Store API updates an existing draft order from customer data. +Fires when the order is synced with cart data from a cart route. ```php -do_action( 'woocommerce_blocks_cart_update_order_from_customer_request', \WC_Order $draft_order, \WC_Customer $customer, \WP_REST_Request $request ) +do_action( 'woocommerce_blocks_cart_update_order_from_request', \WC_Order $draft_order, \WC_Customer $customer, \WP_REST_Request $request ) ``` ### Parameters @@ -281,7 +281,7 @@ do_action( 'woocommerce_blocks_cart_update_order_from_customer_request', \WC_Ord ### Source -File: [StoreApi/Routes/CartUpdateCustomer.php](../src/StoreApi/Routes/CartUpdateCustomer.php) +File: [StoreApi/Routes/AbstractCartRoute.php](../src/StoreApi/Routes/AbstractCartRoute.php) --- diff --git a/src/StoreApi/Routes/AbstractCartRoute.php b/src/StoreApi/Routes/AbstractCartRoute.php index d6c3355b334..3698f284497 100644 --- a/src/StoreApi/Routes/AbstractCartRoute.php +++ b/src/StoreApi/Routes/AbstractCartRoute.php @@ -4,13 +4,16 @@ use Automattic\WooCommerce\Blocks\StoreApi\Schemas\AbstractSchema; use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartSchema; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController; - +use Automattic\WooCommerce\Blocks\StoreApi\Utilities\DraftOrderTrait; +use Automattic\WooCommerce\Blocks\StoreApi\Utilities\OrderController; /** * Abstract Cart Route * * @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions. */ abstract class AbstractCartRoute extends AbstractRoute { + use DraftOrderTrait; + /** * Schema class for this route's response. * @@ -32,18 +35,27 @@ abstract class AbstractCartRoute extends AbstractRoute { */ protected $cart_controller; + /** + * Order controller class instance. + * + * @var OrderController + */ + protected $order_controller; + /** * Constructor accepts two types of schema; one for the item being returned, and one for the cart as a whole. These * may be the same depending on the route. * - * @param CartSchema $cart_schema Schema class for the cart. - * @param AbstractSchema $item_schema Schema class for this route's items if it differs from the cart schema. - * @param CartController $cart_controller Cart controller class. + * @param CartSchema $cart_schema Schema class for the cart. + * @param AbstractSchema $item_schema Schema class for this route's items if it differs from the cart schema. + * @param CartController $cart_controller Cart controller class. + * @param OrderController $order_controller Order controller class. */ - public function __construct( CartSchema $cart_schema, AbstractSchema $item_schema = null, CartController $cart_controller ) { - $this->schema = is_null( $item_schema ) ? $cart_schema : $item_schema; - $this->cart_schema = $cart_schema; - $this->cart_controller = $cart_controller; + public function __construct( CartSchema $cart_schema, AbstractSchema $item_schema = null, CartController $cart_controller, OrderController $order_controller ) { + $this->schema = is_null( $item_schema ) ? $cart_schema : $item_schema; + $this->cart_schema = $cart_schema; + $this->cart_controller = $cart_controller; + $this->order_controller = $order_controller; } /** @@ -74,6 +86,8 @@ public function get_response( \WP_REST_Request $request ) { if ( is_wp_error( $response ) ) { $response = $this->error_to_response( $response ); + } elseif ( in_array( $request->get_method(), [ 'POST', 'PUT', 'PATCH', 'DELETE' ], true ) ) { + $this->cart_updated( $request ); } return $this->add_nonce_headers( $response ); @@ -102,6 +116,29 @@ protected function requires_nonce( \WP_REST_Request $request ) { return 'GET' !== $request->get_method(); } + /** + * Triggered after an update to cart data. Re-calculates totals and updates draft orders (if they already exist) to + * keep all data in sync. + * + * @param \WP_REST_Request $request Request object. + */ + protected function cart_updated( \WP_REST_Request $request ) { + $draft_order = $this->get_draft_order(); + + if ( $draft_order ) { + $this->order_controller->update_order_from_cart( $draft_order ); + + /** + * Fires when the order is synced with cart data from a cart route. + * + * @param \WC_Order $draft_order Order object. + * @param \WC_Customer $customer Customer object. + * @param \WP_REST_Request $request Full details about the request. + */ + do_action( 'woocommerce_blocks_cart_update_order_from_request', $draft_order, $request ); + } + } + /** * Ensures the cart totals are calculated before an API response is generated. */ diff --git a/src/StoreApi/Routes/AbstractRoute.php b/src/StoreApi/Routes/AbstractRoute.php index e3a35ad5bf3..32aef1b8387 100644 --- a/src/StoreApi/Routes/AbstractRoute.php +++ b/src/StoreApi/Routes/AbstractRoute.php @@ -87,8 +87,8 @@ public function get_response( \WP_REST_Request $request ) { /** * Converts an error to a response object. Based on \WP_REST_Server. * - * @param WP_Error $error WP_Error instance. - * @return WP_REST_Response List of associative arrays with code and message keys. + * @param \WP_Error $error WP_Error instance. + * @return \WP_REST_Response List of associative arrays with code and message keys. */ protected function error_to_response( $error ) { $error_data = $error->get_error_data(); diff --git a/src/StoreApi/Routes/CartUpdateCustomer.php b/src/StoreApi/Routes/CartUpdateCustomer.php index ec47d64d37f..7854afc5628 100644 --- a/src/StoreApi/Routes/CartUpdateCustomer.php +++ b/src/StoreApi/Routes/CartUpdateCustomer.php @@ -121,7 +121,6 @@ protected function get_route_post_response( \WP_REST_Request $request ) { $customer->save(); $this->calculate_totals(); - $this->maybe_update_order_from_customer( $customer, $request ); return rest_ensure_response( $this->schema->get_item_response( $cart ) ); } @@ -146,56 +145,4 @@ protected function get_customer_billing_address( \WC_Customer $customer ) { 'phone' => $customer->get_billing_phone(), ]; } - - /** - * If there is a draft order, update the customer data within that also so the - * cart and order are kept in sync. - * - * @param \WC_Customer $customer Customer object. - * @param \WP_REST_Request $request Request object. - */ - protected function maybe_update_order_from_customer( \WC_Customer $customer, \WP_REST_Request $request ) { - $draft_order = $this->get_draft_order(); - - if ( ! $draft_order ) { - return; - } - - $draft_order->set_props( - [ - 'billing_first_name' => $customer->get_billing_first_name(), - 'billing_last_name' => $customer->get_billing_last_name(), - 'billing_company' => $customer->get_billing_company(), - 'billing_address_1' => $customer->get_billing_address_1(), - 'billing_address_2' => $customer->get_billing_address_2(), - 'billing_city' => $customer->get_billing_city(), - 'billing_state' => $customer->get_billing_state(), - 'billing_postcode' => $customer->get_billing_postcode(), - 'billing_country' => $customer->get_billing_country(), - 'billing_email' => $customer->get_billing_email(), - 'billing_phone' => $customer->get_billing_phone(), - 'shipping_first_name' => $customer->get_shipping_first_name(), - 'shipping_last_name' => $customer->get_shipping_last_name(), - 'shipping_company' => $customer->get_shipping_company(), - 'shipping_address_1' => $customer->get_shipping_address_1(), - 'shipping_address_2' => $customer->get_shipping_address_2(), - 'shipping_city' => $customer->get_shipping_city(), - 'shipping_state' => $customer->get_shipping_state(), - 'shipping_postcode' => $customer->get_shipping_postcode(), - 'shipping_country' => $customer->get_shipping_country(), - 'shipping_phone' => $customer->get_shipping_phone(), - ] - ); - - /** - * Fires when the Checkout Block/Store API updates an existing draft order from customer data. - * - * @param \WC_Order $draft_order Order object. - * @param \WC_Customer $customer Customer object. - * @param \WP_REST_Request $request Full details about the request. - */ - do_action( 'woocommerce_blocks_cart_update_order_from_customer_request', $draft_order, $customer, $request ); - - $draft_order->save(); - } } diff --git a/src/StoreApi/Routes/Checkout.php b/src/StoreApi/Routes/Checkout.php index 1d3e315c0f1..8f0d08b6dbd 100644 --- a/src/StoreApi/Routes/Checkout.php +++ b/src/StoreApi/Routes/Checkout.php @@ -13,7 +13,6 @@ use Automattic\WooCommerce\Blocks\StoreApi\Utilities\OrderController; use Automattic\WooCommerce\Checkout\Helpers\ReserveStock; use Automattic\WooCommerce\Checkout\Helpers\ReserveStockException; - /** * Checkout class. * @@ -29,29 +28,6 @@ class Checkout extends AbstractCartRoute { */ private $order = null; - /** - * Order controller class instance. - * - * @var OrderController - */ - protected $order_controller; - - /** - * Constructor accepts two types of schema; one for the item being returned, and one for the cart as a whole. These - * may be the same depending on the route. - * - * @param CartSchema $cart_schema Schema class for the cart. - * @param AbstractSchema $item_schema Schema class for this route's items if it differs from the cart schema. - * @param CartController $cart_controller Cart controller class. - * @param OrderController $order_controller Order controller class. - */ - public function __construct( CartSchema $cart_schema, AbstractSchema $item_schema = null, CartController $cart_controller, OrderController $order_controller ) { - $this->schema = is_null( $item_schema ) ? $cart_schema : $item_schema; - $this->cart_schema = $cart_schema; - $this->cart_controller = $cart_controller; - $this->order_controller = $order_controller; - } - /** * Get the path of this REST route. * diff --git a/src/StoreApi/RoutesController.php b/src/StoreApi/RoutesController.php index 98f6ceaf226..415081cd8cd 100644 --- a/src/StoreApi/RoutesController.php +++ b/src/StoreApi/RoutesController.php @@ -75,19 +75,19 @@ protected function initialize() { $order_controller = new OrderController(); $this->routes = [ - 'cart' => new Routes\Cart( $this->schemas->get( 'cart' ), null, $cart_controller ), - 'cart-add-item' => new Routes\CartAddItem( $this->schemas->get( 'cart' ), null, $cart_controller ), - 'cart-apply-coupon' => new Routes\CartApplyCoupon( $this->schemas->get( 'cart' ), null, $cart_controller ), - 'cart-coupons' => new Routes\CartCoupons( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-coupon' ), $cart_controller ), - 'cart-coupons-by-code' => new Routes\CartCouponsByCode( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-coupon' ), $cart_controller ), - 'cart-extensions' => new Routes\CartExtensions( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-extensions' ), $cart_controller ), - 'cart-items' => new Routes\CartItems( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-item' ), $cart_controller ), - 'cart-items-by-key' => new Routes\CartItemsByKey( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-item' ), $cart_controller ), - 'cart-remove-coupon' => new Routes\CartRemoveCoupon( $this->schemas->get( 'cart' ), null, $cart_controller ), - 'cart-remove-item' => new Routes\CartRemoveItem( $this->schemas->get( 'cart' ), null, $cart_controller ), - 'cart-select-shipping-rate' => new Routes\CartSelectShippingRate( $this->schemas->get( 'cart' ), null, $cart_controller ), - 'cart-update-item' => new Routes\CartUpdateItem( $this->schemas->get( 'cart' ), null, $cart_controller ), - 'cart-update-customer' => new Routes\CartUpdateCustomer( $this->schemas->get( 'cart' ), null, $cart_controller ), + 'cart' => new Routes\Cart( $this->schemas->get( 'cart' ), null, $cart_controller, $order_controller ), + 'cart-add-item' => new Routes\CartAddItem( $this->schemas->get( 'cart' ), null, $cart_controller, $order_controller ), + 'cart-apply-coupon' => new Routes\CartApplyCoupon( $this->schemas->get( 'cart' ), null, $cart_controller, $order_controller ), + 'cart-coupons' => new Routes\CartCoupons( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-coupon' ), $cart_controller, $order_controller ), + 'cart-coupons-by-code' => new Routes\CartCouponsByCode( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-coupon' ), $cart_controller, $order_controller ), + 'cart-extensions' => new Routes\CartExtensions( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-extensions' ), $cart_controller, $order_controller ), + 'cart-items' => new Routes\CartItems( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-item' ), $cart_controller, $order_controller ), + 'cart-items-by-key' => new Routes\CartItemsByKey( $this->schemas->get( 'cart' ), $this->schemas->get( 'cart-item' ), $cart_controller, $order_controller ), + 'cart-remove-coupon' => new Routes\CartRemoveCoupon( $this->schemas->get( 'cart' ), null, $cart_controller, $order_controller ), + 'cart-remove-item' => new Routes\CartRemoveItem( $this->schemas->get( 'cart' ), null, $cart_controller, $order_controller ), + 'cart-select-shipping-rate' => new Routes\CartSelectShippingRate( $this->schemas->get( 'cart' ), null, $cart_controller, $order_controller ), + 'cart-update-item' => new Routes\CartUpdateItem( $this->schemas->get( 'cart' ), null, $cart_controller, $order_controller ), + 'cart-update-customer' => new Routes\CartUpdateCustomer( $this->schemas->get( 'cart' ), null, $cart_controller, $order_controller ), 'checkout' => new Routes\Checkout( $this->schemas->get( 'cart' ), $this->schemas->get( 'checkout' ), $cart_controller, $order_controller ), 'product-attributes' => new Routes\ProductAttributes( $this->schemas->get( 'product-attribute' ) ), 'product-attributes-by-id' => new Routes\ProductAttributesById( $this->schemas->get( 'product-attribute' ) ), diff --git a/src/StoreApi/Schemas/AbstractSchema.php b/src/StoreApi/Schemas/AbstractSchema.php index 5c6474cbea7..ec15ca7a778 100644 --- a/src/StoreApi/Schemas/AbstractSchema.php +++ b/src/StoreApi/Schemas/AbstractSchema.php @@ -1,8 +1,8 @@ wc()->customer->get_shipping_state(), 'shipping_postcode' => wc()->customer->get_shipping_postcode(), 'shipping_country' => wc()->customer->get_shipping_country(), + 'shipping_phone' => wc()->customer->get_shipping_phone(), ] ); - - $shipping_phone_value = is_callable( [ wc()->customer, 'get_shipping_phone' ] ) ? wc()->customer->get_shipping_phone() : wc()->customer->get_meta( 'shipping_phone', true ); - - if ( is_callable( [ $order, 'set_shipping_phone' ] ) ) { - $order->set_shipping_phone( $shipping_phone_value ); - } else { - $order->update_meta_data( '_shipping_phone', $shipping_phone_value ); - } } } diff --git a/tests/php/StoreApi/Routes/Cart.php b/tests/php/StoreApi/Routes/Cart.php index 70f8fd62b34..f86c6f72e10 100644 --- a/tests/php/StoreApi/Routes/Cart.php +++ b/tests/php/StoreApi/Routes/Cart.php @@ -188,16 +188,13 @@ public function test_update_customer() { $action_callback = \Mockery::mock( 'ActionCallback' ); $action_callback->shouldReceive( 'do_customer_callback' )->once(); - $action_callback->shouldReceive( 'do_order_callback' )->once(); add_action( 'woocommerce_blocks_cart_update_customer_from_request', array( $action_callback, 'do_customer_callback' ) ); - add_action( 'woocommerce_blocks_cart_update_order_from_customer_request', array( $action_callback, 'do_order_callback' ) ); $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); remove_action( 'woocommerce_blocks_cart_update_customer_from_request', array( $action_callback, 'do_customer_callback' ) ); - remove_action( 'woocommerce_blocks_cart_update_order_from_customer_request', array( $action_callback, 'do_order_callback' ) ); $this->assertEquals( 200, $response->get_status(), print_r( $response, true ) ); $this->assertArrayHasKey( 'shipping_rates', $data );