Commit 997332ce106 for woocommerce
commit 997332ce1062f87e742a834b756ee0ec7d966e29
Author: Samuel Urbanowicz <samiuelson@gmail.com>
Date: Thu Jun 11 15:36:35 2026 +0200
Fix can_be_refunded tax basis for v4 fee and shipping lines (#65665)
* Fix can_be_refunded tax basis for v4 fee and shipping lines
* Add changelog entry for can_be_refunded tax basis fix
* Remove changelog entry
diff --git a/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Orders/Schema/OrderSchema.php b/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Orders/Schema/OrderSchema.php
index b7c09913e8c..0ae3cc336ec 100644
--- a/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Orders/Schema/OrderSchema.php
+++ b/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Orders/Schema/OrderSchema.php
@@ -710,7 +710,7 @@ class OrderSchema extends AbstractSchema {
$item_data = $this->order_shipping_schema->get_item_response( $shipping_line, $request );
$refunded = $refund_data['totals'][ $shipping_line->get_id() ] ?? 0.0;
- $item_data['can_be_refunded'] = ( (float) $shipping_line->get_total() - $refunded ) > 0;
+ $item_data['can_be_refunded'] = ( (float) $shipping_line->get_total() + (float) $shipping_line->get_total_tax() - $refunded ) > 0;
$data['shipping_lines'][] = $item_data;
}
@@ -736,7 +736,7 @@ class OrderSchema extends AbstractSchema {
$item_data = $this->order_fee_schema->get_item_response( $fee_line, $request );
$refunded = $refund_data['totals'][ $fee_line->get_id() ] ?? 0.0;
- $item_data['can_be_refunded'] = ( (float) $fee_line->get_total() - $refunded ) > 0;
+ $item_data['can_be_refunded'] = ( (float) $fee_line->get_total() + (float) $fee_line->get_total_tax() - $refunded ) > 0;
$data['fee_lines'][] = $item_data;
}
diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/Orders/class-wc-rest-orders-v4-can-be-refunded-test.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/Orders/class-wc-rest-orders-v4-can-be-refunded-test.php
index cc514c5651f..83698fdad48 100644
--- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/Orders/class-wc-rest-orders-v4-can-be-refunded-test.php
+++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version4/Orders/class-wc-rest-orders-v4-can-be-refunded-test.php
@@ -530,6 +530,109 @@ class WC_REST_Orders_V4_Can_Be_Refunded_Test extends WC_REST_Unit_Test_Case {
);
}
+ /**
+ * @testdox Partially refunded shipping line with tax keeps can_be_refunded true.
+ *
+ * Regression test for WOOPLUG-6819: refunded fee/shipping totals from
+ * DataUtils::compute_refunded_quantities_and_totals() are tax-inclusive,
+ * so OrderSchema::can_be_refunded must compare against tax-inclusive line totals.
+ */
+ public function test_partially_refunded_shipping_line_with_tax(): void {
+ $order = wc_create_order( array( 'customer_id' => $this->user_id ) );
+
+ $shipping_item = new WC_Order_Item_Shipping();
+ $shipping_item->set_props(
+ array(
+ 'method_title' => 'Flat Rate',
+ 'total' => '10.00',
+ )
+ );
+ $shipping_item->set_taxes(
+ array(
+ 'total' => array( 1 => '1.50' ),
+ )
+ );
+ $shipping_item->save();
+ $order->add_item( $shipping_item );
+ $order->set_status( 'completed' );
+ $order->save();
+ $order->update_taxes();
+ $order->calculate_totals( false );
+
+ wc_create_refund(
+ array(
+ 'order_id' => $order->get_id(),
+ 'amount' => 10.35,
+ 'line_items' => array(
+ $shipping_item->get_id() => array(
+ 'qty' => 0,
+ 'refund_total' => 9.00,
+ 'refund_tax' => array( 1 => 1.35 ),
+ ),
+ ),
+ )
+ );
+
+ $data = $this->get_order_response( $order->get_id() );
+
+ $this->assertTrue(
+ $data['shipping_lines'][0]['can_be_refunded'],
+ 'Partially refunded shipping line with tax should remain refundable'
+ );
+ }
+
+ /**
+ * @testdox Partially refunded fee line with tax keeps can_be_refunded true.
+ *
+ * Regression test for WOOPLUG-6819.
+ */
+ public function test_partially_refunded_fee_line_with_tax(): void {
+ $order = wc_create_order( array( 'customer_id' => $this->user_id ) );
+
+ $fee_item = new WC_Order_Item_Fee();
+ $fee_item->set_props(
+ array(
+ 'name' => 'Test Fee',
+ 'total' => '20.00',
+ )
+ );
+ $fee_item->set_taxes(
+ array(
+ 'total' => array( 1 => '3.00' ),
+ )
+ );
+ $fee_item->save();
+ $order->add_item( $fee_item );
+ $order->set_status( 'completed' );
+ $order->save();
+ $order->update_taxes();
+ $order->calculate_totals( false );
+
+ // Refunded tax-inclusive total (20.50) exceeds the tax-exclusive line total (20.00) but
+ // not the tax-inclusive total (23.00). Under the old buggy comparison this flipped
+ // can_be_refunded to false even though $2.50 of fee + tax remains refundable.
+ wc_create_refund(
+ array(
+ 'order_id' => $order->get_id(),
+ 'amount' => 20.50,
+ 'line_items' => array(
+ $fee_item->get_id() => array(
+ 'qty' => 0,
+ 'refund_total' => 18.00,
+ 'refund_tax' => array( 1 => 2.50 ),
+ ),
+ ),
+ )
+ );
+
+ $data = $this->get_order_response( $order->get_id() );
+
+ $this->assertTrue(
+ $data['fee_lines'][0]['can_be_refunded'],
+ 'Partially refunded fee line with tax should remain refundable'
+ );
+ }
+
/**
* @testdox Zero-priced product line item with quantity follows quantity logic.
*/