Commit 35dfa2e2a5d for woocommerce

commit 35dfa2e2a5ddfa14396a621774b17f60fa70ec4d
Author: Thomas Roberts <5656702+opr@users.noreply.github.com>
Date:   Wed Apr 29 11:10:40 2026 +0100

    Apply CodeRabbit review fixes to Back in Stock Notifications alpha (#64329)

    * Add CodeRabbit review triage for BIS alpha

    * Move CodeRabbit review triage into StockNotifications feature dir

    * Remove HasherHelper shim now that min-WP is 6.8

    * Add changelog entry for HasherHelper shim removal

    * Rename customer-stock-notifications supported product statuses filter

    * Fix operator precedence in stock notifications batch processor

    * Guard unreadable notifications when purging stock notification data

    * Set cancellation source instead of date when cancelling a notification

    * Fix malformed option tags in stock notification create form

    * Drop unused argument passed to verified stock notification email template

    * Separate verification and unsubscribe action keys in stock notifications

    * Guard unreadable notifications in stock notifications privacy eraser

    * Add changelog entry for BIS CodeRabbit review fixes

    * Tick off completed BIS CodeRabbit triage items

    * Add sprint running doc for BIS alpha

    * Address CI failures: phpcs, phpstan baseline, markdown lint

    * Log PR #53641 Playwright port history in BIS sprint doc

    * Point BIS sprint doc at RSM-437 for Playwright e2e port

    * Reconcile handoff post against BIS sprint scope in running doc

    * Link BIS sprint doc to RSM-671 (docs) and RSM-673 (integrations)

    * Fix MD060 lint failure on BIS sprint doc table separator

    * Guard BIS alpha constant define in legacy bootstrap

    Avoids 'already defined' warnings when WOOCOMMERCE_BIS_ALPHA_ENABLED is set at the wp-config level (which happens in the tests env via .wp-env.json on downstream branches).

    * Extract BIS email-action tokens to constants and cover negative paths

    Pulls the 'verify' / 'unsubscribe' tokens shared between the three BIS
    email templates and EmailActionController into class constants
    (ACTION_VERIFY / ACTION_UNSUBSCRIBE), so a drift on either side of the
    URL contract is now a compile-time typo rather than a silent no-op. All
    three email templates that produce these links are updated to reference
    the same constants.

    Adds a default branch to the action switch that logs unknown tokens via
    wc_get_logger()->debug when WP_DEBUG is enabled, so mis-routed email
    links become visible during alpha testing instead of failing silently.

    Expands EmailActionControllerTests with negative cases for: invalid
    verification key, mismatched action key family (verification key
    present, unsubscribe action requested), missing notification id, and
    unknown action token.

    Addresses CodeRabbit nitpicks on PR #64329.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    * Address further CodeRabbit feedback on BIS feature branch

    - Add missing exit after wp_safe_redirect() in both email-action handlers
      so execution stops once the 302 is queued.

    - Guard check_unsubscribe_key against an empty stored hash, mirroring
      check_verification_key semantics. wp_verify_fast_hash tolerates an
      empty string today but the explicit early-return makes intent
      greppable and removes the implicit dependency.

    - Coerce the verification-key meta to string before str_contains so a
      stored null/int can never trip a TypeError.

    - Reword tests/legacy/bootstrap.php BIS comment to reflect that the
      constant is only set as a default — per-suite overrides still win.

    - Parenthesize the expired-verification-key timestamp arithmetic in
      NotificationTests for readability.

    - Separate empty vs unknown action in EmailActionController: empty
      means the caller omitted the argument (silent return); unknown keeps
      the debug-log branch so genuinely mis-routed tokens surface.

    - Replace assertTrue(true) in the no-assertion happy-path test with
      expectNotToPerformAssertions() to avoid PHPUnit's risky-test warning.

    Addresses CodeRabbit nits across PRs #64329, #64367, #64368, #64369,
    #64370, #64371 that share files on the feature branch.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    * Replace BIS doc line-number anchors with symbol references

    Line-number citations in markdown rot every time the target file is
    edited and give a false-precision signal — a reader searching for the
    cited line after a few weeks of churn lands on unrelated code. Replace
    the remaining ones in SPRINT.md with symbol references (method names
    and file stems).

    Also tighten the CODERABBIT-TRIAGE uncertainty-notes wording and add a
    banner at the top of the triage doc making it explicit that the file
    is a pre-GA scratchpad listed in SPRINT.md for deletion, not reference
    documentation — so anyone who lands on it via a search doesn't treat
    the triage notes as the permanent shape of the code.

    Addresses CodeRabbit nits on PRs #64369 (CODERABBIT-TRIAGE wording) and
    #64370 (SPRINT.md anchor format).

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    * Cover the explicit-empty-action early-return branch

    The unknown-token test locks down the switch's default: path (debug-log
    branch). This adds a separate test for the empty-action early return
    that sits before the switch, so the two distinct code paths for
    'caller omitted the argument' vs 'caller passed a bogus token' are
    each asserted in isolation.

    Addresses CodeRabbit nit on PR #64329.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    * Address remaining CR nits on PR #64329

    - Correct triage reconciliation count from 11 to 12 (9 + 2 + 1 = 12).
    - Extract a private arrange_notification() helper in
      EmailActionControllerTests so each test reads as 'given a notification
      with this status and meta, when the controller is called with X,
      expect status Y' instead of duplicating the 6-line setup block seven
      times.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    * Consolidate BIS-alpha changelogs into a single review-cleanup entry

    Replace 64398-bis-email-action-constants, bis-coderabbit-review-fixes, and bis-remove-hasherhelper-shim with one bis-alpha-cr-fixes-and-cleanup entry summarising the scope of #64329.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    * Delete BIS sprint scratch docs ahead of merge

    Removes SPRINT.md, CODERABBIT-TRIAGE.md, and CODERABBIT-TRIAGE-RAW.md from src/Internal/StockNotifications/ — these were sprint-internal scratch files always intended for pre-merge cleanup. Updates the bis-alpha-cr-fixes-and-cleanup changelog entry to reflect the deletion.

    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

    ---------

    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

