Commit efb47b70bc8 for woocommerce
commit efb47b70bc8c34caae53d717ffb2ff11218b3dd4
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date: Wed Apr 29 13:18:11 2026 +0200
[Performance] Reduce the number of order saves during checkout in StoreAPI (#64392)
Reduces the number of order/customer save-calls, wc_get_price_decimals/wc_get_rounding_precision calls on the checkout via StoreAPI hot path for better performance. \WC_Customer::save is a platform-level fix to avoid unnecessary customer object saves.
diff --git a/plugins/woocommerce/changelog/performance-checkout-thruput-storeapi-checkout b/plugins/woocommerce/changelog/performance-checkout-thruput-storeapi-checkout
new file mode 100644
index 00000000000..da046884e04
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-checkout-thruput-storeapi-checkout
@@ -0,0 +1,4 @@
+Significance: minor
+Type: performance
+
+Optimized the order and customer save workflows during checkout using StoreAPI.
diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
index 7412ae06e9f..e341b5c1338 100644
--- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
+++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
@@ -2008,13 +2008,14 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$shipping_total = 0;
$cart_subtotal_tax = 0;
$cart_total_tax = 0;
+ $price_decimals = wc_get_price_decimals();
$cart_subtotal = $this->get_cart_subtotal_for_order();
$cart_total = (float) $this->get_cart_total_for_order();
// Sum shipping costs.
foreach ( $this->get_shipping_methods() as $shipping ) {
- $shipping_total += NumberUtil::round( $shipping->get_total(), wc_get_price_decimals() );
+ $shipping_total += NumberUtil::round( $shipping->get_total(), $price_decimals );
}
$this->set_shipping_total( $shipping_total );
@@ -2024,7 +2025,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$fee_total = (float) $item->get_total();
if ( 0 > $fee_total ) {
- $max_discount = NumberUtil::round( $cart_total + $fees_total + $shipping_total, wc_get_price_decimals() ) * -1;
+ $max_discount = NumberUtil::round( $cart_total + $fees_total + $shipping_total, $price_decimals ) * -1;
if ( $fee_total < $max_discount && 0 > $max_discount ) {
$item->set_total( $max_discount );
@@ -2051,9 +2052,9 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
}
}
- $this->set_discount_total( NumberUtil::round( $cart_subtotal - $cart_total, wc_get_price_decimals() ) );
+ $this->set_discount_total( NumberUtil::round( $cart_subtotal - $cart_total, $price_decimals ) );
$this->set_discount_tax( wc_round_tax_total( $cart_subtotal_tax - $cart_total_tax ) );
- $this->set_total( NumberUtil::round( $cart_total + $fees_total + (float) $this->get_shipping_total() + (float) $this->get_cart_tax() + (float) $this->get_shipping_tax(), wc_get_price_decimals() ) );
+ $this->set_total( NumberUtil::round( $cart_total + $fees_total + (float) $this->get_shipping_total() + (float) $this->get_cart_tax() + (float) $this->get_shipping_tax(), $price_decimals ) );
if ( $this->has_cogs() && $this->cogs_is_enabled() ) {
$this->calculate_cogs_total_value();
diff --git a/plugins/woocommerce/includes/class-wc-customer.php b/plugins/woocommerce/includes/class-wc-customer.php
index 8b0d2f7f13f..1fed289435c 100644
--- a/plugins/woocommerce/includes/class-wc-customer.php
+++ b/plugins/woocommerce/includes/class-wc-customer.php
@@ -1266,4 +1266,27 @@ class WC_Customer extends WC_Legacy_Customer {
public function set_is_paying_customer( $is_paying_customer ) {
$this->set_prop( 'is_paying_customer', (bool) $is_paying_customer );
}
+
+ /**
+ * Overrides the save method to guard against saves with no data changed.
+ *
+ * @since 10.9.0
+ * @return int
+ */
+ public function save() {
+ $customer_id = $this->get_id();
+ if ( $customer_id ) {
+ $meta_data = $this->meta_data ?? array();
+ $props_changed = ! empty( $this->password ) || ! empty( $this->changes );
+ $state_changed = $props_changed || ! empty( array_filter( $meta_data, static fn( $meta ) => ! $meta->id || ! empty( $meta->get_changes() ) ) );
+ if ( ! $state_changed ) {
+ // Backward compatibility: e.g. '( new WC_Customer( $customer_id ) )->save()' as means to trigger integrations.
+ do_action( 'woocommerce_update_customer', $customer_id, $this ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.HookCommentWrongStyle
+
+ return $this->get_id();
+ }
+ }
+
+ return parent::save();
+ }
}
diff --git a/plugins/woocommerce/includes/class-wc-meta-data.php b/plugins/woocommerce/includes/class-wc-meta-data.php
index dd0e0c752f1..057adf6d0df 100644
--- a/plugins/woocommerce/includes/class-wc-meta-data.php
+++ b/plugins/woocommerce/includes/class-wc-meta-data.php
@@ -13,6 +13,10 @@ defined( 'ABSPATH' ) || exit;
/**
* Meta data class.
+ *
+ * @property int|null $id Meta ID.
+ * @property string $key Meta key.
+ * @property mixed $value Meta value.
*/
class WC_Meta_Data implements JsonSerializable {
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php
index 742e1c83b08..80385dbf660 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php
@@ -329,7 +329,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller {
'display_value' => $meta_item->value, // Default to original value, in case a formatted value is not available.
);
- if ( array_key_exists( $meta_item->id, $formatted_meta_data ) ) {
+ if ( $meta_item->id && array_key_exists( $meta_item->id, $formatted_meta_data ) ) {
$formatted_meta_item = $formatted_meta_data[ $meta_item->id ];
$result['display_key'] = wc_clean( $formatted_meta_item->display_key );
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 39f82099818..0d8494c76ca 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -6,24 +6,6 @@ parameters:
count: 3
path: includes/abstracts/abstract-wc-data.php
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$id\.$#'
- identifier: property.notFound
- count: 2
- path: includes/abstracts/abstract-wc-data.php
-
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$key\.$#'
- identifier: property.notFound
- count: 5
- path: includes/abstracts/abstract-wc-data.php
-
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$value\.$#'
- identifier: property.notFound
- count: 6
- path: includes/abstracts/abstract-wc-data.php
-
-
message: '#^Argument of an invalid type array\<WC_Meta_Data\>\|null supplied for foreach, only iterables are supported\.$#'
identifier: foreach.nonIterable
@@ -13050,12 +13032,6 @@ parameters:
count: 1
path: includes/class-wc-order-item-tax.php
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$id\.$#'
- identifier: property.notFound
- count: 1
- path: includes/class-wc-order-item.php
-
-
message: '#^Access to an undefined property object\:\:\$key\.$#'
identifier: property.notFound
@@ -26154,24 +26130,6 @@ parameters:
count: 1
path: includes/rest-api/Controllers/Version2/class-wc-rest-order-refunds-v2-controller.php
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$id\.$#'
- identifier: property.notFound
- count: 2
- path: includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php
-
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$key\.$#'
- identifier: property.notFound
- count: 2
- path: includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php
-
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$value\.$#'
- identifier: property.notFound
- count: 2
- path: includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php
-
-
message: '#^Call to an undefined method WC_Data\:\:calculate_totals\(\)\.$#'
identifier: method.notFound
@@ -43443,18 +43401,6 @@ parameters:
count: 1
path: src/Admin/API/Reports/Orders/Controller.php
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$key\.$#'
- identifier: property.notFound
- count: 2
- path: src/Admin/API/Reports/Orders/DataStore.php
-
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$value\.$#'
- identifier: property.notFound
- count: 1
- path: src/Admin/API/Reports/Orders/DataStore.php
-
-
message: '#^Method Automattic\\WooCommerce\\Admin\\API\\Reports\\Orders\\DataStore\:\:add_sql_query_params\(\) has no return type specified\.$#'
identifier: missingType.return
@@ -46473,24 +46419,6 @@ parameters:
count: 1
path: src/Admin/Features/Features.php
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$id\.$#'
- identifier: property.notFound
- count: 2
- path: src/Admin/Features/Fulfillments/DataStore/FulfillmentsDataStore.php
-
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$key\.$#'
- identifier: property.notFound
- count: 2
- path: src/Admin/Features/Fulfillments/DataStore/FulfillmentsDataStore.php
-
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$value\.$#'
- identifier: property.notFound
- count: 2
- path: src/Admin/Features/Fulfillments/DataStore/FulfillmentsDataStore.php
-
-
message: '#^Parameter \#2 \$meta \(WC_Meta_Data\) of method Automattic\\WooCommerce\\Admin\\Features\\Fulfillments\\DataStore\\FulfillmentsDataStore\:\:add_meta\(\) should be compatible with parameter \$meta \(stdClass\) of method WC_Data_Store_WP\:\:add_meta\(\)$#'
identifier: method.childParameterType
@@ -60543,18 +60471,6 @@ parameters:
count: 10
path: src/Internal/Admin/Orders/MetaBoxes/CustomMetaBox.php
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$key\.$#'
- identifier: property.notFound
- count: 2
- path: src/Internal/Admin/Orders/MetaBoxes/OrderAttribution.php
-
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$value\.$#'
- identifier: property.notFound
- count: 1
- path: src/Internal/Admin/Orders/MetaBoxes/OrderAttribution.php
-
-
message: '#^Cannot access property \$labels on WP_Taxonomy\|false\.$#'
identifier: property.nonObject
@@ -63753,24 +63669,6 @@ parameters:
count: 1
path: src/Internal/DataStores/Orders/OrdersTableDataStore.php
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$id\.$#'
- identifier: property.notFound
- count: 1
- path: src/Internal/DataStores/Orders/OrdersTableDataStore.php
-
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$key\.$#'
- identifier: property.notFound
- count: 3
- path: src/Internal/DataStores/Orders/OrdersTableDataStore.php
-
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$value\.$#'
- identifier: property.notFound
- count: 2
- path: src/Internal/DataStores/Orders/OrdersTableDataStore.php
-
-
message: '#^Call to an undefined method WC_Abstract_Order\:\:get_address\(\)\.$#'
identifier: method.notFound
@@ -64410,24 +64308,6 @@ parameters:
count: 1
path: src/Internal/DataStores/Orders/OrdersTableQuery.php
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$id\.$#'
- identifier: property.notFound
- count: 1
- path: src/Internal/DataStores/Orders/OrdersTableRefundDataStore.php
-
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$key\.$#'
- identifier: property.notFound
- count: 1
- path: src/Internal/DataStores/Orders/OrdersTableRefundDataStore.php
-
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$value\.$#'
- identifier: property.notFound
- count: 1
- path: src/Internal/DataStores/Orders/OrdersTableRefundDataStore.php
-
-
message: '#^Access to an undefined property object\:\:\$meta_data\.$#'
identifier: property.notFound
@@ -65706,18 +65586,6 @@ parameters:
count: 1
path: src/Internal/Orders/OrderAttributionBlocksController.php
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$key\.$#'
- identifier: property.notFound
- count: 2
- path: src/Internal/Orders/OrderAttributionController.php
-
- -
- message: '#^Access to an undefined property WC_Meta_Data\:\:\$value\.$#'
- identifier: property.notFound
- count: 1
- path: src/Internal/Orders/OrderAttributionController.php
-
-
message: '#^Callback expects 2 parameters, \$accepted_args is set to 10\.$#'
identifier: arguments.count
@@ -72606,12 +72474,6 @@ parameters:
count: 1
path: src/StoreApi/Routes/V1/Checkout.php
- -
- message: '#^Method Automattic\\WooCommerce\\StoreApi\\Routes\\V1\\Checkout\:\:get_route_post_response\(\) has parameter \$request with generic class WP_REST_Request but does not specify its types\: T$#'
- identifier: missingType.generics
- count: 1
- path: src/StoreApi/Routes/V1/Checkout.php
-
-
message: '#^Method Automattic\\WooCommerce\\StoreApi\\Routes\\V1\\Checkout\:\:get_route_response\(\) has parameter \$request with generic class WP_REST_Request but does not specify its types\: T$#'
identifier: missingType.generics
diff --git a/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php b/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php
index 00ac3ad4899..f02982ffba9 100644
--- a/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php
+++ b/plugins/woocommerce/src/StoreApi/Routes/V1/Checkout.php
@@ -355,11 +355,13 @@ class Checkout extends AbstractCartRoute {
* Create (or update) Draft Order and process request data.
*/
$this->create_or_update_draft_order( $request );
+ // Order save-point: 1.
/**
* Persist additional fields, order notes and payment method for order.
*/
$this->update_order_from_request( $request );
+ // Order save-point: 2.
if ( $request->get_param( '__experimental_calc_totals' ) ) {
/**
@@ -378,8 +380,6 @@ class Checkout extends AbstractCartRoute {
$this->cart_controller->validate_cart();
}
- $this->order->save();
-
return $this->prepare_item_for_response(
(object) [
'order' => wc_get_order( $this->order ),
@@ -392,6 +392,29 @@ class Checkout extends AbstractCartRoute {
/**
* Process an order.
*
+ * @throws RouteException On error.
+ * @param \WP_REST_Request<array<string, mixed>> $request Request object.
+ * @return \WP_REST_Response|\WP_Error
+ */
+ protected function get_route_post_response( \WP_REST_Request $request ) { // phpcs:ignore Squiz.Commenting.FunctionComment.IncorrectTypeHint
+ try {
+ return $this->process_order( $request );
+ } catch ( \Throwable $exception ) {
+ if ( $this->order ) {
+ // The optimistic order save bounced back, persist the order as it is to preserve the intermediate state.
+ try {
+ $this->order->save();
+ } catch ( \Throwable $save_exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
+ // Ignore the save exception, the root cause will be bubbled up via re-throwing $exception.
+ }
+ }
+ throw $exception;
+ }
+ }
+
+ /**
+ * Process an order based on optimistic save approach to minimize the number of order saves.
+ *
* 1. Obtain Draft Order
* 2. Process Request
* 3. Process Customer
@@ -399,12 +422,10 @@ class Checkout extends AbstractCartRoute {
* 5. Process Payment
*
* @throws RouteException On error.
- *
- * @param \WP_REST_Request $request Request object.
- *
+ * @param \WP_REST_Request<array<string, mixed>> $request Request object.
* @return \WP_REST_Response|\WP_Error
*/
- protected function get_route_post_response( \WP_REST_Request $request ) {
+ private function process_order( \WP_REST_Request $request ) { // phpcs:ignore Squiz.Commenting.FunctionComment.IncorrectTypeHint
wc_log_order_step( '[Store API #1] Place Order flow initiated', null, false, true );
$validation_callback = $this->validate_callback( $request );
@@ -440,16 +461,19 @@ class Checkout extends AbstractCartRoute {
* uses the up-to-date customer address.
*/
$this->update_customer_from_request( $request );
+ // Customer save-point: 1 (session-stored).
wc_log_order_step( '[Store API #3] Updated customer data from request' );
/**
* Create (or update) Draft Order and process request data.
*/
$this->create_or_update_draft_order( $request );
+ // Order save-point: 1.
wc_log_order_step( '[Store API #4] Created/Updated draft order', array( 'order_object' => $this->order ) );
- $this->update_order_from_request( $request );
+ $this->update_order_from_request( $request, false );
wc_log_order_step( '[Store API #5] Updated order with posted data', array( 'order_object' => $this->order ) );
$this->process_customer( $request );
+ // Customer save-point: 2 (db-stored; optional, guest -> customer transition or customer data has changed).
wc_log_order_step( '[Store API #6] Created and/or persisted customer data from order', array( 'order_object' => $this->order ) );
/**
@@ -522,13 +546,10 @@ class Checkout extends AbstractCartRoute {
'This action was deprecated in WooCommerce Blocks version 7.2.0. Please use woocommerce_store_api_checkout_order_processed instead.'
);
- // Set the order status to 'pending' as an initial step.
- // This allows the order to proceed towards completion. The hook
- // 'woocommerce_store_api_checkout_order_processed' (fired below) can be used
- // to set a custom status *after* this point.
- // If payment isn't needed, the custom status is kept. If payment is needed,
- // the payment gateway's statuses take precedence.
+ // Set initial status to 'pending'; woocommerce_store_api_checkout_order_processed (fired below) can override it.
+ // Custom statuses are preserved when no payment is needed; payment gateway statuses take precedence otherwise.
$this->order->update_status( 'pending' );
+ // Order save-point: 2.
/**
* Fires before an order is processed by the Checkout Block/Store API.
@@ -645,7 +666,6 @@ class Checkout extends AbstractCartRoute {
if ( ! $this->order ) {
$this->order = $this->order_controller->create_order_from_cart();
wc_log_order_step( '[Store API #4::create_or_update_draft_order] Created order from cart', array( 'order_object' => $this->order ) );
-
} else {
$this->order_controller->update_order_from_cart( $this->order, true );
wc_log_order_step( '[Store API #4::create_or_update_draft_order] Updated order from cart', array( 'order_object' => $this->order ) );
@@ -868,12 +888,10 @@ class Checkout extends AbstractCartRoute {
// Associate customer with the order.
$this->order->set_customer_id( $customer_id );
- $this->order->save();
// Set the customer auth cookie.
wc_set_customer_auth_cookie( $customer_id );
wc_log_order_step( '[Store API #6::process_customer] Created new customer', array( 'customer_id' => $customer_id ) );
-
}
// Persist customer address data to account.
diff --git a/plugins/woocommerce/src/StoreApi/Schemas/V1/CartItemSchema.php b/plugins/woocommerce/src/StoreApi/Schemas/V1/CartItemSchema.php
index a7feb68c076..0052bb0620d 100644
--- a/plugins/woocommerce/src/StoreApi/Schemas/V1/CartItemSchema.php
+++ b/plugins/woocommerce/src/StoreApi/Schemas/V1/CartItemSchema.php
@@ -50,6 +50,7 @@ class CartItemSchema extends ItemSchema {
* @param string $cart_item_key Cart item key.
*/
$product_permalink = apply_filters( 'woocommerce_cart_item_permalink', $product->get_permalink(), $cart_item, $cart_item['key'] );
+ $price_decimals = wc_get_price_decimals();
return [
'key' => $cart_item['key'],
@@ -72,10 +73,10 @@ class CartItemSchema extends ItemSchema {
'prices' => (object) $this->prepare_product_price_response( $product, get_option( 'woocommerce_tax_display_cart' ) ),
'totals' => (object) $this->prepare_currency_response(
[
- 'line_subtotal' => $this->prepare_money_response( $cart_item['line_subtotal'], wc_get_price_decimals() ),
- 'line_subtotal_tax' => $this->prepare_money_response( $cart_item['line_subtotal_tax'], wc_get_price_decimals() ),
- 'line_total' => $this->prepare_money_response( $cart_item['line_total'], wc_get_price_decimals() ),
- 'line_total_tax' => $this->prepare_money_response( $cart_item['line_tax'], wc_get_price_decimals() ),
+ 'line_subtotal' => $this->prepare_money_response( $cart_item['line_subtotal'], $price_decimals ),
+ 'line_subtotal_tax' => $this->prepare_money_response( $cart_item['line_subtotal_tax'], $price_decimals ),
+ 'line_total' => $this->prepare_money_response( $cart_item['line_total'], $price_decimals ),
+ 'line_total_tax' => $this->prepare_money_response( $cart_item['line_tax'], $price_decimals ),
]
),
'catalog_visibility' => $product->get_catalog_visibility(),
diff --git a/plugins/woocommerce/src/StoreApi/Schemas/V1/OrderItemSchema.php b/plugins/woocommerce/src/StoreApi/Schemas/V1/OrderItemSchema.php
index 9941817d057..56becf4ae58 100644
--- a/plugins/woocommerce/src/StoreApi/Schemas/V1/OrderItemSchema.php
+++ b/plugins/woocommerce/src/StoreApi/Schemas/V1/OrderItemSchema.php
@@ -124,11 +124,13 @@ class OrderItemSchema extends ItemSchema {
* @return array
*/
public function get_totals( $order_item ) {
+ $price_decimals = wc_get_price_decimals();
+
return [
- 'line_subtotal' => $this->prepare_money_response( $order_item->get_subtotal(), wc_get_price_decimals() ),
- 'line_subtotal_tax' => $this->prepare_money_response( $order_item->get_subtotal_tax(), wc_get_price_decimals() ),
- 'line_total' => $this->prepare_money_response( $order_item->get_total(), wc_get_price_decimals() ),
- 'line_total_tax' => $this->prepare_money_response( $order_item->get_total_tax(), wc_get_price_decimals() ),
+ 'line_subtotal' => $this->prepare_money_response( $order_item->get_subtotal(), $price_decimals ),
+ 'line_subtotal_tax' => $this->prepare_money_response( $order_item->get_subtotal_tax(), $price_decimals ),
+ 'line_total' => $this->prepare_money_response( $order_item->get_total(), $price_decimals ),
+ 'line_total_tax' => $this->prepare_money_response( $order_item->get_total_tax(), $price_decimals ),
];
}
diff --git a/plugins/woocommerce/src/StoreApi/Schemas/V1/ProductSchema.php b/plugins/woocommerce/src/StoreApi/Schemas/V1/ProductSchema.php
index 46d4da98218..a59d00d3406 100644
--- a/plugins/woocommerce/src/StoreApi/Schemas/V1/ProductSchema.php
+++ b/plugins/woocommerce/src/StoreApi/Schemas/V1/ProductSchema.php
@@ -921,6 +921,7 @@ class ProductSchema extends AbstractSchema {
$prices = [];
$tax_display_mode = $this->get_tax_display_mode( $tax_display_mode );
$price_function = $this->get_price_function_from_tax_display_mode( $tax_display_mode );
+ $price_decimals = wc_get_price_decimals();
// If we have a variable product, get the price from the variations (this will use the min value).
if ( $product->is_type( ProductType::VARIABLE ) ) {
@@ -931,9 +932,9 @@ class ProductSchema extends AbstractSchema {
$sale_price = $product->get_sale_price();
}
- $prices['price'] = $this->prepare_money_response( $price_function( $product ), wc_get_price_decimals() );
- $prices['regular_price'] = $this->prepare_money_response( $price_function( $product, [ 'price' => $regular_price ] ), wc_get_price_decimals() );
- $prices['sale_price'] = $this->prepare_money_response( $price_function( $product, [ 'price' => $sale_price ] ), wc_get_price_decimals() );
+ $prices['price'] = $this->prepare_money_response( $price_function( $product ), $price_decimals );
+ $prices['regular_price'] = $this->prepare_money_response( $price_function( $product, [ 'price' => $regular_price ] ), $price_decimals );
+ $prices['sale_price'] = $this->prepare_money_response( $price_function( $product, [ 'price' => $sale_price ] ), $price_decimals );
$prices['price_range'] = $this->get_price_range( $product, $tax_display_mode );
return $this->prepare_currency_response( $prices );
diff --git a/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php b/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php
index 1f357cb3f43..ca93d3c22ab 100644
--- a/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php
+++ b/plugins/woocommerce/src/StoreApi/Utilities/CheckoutTrait.php
@@ -139,8 +139,9 @@ trait CheckoutTrait {
* Update the current order using the posted values from the request.
*
* @param \WP_REST_Request $request Full details about the request.
+ * @param bool $persist Whether to persist the changes right away (defaults to true).
*/
- private function update_order_from_request( \WP_REST_Request $request ) {
+ private function update_order_from_request( \WP_REST_Request $request, bool $persist = true ) {
$this->order->set_customer_note( wc_sanitize_textarea( $request['customer_note'] ) ?? '' );
$payment_method = $this->get_request_payment_method( $request );
if ( null !== $payment_method ) {
@@ -201,7 +202,9 @@ trait CheckoutTrait {
*/
do_action( 'woocommerce_store_api_checkout_update_order_from_request', $this->order, $request );
- $this->order->save();
+ if ( $persist ) {
+ $this->order->save();
+ }
}
/**
diff --git a/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php b/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php
index 95c303abc16..4aeae5755c6 100644
--- a/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php
+++ b/plugins/woocommerce/src/StoreApi/Utilities/OrderController.php
@@ -127,8 +127,9 @@ class OrderController {
* @param \WC_Order $order Order object.
*/
public function sync_customer_data_with_order( \WC_Order $order ) {
- if ( $order->get_customer_id() ) {
- $customer = new \WC_Customer( $order->get_customer_id() );
+ $customer_id = $order->get_customer_id();
+ if ( $customer_id ) {
+ $customer = new \WC_Customer( $customer_id );
$customer->set_props(
array(
'billing_first_name' => $order->get_billing_first_name(),
@@ -154,9 +155,7 @@ class OrderController {
'shipping_phone' => $order->get_shipping_phone(),
)
);
-
$this->additional_fields_controller->sync_customer_additional_fields_with_order( $order, $customer );
-
$customer->save();
}
}
diff --git a/plugins/woocommerce/src/StoreApi/Utilities/ProductItemTrait.php b/plugins/woocommerce/src/StoreApi/Utilities/ProductItemTrait.php
index 80aeeecf84b..da60dbba4c2 100644
--- a/plugins/woocommerce/src/StoreApi/Utilities/ProductItemTrait.php
+++ b/plugins/woocommerce/src/StoreApi/Utilities/ProductItemTrait.php
@@ -15,16 +15,17 @@ trait ProductItemTrait {
* @return array
*/
protected function prepare_product_price_response( \WC_Product $product, $tax_display_mode = '' ) {
- $tax_display_mode = $this->get_tax_display_mode( $tax_display_mode );
- $price_function = $this->get_price_function_from_tax_display_mode( $tax_display_mode );
- $prices = parent::prepare_product_price_response( $product, $tax_display_mode );
+ $tax_display_mode = $this->get_tax_display_mode( $tax_display_mode );
+ $price_function = $this->get_price_function_from_tax_display_mode( $tax_display_mode );
+ $prices = parent::prepare_product_price_response( $product, $tax_display_mode );
+ $rounding_precision = wc_get_rounding_precision();
// Add raw prices (prices with greater precision).
$prices['raw_prices'] = array(
- 'precision' => wc_get_rounding_precision(),
- 'price' => $this->prepare_money_response( $price_function( $product ), wc_get_rounding_precision() ),
- 'regular_price' => $this->prepare_money_response( $price_function( $product, array( 'price' => $product->get_regular_price() ) ), wc_get_rounding_precision() ),
- 'sale_price' => $this->prepare_money_response( $price_function( $product, array( 'price' => $product->get_sale_price() ) ), wc_get_rounding_precision() ),
+ 'precision' => $rounding_precision,
+ 'price' => $this->prepare_money_response( $price_function( $product ), $rounding_precision ),
+ 'regular_price' => $this->prepare_money_response( $price_function( $product, array( 'price' => $product->get_regular_price() ) ), $rounding_precision ),
+ 'sale_price' => $this->prepare_money_response( $price_function( $product, array( 'price' => $product->get_sale_price() ) ), $rounding_precision ),
);
return $prices;