Commit 3fad2674bab for woocommerce
commit 3fad2674babbd767ab92c922c8cac47cd551aac9
Author: Oleksandr Aratovskyi <79862886+oaratovskyi@users.noreply.github.com>
Date: Fri Sep 25 13:33:45 2026 +0300
Fix cleared shipping costs in order edits (#69062)
* fix: save cleared shipping costs as zero
The order editor shows zero when a shipping cost is cleared, but saving the field persisted an empty string. This left stored shipping item data inconsistent with the editor.
Normalize only admin shipping cost strings that format to an empty value before saving. Keep all other inputs on their existing path.
Refs #44110
* chore: add shipping cost fix changelog entry
The admin shipping cost fix changes what new edits store, so merchants need a release note. Add a patch fix entry describing the change.
Refs #44110
diff --git a/plugins/woocommerce/changelog/fix-44110-empty-shipping-cost b/plugins/woocommerce/changelog/fix-44110-empty-shipping-cost
new file mode 100644
index 00000000000..038dda28b44
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-44110-empty-shipping-cost
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Save a cleared shipping cost as 0 when editing an order.
diff --git a/plugins/woocommerce/includes/admin/wc-admin-functions.php b/plugins/woocommerce/includes/admin/wc-admin-functions.php
index b78516d2b00..8953b44b651 100644
--- a/plugins/woocommerce/includes/admin/wc-admin-functions.php
+++ b/plugins/woocommerce/includes/admin/wc-admin-functions.php
@@ -425,6 +425,11 @@ function wc_save_order_items( $order_id, $items ) {
$item_data[ $key ] = isset( $items[ $key ][ $item_id ] ) ? wc_clean( wp_unslash( $items[ $key ][ $item_id ] ) ) : $default;
}
+ // A cleared cost field shows a 0 placeholder, so store 0 rather than an empty string.
+ if ( is_string( $item_data['shipping_cost'] ) && '' === wc_format_decimal( $item_data['shipping_cost'] ) ) {
+ $item_data['shipping_cost'] = 0;
+ }
+
$item_data['shipping_method'] = is_string( $item_data['shipping_method'] ) ? $item_data['shipping_method'] : '';
$item_data['shipping_method_title'] = is_string( $item_data['shipping_method_title'] ) ? $item_data['shipping_method_title'] : '';
diff --git a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-functions-test.php b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-functions-test.php
index 43f92b51182..2aa16e9bd22 100644
--- a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-functions-test.php
+++ b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-functions-test.php
@@ -612,6 +612,49 @@ class WC_Admin_Functions_Test extends \WC_Unit_Test_Case {
);
}
+ /**
+ * @testdox wc_save_order_items() should store the expected shipping cost after an admin edit.
+ *
+ * @dataProvider shipping_cost_data_provider
+ * @param string $posted_cost The submitted shipping cost.
+ * @param string $expected_cost The cost expected after saving.
+ */
+ public function test_wc_save_order_items_saves_shipping_cost( string $posted_cost, string $expected_cost ): void {
+ $order = WC_Helper_Order::create_order();
+ $shipping_item = new WC_Order_Item_Shipping();
+ $shipping_item->set_order_id( $order->get_id() );
+ $shipping_item->set_total( '10' );
+ $item_id = $shipping_item->save();
+
+ $items = array(
+ 'shipping_method_id' => array( $item_id ),
+ 'shipping_method' => array( $item_id => 'flat_rate' ),
+ 'shipping_method_title' => array( $item_id => 'Flat rate' ),
+ 'shipping_cost' => array( $item_id => $posted_cost ),
+ 'shipping_taxes' => array( $item_id => array() ),
+ );
+
+ wc_save_order_items( $order->get_id(), $items );
+
+ $saved_item = new WC_Order_Item_Shipping( $item_id );
+ $this->assertSame( $expected_cost, $saved_item->get_total(), 'The saved shipping total should match the submitted cost' );
+ }
+
+ /**
+ * Shipping cost inputs and their expected stored values.
+ *
+ * @return array<string, array{string, string}>
+ */
+ public function shipping_cost_data_provider(): array {
+ return array(
+ 'empty' => array( '', '0' ),
+ 'whitespace' => array( ' ', '0' ),
+ 'nonnumeric' => array( 'abc', '0' ),
+ 'normal cost' => array( '12.50', '12.50' ),
+ 'zero cost' => array( '0', '0' ),
+ );
+ }
+
/**
* @testdox wc_save_order_items() should preserve an existing shipping line named Shipping.
*