diff --git a/.markdownlintignore b/.markdownlintignore
index 2b50a5b22a1..b7d5b6b6a81 100644
--- a/.markdownlintignore
+++ b/.markdownlintignore
@@ -2,3 +2,4 @@ plugins/woocommerce-docs/tests/src/Blocks/fixtures/*
 plugins/woocommerce/lib/classes/*
 plugins/woocommerce/lib/packages/*
 packages/php/email-editor/vendor-prefixed/*
+plugins/woocommerce/src/Internal/StockNotifications/CODERABBIT-TRIAGE-RAW.md
diff --git a/plugins/woocommerce/changelog/bis-alpha-cr-fixes-and-cleanup b/plugins/woocommerce/changelog/bis-alpha-cr-fixes-and-cleanup
new file mode 100644
index 00000000000..b2b74bb8302
--- /dev/null
+++ b/plugins/woocommerce/changelog/bis-alpha-cr-fixes-and-cleanup
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Address CodeRabbit review feedback on the Back in Stock Notifications alpha and apply small follow-on cleanups: extract email-link action tokens into `EmailActionController` constants, remove the pre-WP-6.8 hasher compatibility shim now that WooCommerce requires WordPress 6.8, delete the in-tree sprint scratch docs (`SPRINT.md`, `CODERABBIT-TRIAGE.md`, `CODERABBIT-TRIAGE-RAW.md`) ahead of merge, and fix the assorted CR-flagged bugs (cancel-action copy-paste, split verification/unsubscribe keys, guard unreadable notifications in retention and privacy erasers, repair admin create-form option tags, correct background-processor operator precedence, rename typo filter, drop unused sprintf arg).
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index f288568d8b8..39f82099818 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -70452,12 +70452,6 @@ parameters:
 			count: 2
 			path: src/Internal/StockNotifications/AsyncTasks/NotificationsProcessor.php

-		-
-			message: '#^Expression on left side of \?\? is not nullable\.$#'
-			identifier: nullCoalesce.expr
-			count: 1
-			path: src/Internal/StockNotifications/AsyncTasks/NotificationsProcessor.php
-
 		-
 			message: '#^Property Automattic\\WooCommerce\\Internal\\StockNotifications\\AsyncTasks\\NotificationsProcessor\:\:\$logger \(Automattic\\WooCommerce\\Internal\\StockNotifications\\AsyncTasks\\Logger\) does not accept WC_Logger_Interface\.$#'
 			identifier: assign.propertyType
@@ -70470,12 +70464,6 @@ parameters:
 			count: 1
 			path: src/Internal/StockNotifications/AsyncTasks/NotificationsProcessor.php

-		-
-			message: '#^Cannot call method delete\(\) on Automattic\\WooCommerce\\Internal\\StockNotifications\\Notification\|bool\.$#'
-			identifier: method.nonObject
-			count: 1
-			path: src/Internal/StockNotifications/DataRetentionController.php
-
 		-
 			message: '#^Method Automattic\\WooCommerce\\Internal\\StockNotifications\\DataRetentionController\:\:clear_daily_task\(\) has no return type specified\.$#'
 			identifier: missingType.return
@@ -70656,42 +70644,12 @@ parameters:
 			count: 2
 			path: src/Internal/StockNotifications/Emails/EmailActionController.php

-		-
-			message: '#^Cannot call method get_meta\(\) on Automattic\\WooCommerce\\Internal\\StockNotifications\\Notification\|true\.$#'
-			identifier: method.nonObject
-			count: 1
-			path: src/Internal/StockNotifications/Emails/EmailActionController.php
-
 		-
 			message: '#^Cannot call method get_name\(\) on WC_Product\|false\|null\.$#'
 			identifier: method.nonObject
 			count: 2
 			path: src/Internal/StockNotifications/Emails/EmailActionController.php

-		-
-			message: '#^Method Automattic\\WooCommerce\\Internal\\StockNotifications\\Emails\\EmailActionController\:\:get_notification_to_be_processed\(\) never returns null so it can be removed from the return type\.$#'
-			identifier: return.unusedType
-			count: 1
-			path: src/Internal/StockNotifications/Emails/EmailActionController.php
-
-		-
-			message: '#^Method Automattic\\WooCommerce\\Internal\\StockNotifications\\Emails\\EmailActionController\:\:get_notification_to_be_processed\(\) should return Automattic\\WooCommerce\\Internal\\StockNotifications\\Notification\|null but returns Automattic\\WooCommerce\\Internal\\StockNotifications\\Notification\|true\.$#'
-			identifier: return.type
-			count: 1
-			path: src/Internal/StockNotifications/Emails/EmailActionController.php
-
-		-
-			message: '#^Method Automattic\\WooCommerce\\Internal\\StockNotifications\\Emails\\EmailActionController\:\:get_notification_to_be_processed\(\) should return Automattic\\WooCommerce\\Internal\\StockNotifications\\Notification\|null but returns false\.$#'
-			identifier: return.type
-			count: 2
-			path: src/Internal/StockNotifications/Emails/EmailActionController.php
-
-		-
-			message: '#^PHPDoc tag @return with type Automattic\\WooCommerce\\Internal\\StockNotifications\\Notification\|false is not subtype of native type Automattic\\WooCommerce\\Internal\\StockNotifications\\Notification\|null\.$#'
-			identifier: return.phpDocType
-			count: 1
-			path: src/Internal/StockNotifications/Emails/EmailActionController.php
-
 		-
 			message: '#^Access to property \$id on an unknown class Automattic\\WooCommerce\\Internal\\StockNotifications\\Emails\\WC_Email\.$#'
 			identifier: class.notFound
@@ -71040,54 +70998,6 @@ parameters:
 			count: 1
 			path: src/Internal/StockNotifications/NotificationQuery.php

-		-
-			message: '#^Cannot call method get_product_id\(\) on Automattic\\WooCommerce\\Internal\\StockNotifications\\Notification\|bool\.$#'
-			identifier: method.nonObject
-			count: 1
-			path: src/Internal/StockNotifications/Privacy/PrivacyEraser.php
-
-		-
-			message: '#^Cannot call method save\(\) on Automattic\\WooCommerce\\Internal\\StockNotifications\\Notification\|bool\.$#'
-			identifier: method.nonObject
-			count: 1
-			path: src/Internal/StockNotifications/Privacy/PrivacyEraser.php
-
-		-
-			message: '#^Cannot call method set_cancellation_source\(\) on Automattic\\WooCommerce\\Internal\\StockNotifications\\Notification\|bool\.$#'
-			identifier: method.nonObject
-			count: 1
-			path: src/Internal/StockNotifications/Privacy/PrivacyEraser.php
-
-		-
-			message: '#^Cannot call method set_date_cancelled\(\) on Automattic\\WooCommerce\\Internal\\StockNotifications\\Notification\|bool\.$#'
-			identifier: method.nonObject
-			count: 1
-			path: src/Internal/StockNotifications/Privacy/PrivacyEraser.php
-
-		-
-			message: '#^Cannot call method set_status\(\) on Automattic\\WooCommerce\\Internal\\StockNotifications\\Notification\|bool\.$#'
-			identifier: method.nonObject
-			count: 1
-			path: src/Internal/StockNotifications/Privacy/PrivacyEraser.php
-
-		-
-			message: '#^Cannot call method set_user_email\(\) on Automattic\\WooCommerce\\Internal\\StockNotifications\\Notification\|bool\.$#'
-			identifier: method.nonObject
-			count: 1
-			path: src/Internal/StockNotifications/Privacy/PrivacyEraser.php
-
-		-
-			message: '#^Cannot call method set_user_id\(\) on Automattic\\WooCommerce\\Internal\\StockNotifications\\Notification\|bool\.$#'
-			identifier: method.nonObject
-			count: 1
-			path: src/Internal/StockNotifications/Privacy/PrivacyEraser.php
-
-		-
-			message: '#^Cannot call method update_meta_data\(\) on Automattic\\WooCommerce\\Internal\\StockNotifications\\Notification\|bool\.$#'
-			identifier: method.nonObject
-			count: 2
-			path: src/Internal/StockNotifications/Privacy/PrivacyEraser.php
-
 		-
 			message: '#^Method Automattic\\WooCommerce\\Internal\\StockNotifications\\Privacy\\PrivacyEraser\:\:register_erasers_exporters\(\) has no return type specified\.$#'
 			identifier: missingType.return
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Admin/NotificationEditPage.php b/plugins/woocommerce/src/Internal/StockNotifications/Admin/NotificationEditPage.php
index 1a3767622fd..61611c029f6 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Admin/NotificationEditPage.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Admin/NotificationEditPage.php
@@ -77,7 +77,7 @@ class NotificationEditPage {
 			case 'cancel_notification':
 				$notification->set_status( NotificationStatus::CANCELLED );
 				$notification->set_date_cancelled( time() );
-				$notification->set_date_notified( NotificationCancellationSource::ADMIN );
+				$notification->set_cancellation_source( NotificationCancellationSource::ADMIN );
 				$result = $notification->save();
 				if ( is_wp_error( $result ) ) {
 					$notice_message = $result->get_error_message();
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Admin/Templates/html-admin-notification-create.php b/plugins/woocommerce/src/Internal/StockNotifications/Admin/Templates/html-admin-notification-create.php
index 8f8f4460da9..ab7be9f6554 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Admin/Templates/html-admin-notification-create.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Admin/Templates/html-admin-notification-create.php
@@ -100,7 +100,7 @@ use Automattic\WooCommerce\Internal\StockNotifications\Admin\NotificationsPage;
 							?>
 							<select class="wc-customer-search" name="user_id" data-placeholder="<?php esc_attr_e( 'Search for a customer&hellip;', 'woocommerce' ); ?>" data-allow_clear="true">
 								<?php if ( $user_string && $user_id ) { ?>
-									<option value="<?php echo esc_attr( $user_id ); ?>" selected="selected"><?php echo wp_kses_post( htmlspecialchars( $user_string, ENT_COMPAT ) ); ?><option>
+									<option value="<?php echo esc_attr( $user_id ); ?>" selected="selected"><?php echo wp_kses_post( htmlspecialchars( $user_string, ENT_COMPAT ) ); ?></option>
 								<?php } ?>
 							</select>
 							<div class="divider"></div>
@@ -136,9 +136,9 @@ use Automattic\WooCommerce\Internal\StockNotifications\Admin\NotificationsPage;
 							// phpcs:enable WordPress.Security.NonceVerification.Recommended
 							$excluded_product_types = array_diff( array_keys( wc_get_product_types() ), array( 'simple', 'variable' ) );
 							?>
-							<select class="wc-product-search" name="product_id" data-action="woocommerce_json_search_products_and_variations" data-exclude_type="<?php echo esc_attr( implode( ',', $excluded_product_types ) ); ?>" data-display_stock="true"data-placeholder="<?php esc_attr_e( 'Select product&hellip;', 'woocommerce' ); ?>" data-allow_clear="true">
+							<select class="wc-product-search" name="product_id" data-action="woocommerce_json_search_products_and_variations" data-exclude_type="<?php echo esc_attr( implode( ',', $excluded_product_types ) ); ?>" data-display_stock="true" data-placeholder="<?php esc_attr_e( 'Select product&hellip;', 'woocommerce' ); ?>" data-allow_clear="true">
 								<?php if ( $product_string && $product_id ) { ?>
-									<option value="<?php echo esc_attr( $product_id ); ?>" selected="selected"><?php echo wp_kses_post( htmlspecialchars( $product_string, ENT_COMPAT ) ); ?><option>
+									<option value="<?php echo esc_attr( $product_id ); ?>" selected="selected"><?php echo wp_kses_post( htmlspecialchars( $product_string, ENT_COMPAT ) ); ?></option>
 								<?php } ?>
 							</select>
 						</div>
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/AsyncTasks/NotificationsProcessor.php b/plugins/woocommerce/src/Internal/StockNotifications/AsyncTasks/NotificationsProcessor.php
index b7be41e9fa4..4ea1b09f77b 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/AsyncTasks/NotificationsProcessor.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/AsyncTasks/NotificationsProcessor.php
@@ -168,7 +168,7 @@ class NotificationsProcessor {
 			$cycle_state = $this->cycle_state_service->get_or_initialize_cycle_state( $product_id );
 			$product     = $this->parse_product( $product_id );
 		} catch ( \Throwable $e ) {
-			$product_id = (int) $product_id ?? 0;
+			$product_id = (int) $product_id;
 			$this->logger->error(
 				sprintf( 'Background process for product %s terminated. Reason: %s', $product_id, $e->getMessage() ),
 				array(
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Config.php b/plugins/woocommerce/src/Internal/StockNotifications/Config.php
index 6856c042f73..e56a955af75 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Config.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Config.php
@@ -71,7 +71,7 @@ class Config {
 	}

 	/**
-	 * Get the supported product stock statuses.
+	 * Get the supported product statuses.
 	 *
 	 * @return array<string>
 	 */
