Commit be3cdb36b64 for woocommerce

commit be3cdb36b64aa764e9874ccb39d0ff72e69a203a
Author: Samuel Urbanowicz <samiuelson@gmail.com>
Date:   Thu Jul 23 16:37:48 2026 +0200

    Prevent ghost 0.00 refund when partial refunds sum above the order total (#66885)

diff --git a/plugins/woocommerce/changelog/fix-order-fully-refunded-ghost-zero-refund b/plugins/woocommerce/changelog/fix-order-fully-refunded-ghost-zero-refund
new file mode 100644
index 00000000000..e6b23fc7f2c
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-order-fully-refunded-ghost-zero-refund
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Prevent a ghost 0.00 "Order fully refunded" refund from being created when partial refunds sum a float epsilon above the stored order total.
diff --git a/plugins/woocommerce/includes/wc-order-functions.php b/plugins/woocommerce/includes/wc-order-functions.php
index ce3ef477769..c38072d2b1b 100644
--- a/plugins/woocommerce/includes/wc-order-functions.php
+++ b/plugins/woocommerce/includes/wc-order-functions.php
@@ -914,7 +914,10 @@ function wc_order_fully_refunded( $order_id ) {
 	$order      = wc_get_order( $order_id );
 	$max_refund = wc_format_decimal( $order->get_total() - $order->get_total_refunded() );

-	if ( ! $max_refund ) {
+	// Numeric comparison, not truthiness: when prior refunds sum a float epsilon
+	// above the order total, the difference formats to '-0' — a truthy string that
+	// is numerically zero — which would otherwise create a ghost 0.00 refund below.
+	if ( (float) $max_refund <= 0 ) {
 		return;
 	}

diff --git a/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-order-functions.php b/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-order-functions.php
index 9c1dc47c227..11e9a4f787a 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-order-functions.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-order-functions.php
@@ -1445,6 +1445,72 @@ class WC_Tests_Order_Functions extends WC_Unit_Test_Case {
 		$this->assertEquals( -5, $refunded_fee->get_total() );
 	}

+	/**
+	 * @testdox Fully refunding an order through partial refunds does not create a ghost 0.00 refund when a sub-cent residue remains.
+	 *
+	 * The order total can sit a fraction of a cent below the sum of its refunds
+	 * (the float sum of refund amounts lands epsilon-high on meta-based storage:
+	 * 25.94 + 51.88 = 77.820000000000007 vs a 77.82 total). The refunded-status
+	 * transition then computes a tiny negative remainder that wc_format_decimal()
+	 * renders as '-0' — a truthy string that is numerically zero — and
+	 * wc_order_fully_refunded() used to turn it into a third, ghost refund of
+	 * 0.00 with reason "Order fully refunded." The epsilon depends on the data
+	 * store's SUM precision, so the test pins it via the order total getter.
+	 */
+	public function test_wc_order_fully_refunded_ignores_negative_zero_remainder() {
+		$product = WC_Helper_Product::create_simple_product();
+		$product->set_regular_price( 12.97 );
+		$product->save();
+
+		$order = new WC_Order();
+		$order->add_product( $product, 6 );
+		$order->calculate_totals( true );
+		$order->set_status( OrderStatus::COMPLETED );
+		$order->save();
+		$this->assertEquals( 77.82, (float) $order->get_total() );
+
+		$item_id = array_keys( $order->get_items( 'line_item' ) )[0];
+
+		// Reproduce the epsilon deterministically on every data store: the total reads
+		// a hair below the 77.82 the two refunds sum to, so the remainder after the
+		// final refund is a tiny negative that wc_format_decimal() renders as '-0'.
+		$force_epsilon_total = function () {
+			return 77.81999999;
+		};
+		add_filter( 'woocommerce_order_get_total', $force_epsilon_total );
+
+		wc_create_refund(
+			array(
+				'order_id'   => $order->get_id(),
+				'amount'     => 25.94,
+				'line_items' => array(
+					$item_id => array(
+						'qty'          => 2,
+						'refund_total' => 25.94,
+					),
+				),
+			)
+		);
+		wc_create_refund(
+			array(
+				'order_id'   => $order->get_id(),
+				'amount'     => 51.88,
+				'line_items' => array(
+					$item_id => array(
+						'qty'          => 4,
+						'refund_total' => 51.88,
+					),
+				),
+			)
+		);
+
+		remove_filter( 'woocommerce_order_get_total', $force_epsilon_total );
+
+		$order = wc_get_order( $order->get_id() );
+		$this->assertEquals( OrderStatus::REFUNDED, $order->get_status(), 'The second refund consumes the rounded remainder and flips the order to refunded.' );
+		$this->assertCount( 2, $order->get_refunds(), 'Only the two real refunds exist — no ghost 0.00 "Order fully refunded." record.' );
+	}
+
 	/**
 	 * @testdox Creating a refund advances the parent order modified date.
 	 *