Commit 1d6df22e27e for woocommerce

commit 1d6df22e27ecdbfb185520f11c1a77724d6d87e2
Author: Faisal Ahammad <faisalahammad24@gmail.com>
Date:   Thu Aug 13 01:02:00 2026 +0600

    Add notice to highlight the refund fix tool (#66265)

    * add(analytics): add notice to highlight refund fix tool

    - Add FullRefundFixDataToolNotice inbox note pointing merchants to the
      existing full refund data fix tool on Status > Tools
    - Widen Analytics::should_show_refund_fix_tool() to public static so
      the note can reuse the same eligibility check as the tool itself
    - Register the note in Events.php and auto-remove it once the store
      no longer has legacy refund data

    Merchants had no way to discover the refund fix tool added in a
    previous release since it only lives on Status > Tools. This adds an
    inbox notice nudging them there when their store actually needs it.

    Closes #62151

    * Fix refund fix tool notice to auto-hide when data fixed

    Replace deprecated Features::is_enabled with FeaturesUtil::feature_is_enabled, bump @since to 11.2.0, and decouple is_applicable from the tool-row visibility flag so the notice hides once the underlying refund data is fixed rather than lingering until the tool row is dismissed.

    * fix(analytics): narrow should_show_refund_fix_tool visibility to internal use

    - Revert should_show_refund_fix_tool() from public to private static, it is
      now only referenced inside Analytics.
    - Mark FullRefundFixDataToolNotice as @internal.

    Addresses PR feedback.

    Refs #66265

diff --git a/plugins/woocommerce/changelog/fix-62151-refund-fix-tool-notice b/plugins/woocommerce/changelog/fix-62151-refund-fix-tool-notice
new file mode 100644
index 00000000000..2b76f2d7769
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-62151-refund-fix-tool-notice
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add inbox notice pointing merchants to the full refund data fix tool
diff --git a/plugins/woocommerce/src/Internal/Admin/Analytics.php b/plugins/woocommerce/src/Internal/Admin/Analytics.php
index 63bcc8934b3..07b7bdc2cc1 100644
--- a/plugins/woocommerce/src/Internal/Admin/Analytics.php
+++ b/plugins/woocommerce/src/Internal/Admin/Analytics.php
@@ -77,7 +77,7 @@ class Analytics {
 		// flag is cleared (clearing happens before the first batch is queued).
 		add_action( 'woocommerce_analytics_refund_fix_batch', array( $this, 'process_refund_fix_batch' ) );

-		if ( $this->should_show_refund_fix_tool() ) {
+		if ( self::should_show_refund_fix_tool() ) {
 			add_filter( 'woocommerce_debug_tools', array( $this, 'register_full_refund_fix_data_tool' ) );
 			add_action( 'admin_footer', array( $this, 'output_refund_fix_tool_js' ) );
 			add_action( 'wp_ajax_woocommerce_check_refund_fix_needed', array( $this, 'ajax_check_refund_fix_needed' ) );
@@ -209,7 +209,7 @@ class Analytics {
 	 *
 	 * @return bool
 	 */
-	private function should_show_refund_fix_tool(): bool {
+	private static function should_show_refund_fix_tool(): bool {
 		return ! OrderUtil::uses_new_full_refund_data()
 			|| 'yes' === get_option( 'woocommerce_analytics_show_old_refund_data_tool' );
 	}
diff --git a/plugins/woocommerce/src/Internal/Admin/Events.php b/plugins/woocommerce/src/Internal/Admin/Events.php
index 5ba281f0571..1217d773e18 100644
--- a/plugins/woocommerce/src/Internal/Admin/Events.php
+++ b/plugins/woocommerce/src/Internal/Admin/Events.php
@@ -14,6 +14,7 @@ use Automattic\WooCommerce\Internal\Admin\Notes\EditProductsOnTheMove;
 use Automattic\WooCommerce\Internal\Admin\Notes\EmailImprovements;
 use Automattic\WooCommerce\Internal\Admin\Notes\EUVATNumber;
 use Automattic\WooCommerce\Internal\Admin\Notes\FirstProduct;
+use Automattic\WooCommerce\Internal\Admin\Notes\FullRefundFixDataToolNotice;
 use Automattic\WooCommerce\Internal\Admin\Notes\InstallJPAndWCSPlugins;
 use Automattic\WooCommerce\Internal\Admin\Notes\LaunchChecklist;
 use Automattic\WooCommerce\Internal\Admin\Notes\MagentoMigration;
@@ -72,6 +73,7 @@ class Events {
 		EmailImprovements::class,
 		EUVATNumber::class,
 		FirstProduct::class,
+		FullRefundFixDataToolNotice::class,
 		LaunchChecklist::class,
 		MagentoMigration::class,
 		ManageOrdersOnTheGo::class,
@@ -199,6 +201,7 @@ class Events {
 	protected function possibly_delete_notes() {
 		PaymentsRemindMeLater::delete_if_not_applicable();
 		PaymentsMoreInfoNeeded::delete_if_not_applicable();
+		FullRefundFixDataToolNotice::delete_if_not_applicable();
 	}

 	/**
diff --git a/plugins/woocommerce/src/Internal/Admin/Notes/FullRefundFixDataToolNotice.php b/plugins/woocommerce/src/Internal/Admin/Notes/FullRefundFixDataToolNotice.php
new file mode 100644
index 00000000000..e311e23e830
--- /dev/null
+++ b/plugins/woocommerce/src/Internal/Admin/Notes/FullRefundFixDataToolNotice.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * WooCommerce Admin Full Refund Fix Data Tool Notice Provider.
+ *
+ * Adds a note to the merchant's inbox pointing to the full refund fix tool on
+ * the WooCommerce > Status > Tools page.
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Internal\Admin\Notes;
+
+defined( 'ABSPATH' ) || exit;
+
+use Automattic\WooCommerce\Admin\Notes\Note;
+use Automattic\WooCommerce\Admin\Notes\NoteTraits;
+use Automattic\WooCommerce\Utilities\FeaturesUtil;
+use Automattic\WooCommerce\Utilities\OrderUtil;
+
+/**
+ * FullRefundFixDataToolNotice
+ *
+ * @internal
+ * @since 11.2.0
+ */
+class FullRefundFixDataToolNotice {
+	/**
+	 * Note traits.
+	 */
+	use NoteTraits;
+
+	/**
+	 * Name of the note for use in the database.
+	 */
+	const NOTE_NAME = 'wc-admin-full-refund-fix-data-tool';
+
+	/**
+	 * Should this note exist?
+	 *
+	 * @return bool
+	 */
+	public static function is_applicable() {
+		if ( ! FeaturesUtil::feature_is_enabled( 'analytics' ) ) {
+			return false;
+		}
+
+		// The notice follows the underlying data state, not the tool-row
+		// visibility flag (`woocommerce_analytics_show_old_refund_data_tool`),
+		// so it auto-hides once the fix has been applied.
+		return ! OrderUtil::uses_new_full_refund_data();
+	}
+
+	/**
+	 * Get the note.
+	 *
+	 * @return Note|null
+	 */
+	public static function get_note() {
+		if ( ! self::is_applicable() ) {
+			return null;
+		}
+
+		$note = new Note();
+
+		$note->set_title( __( 'Fix your refund data in Analytics', 'woocommerce' ) );
+		$note->set_content(
+			__( 'We found some refunded orders where the full refund amount was not recorded correctly in your Analytics reports. Use the full refund fix tool on the Status page to re-import the affected data.', 'woocommerce' )
+		);
+		$note->set_content_data( (object) array() );
+		$note->set_type( Note::E_WC_ADMIN_NOTE_WARNING );
+		$note->set_name( self::NOTE_NAME );
+		$note->set_source( 'woocommerce-admin' );
+
+		$note->add_action(
+			'full-refund-fix-data-tool_view',
+			__( 'Fix refund data', 'woocommerce' ),
+			admin_url( 'admin.php?page=wc-status&tab=tools' ),
+			Note::E_WC_ADMIN_NOTE_UNACTIONED,
+			true
+		);
+
+		return $note;
+	}
+}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Notes/FullRefundFixDataToolNoticeTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Notes/FullRefundFixDataToolNoticeTest.php
new file mode 100644
index 00000000000..1249f970b51
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Notes/FullRefundFixDataToolNoticeTest.php
@@ -0,0 +1,167 @@
+<?php
+/**
+ * Tests for FullRefundFixDataToolNotice class.
+ */
+
+declare( strict_types=1 );
+
+namespace Automattic\WooCommerce\Tests\Internal\Admin\Notes;
+
+use Automattic\WooCommerce\Internal\Admin\Notes\FullRefundFixDataToolNotice;
+use Automattic\WooCommerce\Admin\Notes\Note;
+use WC_Unit_Test_Case;
+
+/**
+ * Class FullRefundFixDataToolNoticeTest
+ */
+class FullRefundFixDataToolNoticeTest extends WC_Unit_Test_Case {
+
+	/**
+	 * Test is_applicable returns false when the analytics feature is disabled.
+	 */
+	public function test_is_applicable_returns_false_when_analytics_disabled() {
+		update_option( 'woocommerce_analytics_enabled', 'no' );
+		delete_option( 'woocommerce_db_version' );
+		update_option( 'woocommerce_analytics_uses_old_full_refund_data', 'yes' );
+
+		$this->assertFalse( FullRefundFixDataToolNotice::is_applicable() );
+	}
+
+	/**
+	 * Test is_applicable returns false for stores without legacy refund data.
+	 */
+	public function test_is_applicable_returns_false_without_legacy_refund_data() {
+		update_option( 'woocommerce_db_version', '10.2.0' );
+		delete_option( 'woocommerce_analytics_uses_old_full_refund_data' );
+		delete_option( 'woocommerce_analytics_show_old_refund_data_tool' );
+
+		$this->assertFalse( FullRefundFixDataToolNotice::is_applicable() );
+	}
+
+	/**
+	 * Test is_applicable returns true when the store has legacy refund data
+	 * and the DB schema is below the threshold where new data applies.
+	 */
+	public function test_is_applicable_returns_true_with_legacy_refund_data() {
+		delete_option( 'woocommerce_db_version' );
+		update_option( 'woocommerce_analytics_uses_old_full_refund_data', 'yes' );
+
+		$this->assertTrue( FullRefundFixDataToolNotice::is_applicable() );
+	}
+
+	/**
+	 * Test is_applicable returns false once the underlying data has been fixed
+	 * (legacy flag cleared) even if the tool row is still flagged as visible
+	 * pending merchant dismissal. The notice follows the data, not the tool row.
+	 */
+	public function test_is_applicable_returns_false_once_data_fixed_even_if_tool_still_visible() {
+		update_option( 'woocommerce_db_version', '10.2.0' );
+		delete_option( 'woocommerce_analytics_uses_old_full_refund_data' );
+		update_option( 'woocommerce_analytics_show_old_refund_data_tool', 'yes' );
+
+		$this->assertFalse( FullRefundFixDataToolNotice::is_applicable() );
+	}
+
+	/**
+	 * Test get_note returns note with expected content and action when applicable.
+	 */
+	public function test_get_note_returns_note_when_applicable() {
+		delete_option( 'woocommerce_db_version' );
+		update_option( 'woocommerce_analytics_uses_old_full_refund_data', 'yes' );
+
+		$note = FullRefundFixDataToolNotice::get_note();
+
+		$this->assertInstanceOf( Note::class, $note );
+		$this->assertEquals( 'Fix your refund data in Analytics', $note->get_title() );
+		$this->assertEquals( Note::E_WC_ADMIN_NOTE_WARNING, $note->get_type() );
+
+		$actions = $note->get_actions();
+		$this->assertCount( 1, $actions, 'Note should have 1 action' );
+		$this->assertEquals( 'full-refund-fix-data-tool_view', $actions[0]->name );
+		$this->assertStringContainsString( 'page=wc-status', $actions[0]->query );
+		$this->assertStringContainsString( 'tab=tools', $actions[0]->query );
+	}
+
+	/**
+	 * Test get_note returns null when not applicable.
+	 */
+	public function test_get_note_returns_null_when_not_applicable() {
+		update_option( 'woocommerce_db_version', '10.2.0' );
+		delete_option( 'woocommerce_analytics_uses_old_full_refund_data' );
+		delete_option( 'woocommerce_analytics_show_old_refund_data_tool' );
+
+		$this->assertNull( FullRefundFixDataToolNotice::get_note() );
+	}
+
+	/**
+	 * Test that the note is added via possibly_add_note for stores with legacy refund data.
+	 */
+	public function test_possibly_add_note_adds_note_when_applicable() {
+		delete_option( 'woocommerce_db_version' );
+		update_option( 'woocommerce_analytics_uses_old_full_refund_data', 'yes' );
+
+		FullRefundFixDataToolNotice::possibly_add_note();
+
+		$data_store = \WC_Data_Store::load( 'admin-note' );
+		$note_ids   = $data_store->get_notes_with_name( FullRefundFixDataToolNotice::NOTE_NAME );
+
+		$this->assertNotEmpty( $note_ids, 'Note should be created when the store has legacy refund data' );
+	}
+
+	/**
+	 * Test that possibly_add_note prevents duplicates.
+	 */
+	public function test_possibly_add_note_prevents_duplicates() {
+		delete_option( 'woocommerce_db_version' );
+		update_option( 'woocommerce_analytics_uses_old_full_refund_data', 'yes' );
+
+		FullRefundFixDataToolNotice::possibly_add_note();
+		FullRefundFixDataToolNotice::possibly_add_note();
+
+		$data_store = \WC_Data_Store::load( 'admin-note' );
+		$note_ids   = $data_store->get_notes_with_name( FullRefundFixDataToolNotice::NOTE_NAME );
+
+		$this->assertCount( 1, $note_ids, 'Only one note should exist' );
+	}
+
+	/**
+	 * Test that the note is removed once the store no longer has legacy refund data.
+	 */
+	public function test_delete_if_not_applicable_removes_note_once_fixed() {
+		delete_option( 'woocommerce_db_version' );
+		update_option( 'woocommerce_analytics_uses_old_full_refund_data', 'yes' );
+		FullRefundFixDataToolNotice::possibly_add_note();
+
+		update_option( 'woocommerce_db_version', '10.2.0' );
+		delete_option( 'woocommerce_analytics_uses_old_full_refund_data' );
+		delete_option( 'woocommerce_analytics_show_old_refund_data_tool' );
+		FullRefundFixDataToolNotice::delete_if_not_applicable();
+
+		$data_store = \WC_Data_Store::load( 'admin-note' );
+		$note_ids   = $data_store->get_notes_with_name( FullRefundFixDataToolNotice::NOTE_NAME );
+
+		$this->assertEmpty( $note_ids, 'Note should be removed once the store no longer has legacy refund data' );
+	}
+
+	/**
+	 * Clean up after each test.
+	 */
+	public function tearDown(): void {
+		parent::tearDown();
+
+		$data_store = \WC_Data_Store::load( 'admin-note' );
+		$note_ids   = $data_store->get_notes_with_name( FullRefundFixDataToolNotice::NOTE_NAME );
+
+		foreach ( $note_ids as $note_id ) {
+			$note = \Automattic\WooCommerce\Admin\Notes\Notes::get_note( $note_id );
+			if ( $note ) {
+				$note->delete();
+			}
+		}
+
+		delete_option( 'woocommerce_analytics_enabled' );
+		delete_option( 'woocommerce_db_version' );
+		delete_option( 'woocommerce_analytics_uses_old_full_refund_data' );
+		delete_option( 'woocommerce_analytics_show_old_refund_data_tool' );
+	}
+}