@@ -81,14 +81,14 @@ class Config {
 		}

 		/**
-		 * Filter: woocommerce_customer_stock_notifications_supported_product_stock_statuses
+		 * Filter: woocommerce_customer_stock_notifications_supported_product_statuses
 		 *
 		 * @since 10.2.0
 		 *
-		 * @param array $product_stock_statuses Product stock statuses.
+		 * @param array $product_statuses Product statuses.
 		 */
 		self::$supported_product_statuses = (array) apply_filters(
-			'woocommerce_customer_stock_notifications_supported_product_stock_statuses',
+			'woocommerce_customer_stock_notifications_supported_product_statuses',
 			array(
 				ProductStatus::PUBLISH,
 			)
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/DataRetentionController.php b/plugins/woocommerce/src/Internal/StockNotifications/DataRetentionController.php
index 7aa88a88749..b395557de2b 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/DataRetentionController.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/DataRetentionController.php
@@ -85,7 +85,9 @@ class DataRetentionController {

 		foreach ( $overdue_notifications as $notification_id ) {
 			$notification = Factory::get_notification( $notification_id );
-			$notification->delete();
+			if ( $notification instanceof Notification ) {
+				$notification->delete();
+			}
 		}
 	}
 }
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationEmail.php b/plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationEmail.php
index c6a4483c96b..00a88725868 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationEmail.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationEmail.php
@@ -186,6 +186,7 @@ class CustomerStockNotificationEmail extends WC_Email {
 			'button_link'      => $button_link,
 			'unsubscribe_link' => add_query_arg(
 				array(
+					'email_link_action'     => EmailActionController::ACTION_UNSUBSCRIBE,
 					'email_link_action_key' => $unsubscribe_key,
 					'notification_id'       => $notification->get_id(),
 				),
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationVerifiedEmail.php b/plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationVerifiedEmail.php
index 33a1b49991e..14edb78dde6 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationVerifiedEmail.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationVerifiedEmail.php
@@ -149,6 +149,7 @@ class CustomerStockNotificationVerifiedEmail extends WC_Email {
 			'is_guest'         => $is_guest,
 			'unsubscribe_link' => add_query_arg(
 				array(
+					'email_link_action'     => EmailActionController::ACTION_UNSUBSCRIBE,
 					'email_link_action_key' => $unsubscribe_key,
 					'notification_id'       => $notification->get_id(),
 				),
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationVerifyEmail.php b/plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationVerifyEmail.php
index 3a0e3a5f1e7..af9d30bc53b 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationVerifyEmail.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationVerifyEmail.php
@@ -173,6 +173,7 @@ class CustomerStockNotificationVerifyEmail extends WC_Email {
 			'verification_expiration_threshold' => $expiration_threshold_text,
 			'verification_link'                 => add_query_arg(
 				array(
+					'email_link_action'     => EmailActionController::ACTION_VERIFY,
 					'email_link_action_key' => $verification_key,
 					'notification_id'       => $notification->get_id(),
 				),
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Emails/EmailActionController.php b/plugins/woocommerce/src/Internal/StockNotifications/Emails/EmailActionController.php
index f336a9f6645..1a74bdf55e1 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Emails/EmailActionController.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Emails/EmailActionController.php
@@ -17,6 +17,22 @@ use Automattic\WooCommerce\Internal\StockNotifications\Notification;
  * @package Automattic\WooCommerce\Internal\StockNotifications\Emails
  */
 class EmailActionController {
+	/**
+	 * Action token for verifying (double opt-in) a pending notification sign-up.
+	 *
+	 * Must match the `email_link_action` query param produced by the verify
+	 * email template.
+	 */
+	public const ACTION_VERIFY = 'verify';
+
+	/**
+	 * Action token for unsubscribing an active notification sign-up.
+	 *
+	 * Must match the `email_link_action` query param produced by the "back in
+	 * stock" and confirmation email templates.
+	 */
+	public const ACTION_UNSUBSCRIBE = 'unsubscribe';
+
 	/**
 	 * EmailActionController constructor.
 	 *
@@ -31,41 +47,69 @@ class EmailActionController {
 	 */
 	public function maybe_process_email_action(): void {
 		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
-		if ( ! isset( $_GET['notification_id'] ) || ! isset( $_GET['email_link_action_key'] ) ) {
+		if ( ! isset( $_GET['notification_id'] ) || ! isset( $_GET['email_link_action_key'] ) || ! isset( $_GET['email_link_action'] ) ) {
 			return;
 		}
 		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
 		$notification_id = absint( wp_unslash( $_GET['notification_id'] ) );
 		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
 		$action_key = sanitize_text_field( wp_unslash( $_GET['email_link_action_key'] ) );
+		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+		$action = sanitize_key( wp_unslash( $_GET['email_link_action'] ) );

-		$this->validate_and_maybe_process_request( $notification_id, $action_key );
+		$this->validate_and_maybe_process_request( $notification_id, $action_key, $action );
 	}

 	/**
-	 * Checks request parameters and processes the notification based on the action key.
+	 * Checks request parameters and processes the notification based on the action type.
 	 *
-	 * @param int    $notification_id The ID of the notification to process.
+	 * @param int    $notification_id       The ID of the notification to process.
 	 * @param string $email_link_action_key The action key from the email link.
+	 * @param string $action                The action to perform: 'verify' or 'unsubscribe'.
 	 * @return void
 	 */
-	public function validate_and_maybe_process_request( int $notification_id, string $email_link_action_key ): void {
+	public function validate_and_maybe_process_request( int $notification_id, string $email_link_action_key, string $action = '' ): void {
 		if ( empty( $email_link_action_key ) || empty( $notification_id ) ) {
 			return;
 		}

+		// An empty $action means the caller omitted the routing argument — a
+		// programming error, not a mis-routed email. Return silently so the
+		// debug branch in the switch is reserved for genuinely-unknown
+		// tokens arriving in the wild.
+		if ( '' === $action ) {
+			return;
+		}
+
 		$notification = $this->get_notification_to_be_processed( $notification_id );

 		if ( ! $notification ) {
 			return;
 		}

-		$action_key = $notification->get_meta( 'email_link_action_key' );
-		if ( strpos( $action_key, ':' ) !== false ) {
-			$this->process_verification_action( $notification, $email_link_action_key );
-		} else {
-			$this->process_unsubscribe_action( $notification, $email_link_action_key );
-		}
+		switch ( $action ) {
+			case self::ACTION_VERIFY:
+				$this->process_verification_action( $notification, $email_link_action_key );
+				break;
+			case self::ACTION_UNSUBSCRIBE:
+				$this->process_unsubscribe_action( $notification, $email_link_action_key );
+				break;
+			default:
+				// Unknown action — silently drop in production, log in debug so
+				// mis-routed email links surface during alpha testing rather
+				// than no-op'ing invisibly.
+				if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
+					wc_get_logger()->debug(
+						sprintf(
+							'Unknown email_link_action "%s" for notification %d',
+							$action,
+							$notification->get_id()
+						),
+						array( 'source' => 'stock-notifications' )
+					);
+				}
+				break;
+		}//end switch
 	}

 	/**
@@ -101,6 +145,7 @@ class EmailActionController {
 			 */
 			$url = apply_filters( 'woocommerce_customer_stock_notification_verified_redirect_url', get_permalink( wc_get_page_id( 'shop' ) ) );
 			wp_safe_redirect( $url );
+			exit;
 		}
 	}

@@ -138,26 +183,19 @@ class EmailActionController {
 			 */
 			$url = apply_filters( 'woocommerce_customer_stock_notification_unsubscribe_redirect_url', get_permalink( wc_get_page_id( 'shop' ) ) );
 			wp_safe_redirect( $url );
+			exit;
 		}
 	}

 	/**
-	 * Retrieves the notification to be processed based on the provided notification ID and action key.
+	 * Retrieves the notification to be processed based on the provided notification ID.
 	 *
 	 * @param int $notification_id The ID of the notification to process.
-	 * @return Notification|false The notification object if found and has an action key, null otherwise.
+	 * @return Notification|null The notification object if found, null otherwise.
 	 */
 	private function get_notification_to_be_processed( int $notification_id ): ?Notification {
 		$notification = Factory::get_notification( (int) $notification_id );

-		if ( ! $notification ) {
-			return false;
-		}
-
-		if ( empty( $notification->get_meta( 'email_link_action_key' ) ) ) {
-			return false;
-		}
-
-		return $notification;
+		return $notification instanceof Notification ? $notification : null;
 	}
 }
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Notification.php b/plugins/woocommerce/src/Internal/StockNotifications/Notification.php
index 3765a1d55a3..791594e05a9 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Notification.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Notification.php
@@ -9,7 +9,6 @@ namespace Automattic\WooCommerce\Internal\StockNotifications;

 use Automattic\WooCommerce\Internal\StockNotifications\Enums\NotificationStatus;
 use Automattic\WooCommerce\Internal\StockNotifications\Enums\NotificationCancellationSource;
-use Automattic\WooCommerce\Internal\StockNotifications\Utilities\HasherHelper;

 defined( 'ABSPATH' ) || exit;

@@ -459,7 +458,7 @@ class Notification extends \WC_Data {
 	 * @return bool True if the key is valid, false otherwise.
 	 */
 	public function check_verification_key( string $key ): bool {
-		$action_key = $this->get_meta( 'email_link_action_key' );
+		$action_key = (string) $this->get_meta( 'verification_action_key' );

 		if ( ! str_contains( $action_key, ':' ) ) {
 			return false;
@@ -472,20 +471,18 @@ class Notification extends \WC_Data {
 			return false;
 		}

-		return HasherHelper::wp_verify_fast_hash( $key, $hash );
+		return wp_verify_fast_hash( $key, $hash );
 	}

 	/**
-	 * Maybe setup verification data for the notification.
-	 *
-	 * This is used to ensure that the notification has valid verification data.
+	 * Generate a verification key and store its hash.
 	 *
 	 * @param bool $persist If true, save the changes to the database.
 	 * @return string The generated verification key.
 	 */
 	public function get_verification_key( bool $persist ): string {
 		$key = wp_generate_password( 20, false );
-		$this->update_meta_data( 'email_link_action_key', time() . ':' . HasherHelper::wp_fast_hash( $key ) );
+		$this->update_meta_data( 'verification_action_key', time() . ':' . wp_fast_hash( $key ) );

 		if ( $persist ) {
 			$this->save();
@@ -501,21 +498,22 @@ class Notification extends \WC_Data {
 	 * @return bool True if the key is valid, false otherwise.
 	 */
 	public function check_unsubscribe_key( string $key ): bool {
-		return HasherHelper::wp_verify_fast_hash( $key, $this->get_meta( 'email_link_action_key' ) );
+		$stored = (string) $this->get_meta( 'unsubscribe_action_key' );
+		if ( '' === $stored ) {
+			return false;
+		}
+		return wp_verify_fast_hash( $key, $stored );
 	}

 	/**
-	 * Maybe setup verification data for the notification.
-	 *
-	 * This is used to ensure that the notification has valid verification data.
+	 * Generate an unsubscribe key and store its hash.
 	 *
 	 * @param bool $persist If true, save the changes to the database.
 	 * @return string The generated unsubscribe key.
 	 */
 	public function get_unsubscribe_key( bool $persist ): string {
-		$key  = wp_generate_password( 20, false );
-		$hash = HasherHelper::wp_fast_hash( $key );
-		$this->update_meta_data( 'email_link_action_key', $hash );
+		$key = wp_generate_password( 20, false );
+		$this->update_meta_data( 'unsubscribe_action_key', wp_fast_hash( $key ) );

 		if ( $persist ) {
 			$this->save();
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Privacy/PrivacyEraser.php b/plugins/woocommerce/src/Internal/StockNotifications/Privacy/PrivacyEraser.php
index 0647aa62e1b..1a3d8b2b746 100644
--- a/plugins/woocommerce/src/Internal/StockNotifications/Privacy/PrivacyEraser.php
+++ b/plugins/woocommerce/src/Internal/StockNotifications/Privacy/PrivacyEraser.php
@@ -7,6 +7,7 @@ namespace Automattic\WooCommerce\Internal\StockNotifications\Privacy;
 use Automattic\WooCommerce\Internal\StockNotifications\Enums\NotificationCancellationSource;
 use Automattic\WooCommerce\Internal\StockNotifications\Factory;
 use Automattic\WooCommerce\Internal\StockNotifications\Enums\NotificationStatus;
+use Automattic\WooCommerce\Internal\StockNotifications\Notification;
 use Automattic\WooCommerce\Internal\StockNotifications\NotificationQuery;

 /**
@@ -61,15 +62,20 @@ class PrivacyEraser extends \WC_Abstract_Privacy {
 		);

 		foreach ( $notifications as $notification_id ) {
-			$notification    = Factory::get_notification( $notification_id );
+			$notification = Factory::get_notification( $notification_id );
+			if ( ! $notification instanceof Notification ) {
+				continue;
+			}
+
 			$anonymous_email = wp_privacy_anonymize_data( 'email', $email_address );
 			$notification->set_user_email( $anonymous_email );
 			$notification->set_user_id( 0 );
 			$notification->set_status( NotificationStatus::CANCELLED );
 			$notification->set_cancellation_source( NotificationCancellationSource::USER );
-			$notification->set_date_cancelled( current_time( 'mysql' ) );
+			$notification->set_date_cancelled( time() );
 			$notification->update_meta_data( '_anonymized', 'yes' );
-			$notification->update_meta_data( 'email_link_action_key', '' );
+			$notification->update_meta_data( 'verification_action_key', '' );
+			$notification->update_meta_data( 'unsubscribe_action_key', '' );
 			$notification->save();
 			$response['messages'][] = sprintf(
 			/* translators: %d the numeric product ID */
@@ -77,7 +83,7 @@ class PrivacyEraser extends \WC_Abstract_Privacy {
 				$notification->get_product_id()
 			);
 			$response['items_removed'] = true;
-		}
+		}//end foreach

 		return $response;
 	}
diff --git a/plugins/woocommerce/src/Internal/StockNotifications/Utilities/HasherHelper.php b/plugins/woocommerce/src/Internal/StockNotifications/Utilities/HasherHelper.php
deleted file mode 100644
index 95ab1125dab..00000000000
--- a/plugins/woocommerce/src/Internal/StockNotifications/Utilities/HasherHelper.php
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-
-declare( strict_types=1 );
-
-namespace Automattic\WooCommerce\Internal\StockNotifications\Utilities;
-
-/**
- * Helper class for hashing.
- *
- * Hint: This is a copy of the hashing functions introduced in WordPress 6.8.
- * Once WooCommerce Core requires WordPress 6.8, we can remove/replace this class.
- *
- * @internal
- */
-class HasherHelper {
-
-	/**
-	 * Hash a string.
-	 *
-	 * @param string $key The string to hash.
-	 * @return string The hashed string.
-	 */
-	public static function wp_fast_hash( string $key ): string {
-		if ( function_exists( 'wp_fast_hash' ) ) {
-			return wp_fast_hash( $key );
-		}
-
-		$hashed = sodium_crypto_generichash( $key, 'wp_fast_hash_6.8+', 30 );
-		return '$generic$' . sodium_bin2base64( $hashed, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING );
-	}
-
-	/**
-	 * Verify a string.
-	 *
-	 * @param string $key The string to verify.
-	 * @param string $hash The hash to verify.
-	 * @return bool Whether the string matches the hash.
-	 */
-	public static function wp_verify_fast_hash( string $key, string $hash ): bool {
-		if ( function_exists( 'wp_verify_fast_hash' ) ) {
-			return wp_verify_fast_hash( $key, $hash );
-		}
-
-		if ( ! str_starts_with( $hash, '$generic$' ) ) {
-			return false;
-		}
-
-		return hash_equals( $hash, self::wp_fast_hash( $key ) );
-	}
-}
diff --git a/plugins/woocommerce/templates/emails/customer-stock-notification-verified.php b/plugins/woocommerce/templates/emails/customer-stock-notification-verified.php
index 90daf247fca..278a5a1a831 100644
--- a/plugins/woocommerce/templates/emails/customer-stock-notification-verified.php
+++ b/plugins/woocommerce/templates/emails/customer-stock-notification-verified.php
@@ -12,7 +12,7 @@
  *
  * @see https://woocommerce.com/document/template-structure/
  * @package WooCommerce\Templates\Emails
- * @version 10.2.0
+ * @version 10.8.0
  */

 // Exit if accessed directly.
@@ -55,7 +55,7 @@ do_action( 'woocommerce_email_header', $email_heading, $email );

 	<table id="notification__footer"><tr><td>
 		<?php
-		echo esc_html( sprintf( __( 'You have received this message because your e-mail address was used to sign up for stock notifications on our store.', 'woocommerce' ), $product->get_name() ) );
+		esc_html_e( 'You have received this message because your e-mail address was used to sign up for stock notifications on our store.', 'woocommerce' );

 		if ( ! $is_guest ) {
 			// translators: %1$s placeholder is the unsubscribe link, %2$s placeholder is the Unsubscribe text link.
diff --git a/plugins/woocommerce/tests/legacy/bootstrap.php b/plugins/woocommerce/tests/legacy/bootstrap.php
index 32d2fa0d9cc..50e17c6e5b5 100644
--- a/plugins/woocommerce/tests/legacy/bootstrap.php
+++ b/plugins/woocommerce/tests/legacy/bootstrap.php
@@ -215,8 +215,11 @@ class WC_Unit_Tests_Bootstrap {
 		define( 'WC_TAX_ROUNDING_MODE', 'auto' );
 		define( 'WC_USE_TRANSACTIONS', false );

-		// Enable Back In Stock alpha during tests.
-		define( 'WOOCOMMERCE_BIS_ALPHA_ENABLED', true );
+		// Default Back In Stock alpha to enabled during tests when no
+		// per-suite override has been set.
+		if ( ! defined( 'WOOCOMMERCE_BIS_ALPHA_ENABLED' ) ) {
+			define( 'WOOCOMMERCE_BIS_ALPHA_ENABLED', true );
+		}

 		update_option( 'woocommerce_enable_coupons', 'yes' );
 		update_option( 'woocommerce_calc_taxes', 'yes' );
diff --git a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php
index b7e2329dd73..314788b7540 100644
--- a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php
@@ -8,7 +8,6 @@ use Automattic\WooCommerce\Internal\StockNotifications\Enums\NotificationCancell
 use Automattic\WooCommerce\Internal\StockNotifications\Notification;
 use Automattic\WooCommerce\Internal\StockNotifications\Factory;
 use Automattic\WooCommerce\Internal\StockNotifications\Enums\NotificationStatus;
-use Automattic\WooCommerce\Internal\StockNotifications\Utilities\HasherHelper;
 use WC_Helper_Product;

 /**
@@ -17,41 +16,132 @@ use WC_Helper_Product;
 class EmailActionControllerTests extends \WC_Unit_Test_Case {

 	/**
-	 * Test that verification action is sets notification status to active.
+	 * Persist a notification with a single action-key meta entry.
+	 *
+	 * @param string $status     Initial NotificationStatus value to set on the notification.
+	 * @param string $meta_key   Meta key to store the action key under (e.g. 'verification_action_key').
+	 * @param string $stored_key Already-formatted key value (caller hashes/timestamps as needed).
+	 * @return int Saved notification id.
 	 */
-	public function test_process_verification_action_sets_status_active() {
+	private function arrange_notification( string $status, string $meta_key, string $stored_key ): int {
 		$product      = WC_Helper_Product::create_simple_product();
 		$notification = new Notification();
 		$notification->set_product_id( $product->get_id() );
-		$notification->set_status( NotificationStatus::PENDING );
+		$notification->set_status( $status );
 		$notification->set_user_email( 'test@example.com' );
-		$key = time() . ':' . HasherHelper::wp_fast_hash( 'test' );
-		$notification->update_meta_data( 'email_link_action_key', $key );
-		$id = $notification->save();
+		$notification->update_meta_data( $meta_key, $stored_key );
+		return $notification->save();
+	}

-		$controller = new EmailActionController();
-		$controller->validate_and_maybe_process_request( $id, 'test' );
-		$updated_notification = Factory::get_notification( $id );
-		$this->assertEquals( NotificationStatus::ACTIVE, $updated_notification->get_status() );
+	/**
+	 * Test that verification action is sets notification status to active.
+	 */
+	public function test_process_verification_action_sets_status_active() {
+		$id = $this->arrange_notification(
+			NotificationStatus::PENDING,
+			'verification_action_key',
+			time() . ':' . wp_fast_hash( 'test' )
+		);
+
+		( new EmailActionController() )->validate_and_maybe_process_request( $id, 'test', 'verify' );
+
+		$this->assertEquals( NotificationStatus::ACTIVE, Factory::get_notification( $id )->get_status() );
 	}

 	/**
 	 * Test that unsubscribe action sets notification status to cancelled, and sets cancellation source to user.
 	 */
 	public function test_process_unsubscribe_action_sets_status_cancelled() {
-		$product      = WC_Helper_Product::create_simple_product();
-		$notification = new Notification();
-		$notification->set_product_id( $product->get_id() );
-		$notification->set_status( NotificationStatus::ACTIVE );
-		$notification->set_user_email( 'test@example.com' );
-		$key = HasherHelper::wp_fast_hash( 'test' );
-		$notification->update_meta_data( 'email_link_action_key', $key );
-		$id = $notification->save();
-
-		$controller = new EmailActionController();
-		$controller->validate_and_maybe_process_request( $id, 'test' );
-		$updated_notification = Factory::get_notification( $id );
-		$this->assertEquals( NotificationStatus::CANCELLED, $updated_notification->get_status() );
-		$this->assertEquals( NotificationCancellationSource::USER, $updated_notification->get_cancellation_source() );
+		$id = $this->arrange_notification(
+			NotificationStatus::ACTIVE,
+			'unsubscribe_action_key',
+			wp_fast_hash( 'test' )
+		);
+
+		( new EmailActionController() )->validate_and_maybe_process_request( $id, 'test', 'unsubscribe' );
+
+		$updated = Factory::get_notification( $id );
+		$this->assertEquals( NotificationStatus::CANCELLED, $updated->get_status() );
+		$this->assertEquals( NotificationCancellationSource::USER, $updated->get_cancellation_source() );
+	}
+
+	/**
+	 * A verification request with a key that doesn't match the stored one must
+	 * leave the notification untouched.
+	 */
+	public function test_process_verification_action_with_invalid_key_leaves_status_pending() {
+		$id = $this->arrange_notification(
+			NotificationStatus::PENDING,
+			'verification_action_key',
+			time() . ':' . wp_fast_hash( 'real-key' )
+		);
+
+		( new EmailActionController() )->validate_and_maybe_process_request( $id, 'wrong-key', 'verify' );
+
+		$this->assertEquals( NotificationStatus::PENDING, Factory::get_notification( $id )->get_status() );
+	}
+
+	/**
+	 * An `unsubscribe` action routed against a notification that only has a
+	 * verification key must not cancel the notification.
+	 */
+	public function test_process_unsubscribe_action_with_only_verification_key_does_not_cancel() {
+		// Only a verification key is stored — the unsubscribe_action_key meta
+		// is deliberately empty to simulate a mis-routed link.
+		$id = $this->arrange_notification(
+			NotificationStatus::ACTIVE,
+			'verification_action_key',
+			time() . ':' . wp_fast_hash( 'test' )
+		);
+
+		( new EmailActionController() )->validate_and_maybe_process_request( $id, 'test', 'unsubscribe' );
+
+		$this->assertEquals( NotificationStatus::ACTIVE, Factory::get_notification( $id )->get_status() );
+	}
+
+	/**
+	 * Calling with a zero/missing notification id must early-return without
+	 * error.
+	 */
+	public function test_process_action_with_missing_notification_id_handles_gracefully() {
+		// The guard in validate_and_maybe_process_request short-circuits when
+		// the id is 0; no side-effect to assert, so suppress PHPUnit's risky
+		// warning without a no-op assertion.
+		$this->expectNotToPerformAssertions();
+
+		( new EmailActionController() )->validate_and_maybe_process_request( 0, 'any-key', 'verify' );
+	}
+
+	/**
+	 * Unknown action tokens must no-op rather than running either the verify
+	 * or unsubscribe code paths.
+	 */
+	public function test_process_action_with_unknown_token_does_not_mutate_notification() {
+		$id = $this->arrange_notification(
+			NotificationStatus::PENDING,
+			'verification_action_key',
+			time() . ':' . wp_fast_hash( 'test' )
+		);
+
+		( new EmailActionController() )->validate_and_maybe_process_request( $id, 'test', 'bogus-action' );
+
+		$this->assertEquals( NotificationStatus::PENDING, Factory::get_notification( $id )->get_status() );
+	}
+
+	/**
+	 * An empty \$action is a caller-side bug (missing argument) that must
+	 * short-circuit before the switch; asserted separately from the
+	 * unknown-token branch, which takes the `default:` debug-log path.
+	 */
+	public function test_process_action_with_empty_action_early_returns() {
+		$id = $this->arrange_notification(
+			NotificationStatus::PENDING,
+			'verification_action_key',
+			time() . ':' . wp_fast_hash( 'test' )
+		);
+
+		( new EmailActionController() )->validate_and_maybe_process_request( $id, 'test', '' );
+
+		$this->assertEquals( NotificationStatus::PENDING, Factory::get_notification( $id )->get_status() );
 	}
 }
diff --git a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/NotificationTests.php b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/NotificationTests.php
index 569ab445a6b..865fd720557 100644
--- a/plugins/woocommerce/tests/php/src/Internal/StockNotifications/NotificationTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/StockNotifications/NotificationTests.php
@@ -5,7 +5,6 @@ namespace Automattic\WooCommerce\Tests\Internal\StockNotifications;

 use Automattic\WooCommerce\Internal\StockNotifications\Notification;
 use Automattic\WooCommerce\Internal\StockNotifications\Config;
-use Automattic\WooCommerce\Internal\StockNotifications\Utilities\HasherHelper;

 /**
  * NotificationTests data tests.
@@ -171,7 +170,7 @@ class NotificationTests extends \WC_Unit_Test_Case {

 		// Refetch.
 		$notification = new Notification( $notification->get_id() );
-		$this->assertEmpty( $notification->get_meta( 'email_link_action_key' ) );
+		$this->assertEmpty( $notification->get_meta( 'verification_action_key' ) );
 		$this->assertFalse( $notification->check_verification_key( $key ) );

 		// Re-create.
@@ -180,7 +179,7 @@ class NotificationTests extends \WC_Unit_Test_Case {

 		// Refetch.
 		$notification = new Notification( $notification->get_id() );
-		$this->assertNotEmpty( $notification->get_meta( 'email_link_action_key' ) );
+		$this->assertNotEmpty( $notification->get_meta( 'verification_action_key' ) );
 		$this->assertTrue( $notification->check_verification_key( $key2 ) );
 	}

@@ -198,7 +197,7 @@ class NotificationTests extends \WC_Unit_Test_Case {

 		// Refetch.
 		$notification = new Notification( $notification->get_id() );
-		$this->assertEmpty( $notification->get_meta( 'email_link_action_key' ) );
+		$this->assertEmpty( $notification->get_meta( 'unsubscribe_action_key' ) );
 		$this->assertFalse( $notification->check_unsubscribe_key( $key ) );

 		// Re-create.
@@ -207,7 +206,7 @@ class NotificationTests extends \WC_Unit_Test_Case {

 		// Refetch.
 		$notification = new Notification( $notification->get_id() );
-		$this->assertNotEmpty( $notification->get_meta( 'email_link_action_key' ) );
+		$this->assertNotEmpty( $notification->get_meta( 'unsubscribe_action_key' ) );
 		$this->assertTrue( $notification->check_unsubscribe_key( $key2 ) );
 	}

@@ -221,8 +220,8 @@ class NotificationTests extends \WC_Unit_Test_Case {
 		$notification->save();

 		// Save a custom key.
-		$key = time() - Config::get_verification_expiration_time_threshold() - 1 . ':' . HasherHelper::wp_fast_hash( 'test' );
-		$notification->update_meta_data( 'email_link_action_key', $key );
+		$key = ( time() - Config::get_verification_expiration_time_threshold() - 1 ) . ':' . wp_fast_hash( 'test' );
+		$notification->update_meta_data( 'verification_action_key', $key );
 		$notification->save();

 		$this->assertFalse( $notification->check_verification_key( 'test' ) );