Commit bbd747eed9 for woocommerce

commit bbd747eed92b55bd309788e2aa78cdc928e97a1c
Author: Leonardo Lopes de Albuquerque <leonardo.albuquerque@automattic.com>
Date:   Wed Feb 4 14:30:36 2026 -0300

    [Fraud protection] Replace Dispatcher with SessionDataCollector and Blackbox API (#63070)

    Refactored the WooCommerce Fraud Protection system to replace the real-time event dispatching pattern with a session-based data collection approach, integrating directly with the Blackbox fraud protection API

diff --git a/plugins/woocommerce/src/Internal/FraudProtection/ApiClient.php b/plugins/woocommerce/src/Internal/FraudProtection/ApiClient.php
index c1d0609030..e71574255f 100644
--- a/plugins/woocommerce/src/Internal/FraudProtection/ApiClient.php
+++ b/plugins/woocommerce/src/Internal/FraudProtection/ApiClient.php
@@ -12,10 +12,11 @@ use Automattic\Jetpack\Connection\Client as Jetpack_Connection_Client;
 defined( 'ABSPATH' ) || exit;

 /**
- * Handles communication with the WPCOM fraud protection endpoint.
+ * Handles communication with the Blackbox fraud protection API.
  *
- * Uses Jetpack Connection for authenticated requests to the WPCOM endpoint
- * to get fraud protection decisions (allow, block, or challenge).
+ * Uses Jetpack Connection for authenticated requests to the Blackbox API
+ * to verify sessions and report fraud events. The API returns fraud protection
+ * decisions (allow, block, or challenge).
  *
  * This class implements a fail-open pattern: if the endpoint is unreachable,
  * times out, or returns an error, it returns an "allow" decision to ensure
@@ -28,18 +29,27 @@ class ApiClient {

 	/**
 	 * Default timeout for API requests in seconds.
+	 *
+	 * Using 10 seconds as a reasonable timeout for fraud verification during checkout.
+	 * This balances giving the API enough time to respond while not blocking
+	 * checkout for too long if the service is slow.
+	 */
+	private const DEFAULT_TIMEOUT = 10;
+
+	/**
+	 * Blackbox API base URL.
 	 */
-	private const DEFAULT_TIMEOUT = 30;
+	private const BLACKBOX_API_BASE_URL = 'https://blackbox-api.wp.com/v1';

 	/**
-	 * WPCOM API version.
+	 * Blackbox API verify endpoint path.
 	 */
-	private const WPCOM_API_VERSION = '2';
+	private const VERIFY_ENDPOINT = '/verify';

 	/**
-	 * WPCOM fraud protection events endpoint path within Transact platform.
+	 * Blackbox API report endpoint path.
 	 */
-	private const EVENTS_ENDPOINT = 'transact/fraud_protection/events';
+	private const REPORT_ENDPOINT = '/report';

 	/**
 	 * Decision type: allow session.
@@ -67,38 +77,90 @@ class ApiClient {
 	);

 	/**
-	 * Send a fraud protection event and get a decision from WPCOM endpoint.
+	 * Verify a session with the Blackbox API and get a fraud decision.
 	 *
 	 * Implements fail-open pattern: if the endpoint is unreachable or times out,
 	 * returns "allow" decision and logs the error.
 	 *
 	 * @since 10.5.0
 	 *
-	 * @param string               $event_type Type of event being sent (e.g., 'cart_updated', 'checkout_started').
-	 * @param array<string, mixed> $event_data Event data to send to the endpoint.
+	 * @param string               $session_id Session ID to verify.
+	 * @param array<string, mixed> $payload    Event data to send to the endpoint.
 	 * @return string Decision: "allow" or "block".
 	 */
-	public function send_event( string $event_type, array $event_data ): string {
-		$payload = array_merge(
-			array( 'event_type' => $event_type ),
-			array_filter( $event_data, fn( $value ) => null !== $value )
+	public function verify( string $session_id, array $payload ): string {
+		FraudProtectionController::log(
+			'info',
+			'Verifying session with Blackbox API',
+			array(
+				'session_id' => $session_id,
+				'payload'    => $payload,
+			)
 		);

+		$response = $this->make_request( 'POST', self::VERIFY_ENDPOINT, $session_id, $payload );
+
+		return $this->process_decision_response( $response, $payload );
+	}
+
+	/**
+	 * Report a fraud event to the Blackbox API.
+	 *
+	 * Used for reporting outcomes and feedback to improve fraud detection.
+	 * This is a fire-and-forget operation - errors are logged but do not
+	 * affect the checkout flow.
+	 *
+	 * @since 10.5.0
+	 *
+	 * @param string               $session_id Session ID to report.
+	 * @param array<string, mixed> $payload    Event data to send to the endpoint.
+	 * @return bool True if report was sent successfully, false otherwise.
+	 */
+	public function report( string $session_id, array $payload ): bool {
 		FraudProtectionController::log(
 			'info',
-			sprintf( 'Sending fraud protection event: %s', $event_type ),
+			'Reporting event to Blackbox API',
 			array( 'payload' => $payload )
 		);

-		$response = $this->make_request( 'POST', self::EVENTS_ENDPOINT, $payload );
+		$response = $this->make_request( 'POST', self::REPORT_ENDPOINT, $session_id, $payload );

+		if ( is_wp_error( $response ) ) {
+			FraudProtectionController::log(
+				'error',
+				sprintf(
+					'Failed to report event to Blackbox API: %s',
+					$response->get_error_message()
+				),
+				array( 'error' => $response->get_error_data() )
+			);
+			return false;
+		}
+
+		FraudProtectionController::log(
+			'info',
+			'Event reported successfully',
+			array( 'response' => $response )
+		);
+
+		return true;
+	}
+
+	/**
+	 * Process the API response and extract the decision.
+	 *
+	 * @param array<string, mixed>|\WP_Error $response   API response or WP_Error.
+	 * @param array<string, mixed>           $event_data Event data for logging.
+	 * @return string Decision: "allow" or "block".
+	 */
+	private function process_decision_response( $response, array $event_data ): string {
 		if ( is_wp_error( $response ) ) {
 			$error_data = $response->get_error_data() ?? array();
 			$error_data = is_array( $error_data ) ? $error_data : array( 'error' => $error_data );
 			FraudProtectionController::log(
 				'error',
 				sprintf(
-					'Event track request failed: %s. Failing open with "allow" decision.',
+					'Blackbox API request failed: %s. Failing open with "allow" decision.',
 					$response->get_error_message()
 				),
 				$error_data
@@ -131,6 +193,7 @@ class ApiClient {

 		$session    = is_array( $event_data['session'] ?? null ) ? $event_data['session'] : array();
 		$session_id = $session['session_id'] ?? 'unknown';
+		$event_type = $event_data['event_type'] ?? 'unknown';
 		FraudProtectionController::log(
 			'info',
 			sprintf(
@@ -146,14 +209,18 @@ class ApiClient {
 	}

 	/**
-	 * Make an HTTP request to a WPCOM endpoint via Jetpack Connection.
+	 * Make an HTTP request to the Blackbox API via Jetpack Connection.
+	 *
+	 * Uses Jetpack's signed request mechanism which authenticates with the
+	 * blog token scoped to the blog_id.
 	 *
-	 * @param string               $method  HTTP method (GET, POST, etc.).
-	 * @param string               $path    Endpoint path (relative to sites/{blog_id}/).
-	 * @param array<string, mixed> $payload Request payload.
+	 * @param string               $method     HTTP method (GET, POST, etc.).
+	 * @param string               $path       Endpoint path (relative to Blackbox API base URL).
+	 * @param string               $session_id Session ID for the request.
+	 * @param array<string, mixed> $payload    Request payload.
 	 * @return array<string, mixed>|\WP_Error Parsed JSON response or WP_Error on failure.
 	 */
-	private function make_request( string $method, string $path, array $payload ) {
+	private function make_request( string $method, string $path, string $session_id, array $payload ) {
 		if ( ! class_exists( Jetpack_Connection_Client::class ) ) {
 			return new \WP_Error(
 				'jetpack_not_available',
@@ -169,9 +236,15 @@ class ApiClient {
 			);
 		}

-		$full_path = sprintf( 'sites/%d/%s', $blog_id, $path );
+		$payload['blog_id'] = $blog_id;

-		$body = \wp_json_encode( $payload );
+		$body = \wp_json_encode(
+			array(
+				'session_id'  => $session_id,
+				'private_key' => '', // Woo will not use private keys for now.
+				'extra'       => $payload,
+			)
+		);

 		if ( false === $body ) {
 			return new \WP_Error(
@@ -181,16 +254,19 @@ class ApiClient {
 			);
 		}

-		$response = Jetpack_Connection_Client::wpcom_json_api_request_as_blog(
-			$full_path,
-			self::WPCOM_API_VERSION,
+		$url = self::BLACKBOX_API_BASE_URL . $path;
+
+		// Use Jetpack Connection Client to make a signed request.
+		// This authenticates with the blog token automatically.
+		$response = Jetpack_Connection_Client::remote_request(
 			array(
-				'headers' => array( 'Content-Type' => 'application/json' ),
-				'method'  => $method,
-				'timeout' => self::DEFAULT_TIMEOUT,
+				'url'           => $url,
+				'method'        => $method,
+				'timeout'       => self::DEFAULT_TIMEOUT,
+				'headers'       => array( 'Content-Type' => 'application/json' ),
+				'auth_location' => 'header',
 			),
-			$body,
-			'wpcom'
+			$body
 		);

 		if ( is_wp_error( $response ) ) {
@@ -210,7 +286,7 @@ class ApiClient {
 		if ( $response_code >= 300 ) {
 			return new \WP_Error(
 				'api_error',
-				sprintf( 'Endpoint %s returned status code %d', "$method $path", $response_code ),
+				sprintf( 'Blackbox API %s %s returned status code %d', $method, $path, $response_code ),
 				array( 'response' => JSON_ERROR_NONE === json_last_error() ? $data : $response_body )
 			);
 		}
diff --git a/plugins/woocommerce/src/Internal/FraudProtection/CartEventTracker.php b/plugins/woocommerce/src/Internal/FraudProtection/CartEventTracker.php
index 6b6e214e50..467e824dd6 100644
--- a/plugins/woocommerce/src/Internal/FraudProtection/CartEventTracker.php
+++ b/plugins/woocommerce/src/Internal/FraudProtection/CartEventTracker.php
@@ -13,8 +13,8 @@ defined( 'ABSPATH' ) || exit;
  * Tracks cart events for fraud protection analysis.
  *
  * This class provides methods to track cart events (add, update, remove, restore)
- * for fraud protection event dispatching. Event-specific data is passed
- * to the dispatcher which handles session data collection internally.
+ * for fraud protection. Event-specific data is passed
+ * to the SessionDataCollector which handles session data storage internally.
  *
  * @since 10.5.0
  * @internal This class is part of the internal API and is subject to change without notice.
@@ -22,41 +22,40 @@ defined( 'ABSPATH' ) || exit;
 class CartEventTracker {

 	/**
-	 * Fraud protection dispatcher instance.
+	 * Session data collector instance.
 	 *
-	 * @var FraudProtectionDispatcher
+	 * @var SessionDataCollector
 	 */
-	private FraudProtectionDispatcher $dispatcher;
+	private SessionDataCollector $session_data_collector;

 	/**
 	 * Initialize with dependencies.
 	 *
 	 * @internal
 	 *
-	 * @param FraudProtectionDispatcher $dispatcher The fraud protection dispatcher instance.
+	 * @param SessionDataCollector $session_data_collector The session data collector instance.
 	 */
-	final public function init( FraudProtectionDispatcher $dispatcher ): void {
-		$this->dispatcher = $dispatcher;
+	final public function init( SessionDataCollector $session_data_collector ): void {
+		$this->session_data_collector = $session_data_collector;
 	}

 	/**
 	 * Track cart page loaded event.
 	 *
-	 * Triggers fraud protection event dispatching when the cart page is initially loaded.
+	 * Collects session data when the cart page is initially loaded.
 	 * This captures the initial session state before any user interactions.
 	 *
 	 * @internal
 	 * @return void
 	 */
 	public function track_cart_page_loaded(): void {
-		// Track the page load event. Session data will be collected by the dispatcher.
-		$this->dispatcher->dispatch_event( 'cart_page_loaded', array() );
+		$this->session_data_collector->collect( 'cart_page_loaded', array() );
 	}

 	/**
 	 * Track cart item added event.
 	 *
-	 * Triggers fraud protection event dispatching when an item is added to the cart.
+	 * Collects session data when an item is added to the cart.
 	 *
 	 * @internal
 	 *
@@ -74,14 +73,13 @@ class CartEventTracker {
 			$variation_id
 		);

-		// Trigger event dispatching.
-		$this->dispatcher->dispatch_event( 'cart_item_added', $event_data );
+		$this->session_data_collector->collect( 'cart_item_added', $event_data );
 	}

 	/**
 	 * Track cart item quantity updated event.
 	 *
-	 * Triggers fraud protection event dispatching when cart item quantity is updated.
+	 * Collects session data when cart item quantity is updated.
 	 *
 	 * @internal
 	 *
@@ -108,17 +106,15 @@ class CartEventTracker {
 			$variation_id
 		);

-		// Add old quantity for context.
 		$event_data['old_quantity'] = (int) $old_quantity;

-		// Trigger event dispatching.
-		$this->dispatcher->dispatch_event( 'cart_item_updated', $event_data );
+		$this->session_data_collector->collect( 'cart_item_updated', $event_data );
 	}

 	/**
 	 * Track cart item removed event.
 	 *
-	 * Triggers fraud protection event dispatching when an item is removed from the cart.
+	 * Collects session data when an item is removed from the cart.
 	 *
 	 * @internal
 	 *
@@ -144,14 +140,13 @@ class CartEventTracker {
 			$variation_id
 		);

-		// Trigger event dispatching.
-		$this->dispatcher->dispatch_event( 'cart_item_removed', $event_data );
+		$this->session_data_collector->collect( 'cart_item_removed', $event_data );
 	}

 	/**
 	 * Track cart item restored event.
 	 *
-	 * Triggers fraud protection event dispatching when a removed item is restored to the cart.
+	 * Collects session data when a removed item is restored to the cart.
 	 *
 	 * @internal
 	 *
@@ -177,8 +172,7 @@ class CartEventTracker {
 			$variation_id
 		);

-		// Trigger event dispatching.
-		$this->dispatcher->dispatch_event( 'cart_item_restored', $event_data );
+		$this->session_data_collector->collect( 'cart_item_restored', $event_data );
 	}

 	/**
diff --git a/plugins/woocommerce/src/Internal/FraudProtection/CheckoutEventTracker.php b/plugins/woocommerce/src/Internal/FraudProtection/CheckoutEventTracker.php
index 317a76f528..b2ff257508 100644
--- a/plugins/woocommerce/src/Internal/FraudProtection/CheckoutEventTracker.php
+++ b/plugins/woocommerce/src/Internal/FraudProtection/CheckoutEventTracker.php
@@ -13,21 +13,13 @@ defined( 'ABSPATH' ) || exit;
  * Tracks checkout events for fraud protection analysis.
  *
  * This class provides methods to track both WooCommerce Blocks (Store API) and traditional
- * shortcode checkout events for fraud protection event dispatching.
- * Event-specific data is passed to the dispatcher which handles session data collection internally.
+ * shortcode checkout events for fraud protection. Event-specific data is passed to the
+ * SessionDataCollector which handles session data storage internally.
  *
  * @since 10.5.0
  * @internal This class is part of the internal API and is subject to change without notice.
  */
 class CheckoutEventTracker {
-
-	/**
-	 * Fraud protection dispatcher instance.
-	 *
-	 * @var FraudProtectionDispatcher
-	 */
-	private FraudProtectionDispatcher $dispatcher;
-
 	/**
 	 * Session data collector instance.
 	 *
@@ -40,26 +32,23 @@ class CheckoutEventTracker {
 	 *
 	 * @internal
 	 *
-	 * @param FraudProtectionDispatcher $dispatcher                The fraud protection dispatcher instance.
-	 * @param SessionDataCollector      $session_data_collector    The session data collector instance.
+	 * @param SessionDataCollector $session_data_collector    The session data collector instance.
 	 */
-	final public function init( FraudProtectionDispatcher $dispatcher, SessionDataCollector $session_data_collector ): void {
-		$this->dispatcher             = $dispatcher;
+	final public function init( SessionDataCollector $session_data_collector ): void {
 		$this->session_data_collector = $session_data_collector;
 	}

 	/**
 	 * Track checkout page loaded event.
 	 *
-	 * Triggers fraud protection event dispatching when the checkout page is initially loaded.
+	 * Collects session data when the checkout page is initially loaded.
 	 * This captures the initial session state before any user interactions.
 	 *
 	 * @internal
 	 * @return void
 	 */
 	public function track_checkout_page_loaded(): void {
-		// Track the page load event. Session data will be collected by the dispatcher.
-		$this->dispatcher->dispatch_event( 'checkout_page_loaded', array() );
+		$this->session_data_collector->collect( 'checkout_page_loaded', array() );
 	}

 	/**
@@ -73,7 +62,7 @@ class CheckoutEventTracker {
 	 */
 	public function track_blocks_checkout_update(): void {
 		// At this point we don't have any payment or shipping data, so we pass an empty array.
-		$this->dispatcher->dispatch_event( 'checkout_update', array() );
+		$this->session_data_collector->collect( 'checkout_update', array() );
 	}

 	/**
@@ -120,7 +109,7 @@ class CheckoutEventTracker {
 		// Only dispatch if either country changed.
 		if ( $billing_changed || $shipping_changed ) {
 			$event_data = $this->format_checkout_event_data( 'field_update', $data );
-			$this->dispatcher->dispatch_event( 'checkout_update', $event_data );
+			$this->session_data_collector->collect( 'checkout_update', $event_data );
 		}
 	}

@@ -271,6 +260,6 @@ class CheckoutEventTracker {
 			'status'         => $order->get_status(),
 		);

-		$this->dispatcher->dispatch_event( 'order_placed', $event_data );
+		$this->session_data_collector->collect( 'order_placed', $event_data );
 	}
 }
diff --git a/plugins/woocommerce/src/Internal/FraudProtection/FraudProtectionDispatcher.php b/plugins/woocommerce/src/Internal/FraudProtection/FraudProtectionDispatcher.php
deleted file mode 100644
index 73637a2b2a..0000000000
--- a/plugins/woocommerce/src/Internal/FraudProtection/FraudProtectionDispatcher.php
+++ /dev/null
@@ -1,147 +0,0 @@
-<?php
-/**
- * FraudProtectionDispatcher class file.
- */
-
-declare( strict_types=1 );
-
-namespace Automattic\WooCommerce\Internal\FraudProtection;
-
-defined( 'ABSPATH' ) || exit;
-
-/**
- * Centralized fraud protection event dispatcher.
- *
- * This class provides a unified interface for dispatching fraud protection events.
- * It coordinates data collection and transmission for fraud protection events by
- * orchestrating ApiClient and DecisionHandler components.
- *
- * @since 10.5.0
- * @internal This class is part of the internal API and is subject to change without notice.
- */
-class FraudProtectionDispatcher {
-
-	/**
-	 * API client instance.
-	 *
-	 * @var ApiClient
-	 */
-	private ApiClient $api_client;
-
-	/**
-	 * Decision handler instance.
-	 *
-	 * @var DecisionHandler
-	 */
-	private DecisionHandler $decision_handler;
-
-	/**
-	 * Fraud protection controller instance.
-	 *
-	 * @var FraudProtectionController
-	 */
-	private FraudProtectionController $fraud_protection_controller;
-
-	/**
-	 * Session data collector instance.
-	 *
-	 * @var SessionDataCollector
-	 */
-	private SessionDataCollector $data_collector;
-
-	/**
-	 * Initialize with dependencies.
-	 *
-	 * @internal
-	 *
-	 * @param ApiClient                 $api_client                  The API client instance.
-	 * @param DecisionHandler           $decision_handler            The decision handler instance.
-	 * @param FraudProtectionController $fraud_protection_controller The fraud protection controller instance.
-	 * @param SessionDataCollector      $data_collector              The session data collector instance.
-	 */
-	final public function init(
-		ApiClient $api_client,
-		DecisionHandler $decision_handler,
-		FraudProtectionController $fraud_protection_controller,
-		SessionDataCollector $data_collector
-	): void {
-		$this->api_client                  = $api_client;
-		$this->decision_handler            = $decision_handler;
-		$this->fraud_protection_controller = $fraud_protection_controller;
-		$this->data_collector              = $data_collector;
-	}
-
-	/**
-	 * Dispatch fraud protection event.
-	 *
-	 * This method collects session data and dispatches it to the fraud protection service.
-	 * It orchestrates the following flow:
-	 * 1. Check if feature is enabled (fail-open if not)
-	 * 2. Collect comprehensive session data via SessionDataCollector
-	 * 3. Apply extension data filter to allow custom data
-	 * 4. Send event to API and get decision
-	 * 5. Apply decision via DecisionHandler
-	 *
-	 * The method implements graceful degradation - any errors during tracking
-	 * will be logged but will not break the functionality.
-	 *
-	 * @param string $event_type Event type identifier (e.g., 'cart_item_added').
-	 * @param array  $event_data Optional event-specific data to include with session data.
-	 * @return void
-	 */
-	public function dispatch_event( string $event_type, array $event_data = array() ): void {
-		try {
-			// Check if feature is enabled - fail-open if not.
-			if ( ! $this->fraud_protection_controller->feature_is_enabled() ) {
-				FraudProtectionController::log(
-					'debug',
-					sprintf(
-						'Fraud protection event not dispatched (feature disabled): %s',
-						$event_type
-					),
-					array( 'event_type' => $event_type )
-				);
-				return;
-			}
-
-			// Collect comprehensive session data.
-			$collected_data = $this->data_collector->collect( $event_type, $event_data );
-
-			/**
-			 * Filters the fraud protection event data before sending to the API.
-			 *
-			 * This filter allows extensions to modify or add custom data to fraud protection
-			 * events. Common use cases include:
-			 * - Adding custom payment gateway data
-			 * - Adding subscription-specific context
-			 * - Adding custom risk signals
-			 *
-			 * @since 10.5.0
-			 *
-			 * @param array  $collected_data Fully-collected event data including session context.
-			 * @param string $event_type     Event type identifier (e.g., 'cart_item_added').
-			 */
-			$collected_data = apply_filters( 'woocommerce_fraud_protection_event_data', $collected_data, $event_type );
-
-			// Send event to API and get decision.
-			$decision = $this->api_client->send_event( $event_type, $collected_data );
-
-			// Apply decision via DecisionHandler.
-			$this->decision_handler->apply_decision( $decision, $collected_data );
-		} catch ( \Exception $e ) {
-			// Gracefully handle errors - fraud protection should never break functionality.
-			FraudProtectionController::log(
-				'error',
-				sprintf(
-					'Failed to dispatch fraud protection event: %s | Error: %s',
-					$event_type,
-					$e->getMessage()
-				),
-				array(
-					'event_type' => $event_type,
-					'exception'  => $e,
-				)
-			);
-		}
-	}
-}
diff --git a/plugins/woocommerce/src/Internal/FraudProtection/PaymentMethodEventTracker.php b/plugins/woocommerce/src/Internal/FraudProtection/PaymentMethodEventTracker.php
index 3e7530a11f..f3aa41548a 100644
--- a/plugins/woocommerce/src/Internal/FraudProtection/PaymentMethodEventTracker.php
+++ b/plugins/woocommerce/src/Internal/FraudProtection/PaymentMethodEventTracker.php
@@ -13,8 +13,8 @@ defined( 'ABSPATH' ) || exit;
  * Tracks payment method events for fraud protection analysis.
  *
  * This class provides methods to track events for adding payment methods in My Account page
- * for fraud protection.
- * Event-specific data is passed to the dispatcher which handles session data collection internally.
+ * for fraud protection. Event-specific data is passed to the SessionDataCollector which
+ * handles session data storage internally.
  *
  * @since 10.5.0
  * @internal This class is part of the internal API and is subject to change without notice.
@@ -22,41 +22,40 @@ defined( 'ABSPATH' ) || exit;
 class PaymentMethodEventTracker {

 	/**
-	 * Fraud protection dispatcher instance.
+	 * Session data collector instance.
 	 *
-	 * @var FraudProtectionDispatcher
+	 * @var SessionDataCollector
 	 */
-	private FraudProtectionDispatcher $dispatcher;
+	private SessionDataCollector $session_data_collector;

 	/**
 	 * Initialize with dependencies.
 	 *
 	 * @internal
 	 *
-	 * @param FraudProtectionDispatcher $dispatcher The fraud protection dispatcher instance.
+	 * @param SessionDataCollector $session_data_collector The session data collector instance.
 	 */
-	final public function init( FraudProtectionDispatcher $dispatcher ): void {
-		$this->dispatcher = $dispatcher;
+	final public function init( SessionDataCollector $session_data_collector ): void {
+		$this->session_data_collector = $session_data_collector;
 	}

 	/**
 	 * Track add payment method page loaded event.
 	 *
-	 * Triggers fraud protection event dispatching when the add payment method page is initially loaded.
+	 * Collects session data when the add payment method page is initially loaded.
 	 * This captures the initial session state before any user interactions.
 	 *
 	 * @internal
 	 * @return void
 	 */
 	public function track_add_payment_method_page_loaded(): void {
-		// Track the page load event. Session data will be collected by the dispatcher.
-		$this->dispatcher->dispatch_event( 'add_payment_method_page_loaded', array() );
+		$this->session_data_collector->collect( 'add_payment_method_page_loaded', array() );
 	}

 	/**
 	 * Track payment method added event.
 	 *
-	 * Triggers fraud protection event tracking when a payment method is added.
+	 * Collects session data when a payment method is added.
 	 *
 	 * @internal
 	 *
@@ -66,8 +65,7 @@ class PaymentMethodEventTracker {
 	public function track_payment_method_added( $token_id, $token ): void {
 		$event_data = $this->build_payment_method_event_data( 'added', $token );

-		// Trigger event dispatching.
-		$this->dispatcher->dispatch_event( 'payment_method_added', $event_data );
+		$this->session_data_collector->collect( 'payment_method_added', $event_data );
 	}

 	/**
@@ -75,7 +73,7 @@ class PaymentMethodEventTracker {
 	 *
 	 * Extracts relevant information from the payment token object including
 	 * token type, gateway ID, user ID, and card details for card tokens.
-	 * This data will be merged with comprehensive session data during event tracking.
+	 * This data will be merged with session data during collection.
 	 *
 	 * @param string            $action Action type (added, updated, set_default, deleted, add_failed).
 	 * @param \WC_Payment_Token $token  The payment token object.
diff --git a/plugins/woocommerce/src/Internal/FraudProtection/SessionDataCollector.php b/plugins/woocommerce/src/Internal/FraudProtection/SessionDataCollector.php
index 6cf284540f..a1f1975ef3 100644
--- a/plugins/woocommerce/src/Internal/FraudProtection/SessionDataCollector.php
+++ b/plugins/woocommerce/src/Internal/FraudProtection/SessionDataCollector.php
@@ -51,28 +51,79 @@ class SessionDataCollector {
 	 * @since 10.5.0
 	 *
 	 * @param string|null $event_type Optional event type identifier (e.g., 'checkout_started', 'payment_attempt').
-	 * @param array       $event_data Optional event-specific additional context data (may include 'order_id').
-	 * @return array Nested array containing all collected fraud protection data.
+	 * @param array       $event_data Optional event-specific additional context data.
 	 */
-	public function collect( ?string $event_type = null, array $event_data = array() ): array {
+	public function collect( ?string $event_type = null, array $event_data = array() ): void {
 		// Ensure cart and session are loaded.
 		$this->session_clearance_manager->ensure_cart_loaded();

-		// Extract order ID from event_data if provided.
-		// There seem to be no universal way to get order id from session data, so we may start with passing it as a parameter when calling this method.
-		$order_id_from_event = $event_data['order_id'] ?? null;
+		$data = array(
+			'event_type' => $event_type,
+			'timestamp'  => gmdate( 'Y-m-d H:i:s' ),
+			'event_data' => $event_data,
+		);
+
+		// Save the collected data in the session for fraud analysis tracking, preserving multiple calls.
+		if ( WC()->session instanceof \WC_Session ) {
+			// Retrieve existing data array or initialize if not present.
+			$collected_data = WC()->session->get( 'fraud_protection_collected_data' );
+			if ( ! is_array( $collected_data ) ) {
+				$collected_data = array();
+			}
+			$collected_data[] = $data;
+			$collected_data   = $this->trim_to_max_size( $collected_data );
+			WC()->session->set( 'fraud_protection_collected_data', $collected_data );
+		} else {
+			FraudProtectionController::log(
+				'error',
+				'Attempted to save fraud protection data, but no valid WooCommerce session exists.',
+				array(
+					'context'    => 'SessionDataCollector::collect',
+					'event_type' => $event_type,
+					'event_data' => $event_data,
+				)
+			);
+		}
+	}

-		return array(
-			'event_type'       => $event_type,
-			'timestamp'        => gmdate( 'Y-m-d H:i:s' ),
+	/**
+	 * Get all collected fraud protection data from the session.
+	 *
+	 * Retrieves the array of collected event data stored during this session.
+	 * Returns an empty array if no data has been collected or session is unavailable.
+	 *
+	 * @since 10.5.0
+	 *
+	 * @param int|null $order_id Optional order ID to include order data in the response.
+	 * @return array Array of collected fraud protection event data.
+	 */
+	public function get_collected_data( ?int $order_id = null ): array {
+		$data = array(
 			'wc_version'       => WC()->version,
 			'session'          => $this->get_session_data(),
 			'customer'         => $this->get_customer_data(),
-			'order'            => $this->get_order_data( $order_id_from_event ),
+			'order'            => array(),
 			'shipping_address' => $this->get_shipping_address(),
 			'billing_address'  => $this->get_billing_address(),
-			'event_data'       => $event_data,
+			'collected_events' => array(),
 		);
+
+		if ( $order_id ) {
+			$data['order'] = $this->get_order_data( $order_id );
+		}
+
+		// Calculate base data size to ensure total response stays under limit.
+		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- Used for size calculation only.
+		$base_size = strlen( serialize( $data ) );
+
+		if ( WC()->session instanceof \WC_Session ) {
+			$collected_data = WC()->session->get( 'fraud_protection_collected_data' );
+			if ( is_array( $collected_data ) ) {
+				$data['collected_events'] = $this->trim_to_max_size( $collected_data, $base_size );
+			}
+		}
+
+		return $data;
 	}

 	/**
@@ -604,4 +655,32 @@ class SessionDataCollector {
 		);
 		return implode( ', ', $category_names );
 	}
+
+	/**
+	 * Trim collected data array to ensure it stays within 1 MB size limit.
+	 *
+	 * Removes oldest entries from the array until the serialized size is under the limit.
+	 * Always keeps at least one entry (the most recent).
+	 *
+	 * @since 10.5.0
+	 *
+	 * @param array $data      Array of collected event data.
+	 * @param int   $base_size Size in bytes of additional data that will be combined with this array.
+	 * @return array Trimmed array that fits within the size limit.
+	 */
+	private function trim_to_max_size( array $data, int $base_size = 0 ): array {
+		$max_size_bytes = 1 * 1024 * 1024 - $base_size; // 1 MB minus base data size.
+		$data_count     = count( $data );
+		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- Used for size calculation only.
+		$data_size = strlen( serialize( $data ) );
+
+		while ( $data_count > 1 && $data_size > $max_size_bytes ) {
+			array_shift( $data );
+			$data_count = count( $data );
+			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- Used for size calculation only.
+			$data_size = strlen( serialize( $data ) );
+		}
+
+		return $data;
+	}
 }
diff --git a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/ApiClientTest.php b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/ApiClientTest.php
index abbbcf51cd..f926f03021 100644
--- a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/ApiClientTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/ApiClientTest.php
@@ -14,6 +14,10 @@ use WP_Error;

 /**
  * Tests for the ApiClient class.
+ *
+ * Tests the Blackbox API client which provides:
+ * - verify(): Verify a session and get a fraud decision (allow/block)
+ * - report(): Report fraud events for feedback
  */
 class ApiClientTest extends WC_Unit_Test_Case {

@@ -24,7 +28,7 @@ class ApiClientTest extends WC_Unit_Test_Case {
 	 *
 	 * @var ApiClient
 	 */
-	private $sut;
+	private ApiClient $sut;

 	/**
 	 * Set up test fixtures.
@@ -48,436 +52,301 @@ class ApiClientTest extends WC_Unit_Test_Case {
 		parent::tearDown();
 	}

+	/*
+	|--------------------------------------------------------------------------
+	| verify() Tests
+	|--------------------------------------------------------------------------
+	*/
+
 	/**
-	 * @testdox Send Event should return allow when Jetpack blog ID is not found.
+	 * Test verify calls correct endpoint with payload.
+	 *
+	 * @testdox verify() calls Blackbox API /verify endpoint with the correct payload
 	 */
-	public function test_send_event_returns_allow_when_blog_id_not_found(): void {
-		update_option( 'jetpack_options', array( 'id' => null ) );
+	public function test_verify_calls_verify_endpoint(): void {
+		$captured_url  = null;
+		$captured_body = null;
+
+		add_filter(
+			'pre_http_request',
+			function ( $preempt, $args, $url ) use ( &$captured_url, &$captured_body ) {
+				unset( $preempt );
+				$captured_body = json_decode( $args['body'], true );
+				$captured_url  = $url;
+				return array(
+					'response' => array( 'code' => 200 ),
+					'body'     => wp_json_encode( array( 'decision' => 'allow' ) ),
+				);
+			},
+			10,
+			3
+		);

-		$result = $this->sut->send_event( 'cart_updated', array( 'session_id' => 'test-session' ) );
+		$this->sut->verify( 'test-session-id', array( 'event_type' => 'checkout_started' ) );

-		$this->assertSame( ApiClient::DECISION_ALLOW, $result, 'Should fail open with allow decision' );
-		$this->assertLogged( 'error', 'Jetpack blog ID not found', array( 'source' => 'woo-fraud-protection' ) );
+		$this->assertStringContainsString( 'blackbox-api.wp.com/v1/verify', $captured_url );
+		$this->assertSame( 'test-session-id', $captured_body['session_id'] );
+		$this->assertArrayHasKey( 'extra', $captured_body );
+		$this->assertArrayHasKey( 'blog_id', $captured_body['extra'] );
+		$this->assertSame( 12345, $captured_body['extra']['blog_id'] );
 	}

 	/**
-	 * @testdox Send Event should return allow when HTTP request fails.
+	 * Test verify returns allow decision.
+	 *
+	 * @testdox verify() returns allow decision from API
 	 */
-	public function test_send_event_returns_allow_when_http_request_fails(): void {
+	public function test_verify_returns_allow_decision(): void {
 		add_filter(
 			'pre_http_request',
-			function () {
-				return new WP_Error( 'http_error', 'Connection failed', 'error_data' );
-			}
+			fn() => array(
+				'response' => array( 'code' => 200 ),
+				'body'     => wp_json_encode( array( 'decision' => 'allow' ) ),
+			)
 		);

-		$result = $this->sut->send_event( 'cart_updated', array( 'session_id' => 'test-session' ) );
+		$result = $this->sut->verify( 'test-session-id', array( 'event_type' => 'checkout_started' ) );

-		$this->assertSame( ApiClient::DECISION_ALLOW, $result, 'Should fail open with allow decision' );
-		$this->assertLogged(
-			'error',
-			'Connection failed',
-			array(
-				'source' => 'woo-fraud-protection',
-				'error'  => 'error_data',
-			)
-		);
+		$this->assertSame( ApiClient::DECISION_ALLOW, $result );
 	}

 	/**
-	 * @testdox Send Event should return allow when API returns HTTP error status.
+	 * Test verify returns block decision.
+	 *
+	 * @testdox verify() returns block decision from API
 	 */
-	public function test_send_event_returns_allow_when_api_returns_http_error(): void {
+	public function test_verify_returns_block_decision(): void {
 		add_filter(
 			'pre_http_request',
-			function () {
-				return array(
-					'response' => array( 'code' => 500 ),
-					'body'     => 'Internal Server Error',
-				);
-			}
+			fn() => array(
+				'response' => array( 'code' => 200 ),
+				'body'     => wp_json_encode( array( 'decision' => 'block' ) ),
+			)
 		);

-		$result = $this->sut->send_event( 'cart_updated', array( 'session_id' => 'test-session' ) );
+		$result = $this->sut->verify( 'test-session-id', array( 'event_type' => 'checkout_started' ) );

-		$this->assertSame( ApiClient::DECISION_ALLOW, $result, 'Should fail open with allow decision' );
-		$this->assertLogged(
-			'error',
-			'Endpoint POST transact/fraud_protection/events returned status code 500',
-			array(
-				'source'   => 'woo-fraud-protection',
-				'response' => 'Internal Server Error',
-			)
-		);
+		$this->assertSame( ApiClient::DECISION_BLOCK, $result );
 	}

 	/**
-	 * @testdox Send Event should return allow when API returns HTTP error with JSON body.
+	 * Test verify fails open when blog_id not found.
+	 *
+	 * @testdox verify() fails open with allow when blog_id not found
 	 */
-	public function test_send_event_returns_allow_when_api_returns_http_error_with_json_body(): void {
-		$response = array(
-			'error'   => 'invalid_request',
-			'message' => 'Missing required field',
-		);
-
-		add_filter(
-			'pre_http_request',
-			function () use ( $response ) {
-				return array(
-					'response' => array( 'code' => 400 ),
-					'body'     => wp_json_encode( $response ),
-				);
-			}
-		);
+	public function test_verify_fails_open_when_blog_id_not_found(): void {
+		update_option( 'jetpack_options', array( 'id' => null ) );

-		$result = $this->sut->send_event( 'cart_updated', array( 'session_id' => 'test-session' ) );
+		$result = $this->sut->verify( 'test-session-id', array( 'event_type' => 'checkout_started' ) );

-		$this->assertSame( ApiClient::DECISION_ALLOW, $result, 'Should fail open with allow decision' );
-		$this->assertLogged(
-			'error',
-			'Endpoint POST transact/fraud_protection/events returned status code 400',
-			array(
-				'source'   => 'woo-fraud-protection',
-				'response' => $response,
-			)
-		);
+		$this->assertSame( ApiClient::DECISION_ALLOW, $result );
+		$this->assertLogged( 'error', 'Jetpack blog ID not found' );
 	}

 	/**
-	 * @testdox Send Event should return allow when API returns invalid JSON.
+	 * Test verify fails open on HTTP error.
+	 *
+	 * @testdox verify() fails open with allow when HTTP request fails
 	 */
-	public function test_send_event_returns_allow_when_api_returns_invalid_json(): void {
+	public function test_verify_fails_open_on_http_error(): void {
 		add_filter(
 			'pre_http_request',
-			function () {
-				return array(
-					'response' => array( 'code' => 200 ),
-					'body'     => 'not valid json',
-				);
-			}
+			fn() => new WP_Error( 'http_error', 'Connection timeout' )
 		);

-		$result = $this->sut->send_event( 'cart_updated', array( 'session_id' => 'test-session' ) );
+		$result = $this->sut->verify( 'test-session-id', array( 'event_type' => 'checkout_started' ) );

-		$this->assertSame( ApiClient::DECISION_ALLOW, $result, 'Should fail open with allow decision' );
-		$this->assertLogged(
-			'error',
-			'Failed to decode JSON response: Syntax error',
-			array(
-				'source'   => 'woo-fraud-protection',
-				'response' => 'not valid json',
-			)
-		);
+		$this->assertSame( ApiClient::DECISION_ALLOW, $result );
+		$this->assertLogged( 'error', 'Connection timeout' );
 	}

 	/**
-	 * @testdox Send Event should return allow when API response is missing decision field.
+	 * Test verify fails open on server error.
+	 *
+	 * @testdox verify() fails open with allow when API returns 5xx error
 	 */
-	public function test_send_event_returns_allow_when_response_missing_decision_field(): void {
+	public function test_verify_fails_open_on_server_error(): void {
 		add_filter(
 			'pre_http_request',
-			function () {
-				return array(
-					'response' => array( 'code' => 200 ),
-					'body'     => wp_json_encode( array( 'fraud_event_id' => 123 ) ),
-				);
-			}
+			fn() => array(
+				'response' => array( 'code' => 500 ),
+				'body'     => 'Internal Server Error',
+			)
 		);

-		$result = $this->sut->send_event( 'cart_updated', array( 'session_id' => 'test-session' ) );
+		$result = $this->sut->verify( 'test-session-id', array( 'event_type' => 'checkout_started' ) );

-		$this->assertSame( ApiClient::DECISION_ALLOW, $result, 'Should fail open with allow decision' );
-		$this->assertLogged(
-			'error',
-			'missing "decision" field',
-			array(
-				'source'   => 'woo-fraud-protection',
-				'response' => array( 'fraud_event_id' => 123 ),
-			)
-		);
+		$this->assertSame( ApiClient::DECISION_ALLOW, $result );
+		$this->assertLogged( 'error', 'status code 500' );
 	}

 	/**
-	 * @testdox Send Event should return allow when API returns invalid decision value.
+	 * Test verify fails open on invalid JSON.
+	 *
+	 * @testdox verify() fails open with allow when API returns invalid JSON
 	 */
-	public function test_send_event_returns_allow_when_invalid_decision_value(): void {
+	public function test_verify_fails_open_on_invalid_json(): void {
 		add_filter(
 			'pre_http_request',
-			function () {
-				return array(
-					'response' => array( 'code' => 200 ),
-					'body'     => wp_json_encode( array( 'decision' => 'invalid_decision' ) ),
-				);
-			}
+			fn() => array(
+				'response' => array( 'code' => 200 ),
+				'body'     => 'not valid json',
+			)
 		);

-		$result = $this->sut->send_event( 'cart_updated', array( 'session_id' => 'test-session' ) );
+		$result = $this->sut->verify( 'test-session-id', array( 'event_type' => 'checkout_started' ) );

-		$this->assertSame( ApiClient::DECISION_ALLOW, $result, 'Should fail open with allow decision' );
-		$this->assertLogged(
-			'error',
-			'Invalid decision value "invalid_decision"',
-			array(
-				'source'   => 'woo-fraud-protection',
-				'response' => array( 'decision' => 'invalid_decision' ),
-			)
-		);
+		$this->assertSame( ApiClient::DECISION_ALLOW, $result );
+		$this->assertLogged( 'error', 'Failed to decode JSON' );
 	}

 	/**
-	 * @testdox Send Event should return allow decision from API.
+	 * Test verify fails open when decision field missing.
+	 *
+	 * @testdox verify() fails open with allow when response missing decision field
 	 */
-	public function test_send_event_returns_allow_decision_from_api(): void {
+	public function test_verify_fails_open_when_missing_decision(): void {
 		add_filter(
 			'pre_http_request',
-			function () {
-				return array(
-					'response' => array( 'code' => 200 ),
-					'body'     => wp_json_encode(
-						array(
-							'fraud_event_id' => 123,
-							'decision'       => 'allow',
-							'risk_score'     => 10,
-						)
-					),
-				);
-			}
-		);
-
-		$result = $this->sut->send_event( 'cart_updated', array( 'session_id' => 'test-session' ) );
-
-		$this->assertSame( ApiClient::DECISION_ALLOW, $result, 'Should return allow decision' );
-		$this->assertLogged(
-			'info',
-			'Fraud decision received: allow',
-			array(
-				'source'   => 'woo-fraud-protection',
-				'response' => array(
-					'fraud_event_id' => 123,
-					'decision'       => 'allow',
-					'risk_score'     => 10,
-				),
+			fn() => array(
+				'response' => array( 'code' => 200 ),
+				'body'     => wp_json_encode( array( 'risk_score' => 50 ) ),
 			)
 		);
-		$this->assertNoErrorLogged();
+
+		$result = $this->sut->verify( 'test-session-id', array( 'event_type' => 'checkout_started' ) );
+
+		$this->assertSame( ApiClient::DECISION_ALLOW, $result );
+		$this->assertLogged( 'error', 'missing "decision" field' );
 	}

 	/**
-	 * @testdox Send Event should return block decision from API.
+	 * Test verify fails open on invalid decision value.
+	 *
+	 * @testdox verify() fails open with allow when decision value is invalid
 	 */
-	public function test_send_event_returns_block_decision_from_api(): void {
+	public function test_verify_fails_open_on_invalid_decision(): void {
 		add_filter(
 			'pre_http_request',
-			function () {
-				return array(
-					'response' => array( 'code' => 200 ),
-					'body'     => wp_json_encode(
-						array(
-							'fraud_event_id' => 123,
-							'decision'       => 'block',
-							'risk_score'     => 95,
-							'reason_tags'    => array( 'failures_per_ip' ),
-						)
-					),
-				);
-			}
-		);
-
-		$result = $this->sut->send_event( 'checkout_started', array( 'session_id' => 'test-session' ) );
-
-		$this->assertSame( ApiClient::DECISION_BLOCK, $result, 'Should return block decision' );
-		$this->assertLogged(
-			'info',
-			'Fraud decision received: block',
-			array(
-				'source'   => 'woo-fraud-protection',
-				'response' => array(
-					'fraud_event_id' => 123,
-					'decision'       => 'block',
-					'risk_score'     => 95,
-					'reason_tags'    => array( 'failures_per_ip' ),
-				),
+			fn() => array(
+				'response' => array( 'code' => 200 ),
+				'body'     => wp_json_encode( array( 'decision' => 'unknown_value' ) ),
 			)
 		);
-		$this->assertNoErrorLogged();
+
+		$result = $this->sut->verify( 'test-session-id', array( 'event_type' => 'checkout_started' ) );
+
+		$this->assertSame( ApiClient::DECISION_ALLOW, $result );
+		$this->assertLogged( 'error', 'Invalid decision value' );
 	}

+	/*
+	|--------------------------------------------------------------------------
+	| report() Tests
+	|--------------------------------------------------------------------------
+	*/
+
 	/**
-	 * @testdox Send Event should return allow when API returns challenge decision (challenge flow not yet implemented).
+	 * Test report calls correct endpoint.
+	 *
+	 * @testdox report() calls Blackbox API /report endpoint
 	 */
-	public function test_send_event_returns_allow_when_challenge_decision_from_api(): void {
+	public function test_report_calls_report_endpoint(): void {
+		$captured_url  = null;
+		$captured_body = null;
+
 		add_filter(
 			'pre_http_request',
-			function () {
+			function ( $preempt, $args, $url ) use ( &$captured_url, &$captured_body ) {
+				unset( $preempt );
+				$captured_url  = $url;
+				$captured_body = json_decode( $args['body'], true );
 				return array(
 					'response' => array( 'code' => 200 ),
-					'body'     => wp_json_encode(
-						array(
-							'fraud_event_id' => 123,
-							'decision'       => 'challenge',
-							'risk_score'     => 65,
-						)
-					),
+					'body'     => wp_json_encode( array( 'status' => 'ok' ) ),
 				);
-			}
+			},
+			10,
+			3
 		);

-		$result = $this->sut->send_event( 'checkout_started', array( 'session_id' => 'test-session' ) );
+		$this->sut->report( 'test-session-id', array( 'event_type' => 'payment_success' ) );

-		$this->assertSame( ApiClient::DECISION_ALLOW, $result, 'Should fail open with allow until challenge flow is implemented' );
-		$this->assertLogged( 'error', 'Invalid decision value "challenge"' );
+		$this->assertStringContainsString( 'blackbox-api.wp.com/v1/report', $captured_url );
+		$this->assertSame( 'test-session-id', $captured_body['session_id'] );
+		$this->assertArrayHasKey( 'extra', $captured_body );
+		$this->assertArrayHasKey( 'blog_id', $captured_body['extra'] );
+		$this->assertSame( 12345, $captured_body['extra']['blog_id'] );
 	}

 	/**
-	 * @testdox Send Event should log session ID from nested session data structure.
+	 * Test report returns true on success.
+	 *
+	 * @testdox report() returns true on success
 	 */
-	public function test_send_event_logs_session_id_from_nested_structure(): void {
+	public function test_report_returns_true_on_success(): void {
 		add_filter(
 			'pre_http_request',
-			function () {
-				return array(
-					'response' => array( 'code' => 200 ),
-					'body'     => wp_json_encode(
-						array(
-							'fraud_event_id' => 123,
-							'decision'       => 'allow',
-						)
-					),
-				);
-			}
-		);
-
-		// Use the nested structure that SessionDataCollector::collect() returns.
-		$session_data = array(
-			'event_type' => 'cart_item_added',
-			'session'    => array(
-				'session_id' => 'nested-test-session-id',
-				'ip_address' => '192.168.1.1',
-			),
-			'customer'   => array(
-				'first_name' => 'Test',
-			),
+			fn() => array(
+				'response' => array( 'code' => 200 ),
+				'body'     => wp_json_encode( array( 'status' => 'ok' ) ),
+			)
 		);

-		$result = $this->sut->send_event( 'cart_item_added', $session_data );
+		$result = $this->sut->report( 'test-session-id', array( 'event_type' => 'payment_success' ) );

-		$this->assertSame( ApiClient::DECISION_ALLOW, $result );
-		$this->assertLogged(
-			'info',
-			'Session: nested-test-session-id',
-			array( 'source' => 'woo-fraud-protection' )
-		);
+		$this->assertTrue( $result );
+		$this->assertLogged( 'info', 'Event reported successfully' );
 	}

 	/**
-	 * @testdox Send Event should log 'unknown' session ID when session data is missing.
+	 * Test report returns false when blog_id not found.
+	 *
+	 * @testdox report() returns false when blog_id not found
 	 */
-	public function test_send_event_logs_unknown_when_session_data_missing(): void {
-		add_filter(
-			'pre_http_request',
-			function () {
-				return array(
-					'response' => array( 'code' => 200 ),
-					'body'     => wp_json_encode(
-						array(
-							'fraud_event_id' => 123,
-							'decision'       => 'allow',
-						)
-					),
-				);
-			}
-		);
-
-		// Session data without the 'session' key.
-		$session_data = array(
-			'event_type' => 'cart_item_added',
-			'customer'   => array(
-				'first_name' => 'Test',
-			),
-		);
+	public function test_report_returns_false_when_blog_id_not_found(): void {
+		update_option( 'jetpack_options', array( 'id' => null ) );

-		$result = $this->sut->send_event( 'cart_item_added', $session_data );
+		$result = $this->sut->report( 'test-session-id', array( 'event_type' => 'payment_success' ) );

-		$this->assertSame( ApiClient::DECISION_ALLOW, $result );
-		$this->assertLogged(
-			'info',
-			'Session: unknown',
-			array( 'source' => 'woo-fraud-protection' )
-		);
+		$this->assertFalse( $result );
+		$this->assertLogged( 'error', 'Jetpack blog ID not found' );
 	}

 	/**
-	 * @testdox Send Event should log 'unknown' session ID when session is not an array.
+	 * Test report returns false on HTTP error.
+	 *
+	 * @testdox report() returns false when HTTP request fails
 	 */
-	public function test_send_event_logs_unknown_when_session_is_not_array(): void {
+	public function test_report_returns_false_on_http_error(): void {
 		add_filter(
 			'pre_http_request',
-			function () {
-				return array(
-					'response' => array( 'code' => 200 ),
-					'body'     => wp_json_encode(
-						array(
-							'fraud_event_id' => 123,
-							'decision'       => 'allow',
-						)
-					),
-				);
-			}
-		);
-
-		// Session data with 'session' as a non-array value.
-		$session_data = array(
-			'event_type' => 'cart_item_added',
-			'session'    => 'invalid-string-value',
+			fn() => new WP_Error( 'http_error', 'Connection timeout' )
 		);

-		$result = $this->sut->send_event( 'cart_item_added', $session_data );
+		$result = $this->sut->report( 'test-session-id', array( 'event_type' => 'payment_success' ) );

-		$this->assertSame( ApiClient::DECISION_ALLOW, $result );
-		$this->assertLogged(
-			'info',
-			'Session: unknown',
-			array( 'source' => 'woo-fraud-protection' )
-		);
+		$this->assertFalse( $result );
+		$this->assertLogged( 'error', 'Failed to report event' );
 	}

 	/**
-	 * @testdox Should filter out null values from session data payload.
+	 * Test report returns false on server error.
+	 *
+	 * @testdox report() returns false when API returns error status
 	 */
-	public function test_filters_null_values_from_payload(): void {
-		$captured_request_body = null;
-
+	public function test_report_returns_false_on_server_error(): void {
 		add_filter(
 			'pre_http_request',
-			function ( $preempt, $args ) use ( &$captured_request_body ) {
-				$captured_request_body = $args['body'];
-				return array(
-					'response' => array( 'code' => 200 ),
-					'body'     => wp_json_encode( array( 'decision' => 'allow' ) ),
-				);
-			},
-			10,
-			2
-		);
-
-		$session_data = array(
-			'session_id'   => 'test-session',
-			'ip_address'   => '192.168.1.1',
-			'email'        => null,
-			'user_agent'   => 'Mozilla/5.0',
-			'billing_name' => null,
+			fn() => array(
+				'response' => array( 'code' => 500 ),
+				'body'     => 'Internal Server Error',
+			)
 		);

-		$this->sut->send_event( 'cart_updated', $session_data );
-
-		$this->assertNotNull( $captured_request_body, 'Request body should be captured' );
+		$result = $this->sut->report( 'test-session-id', array( 'event_type' => 'payment_success' ) );

-		$decoded_body = json_decode( $captured_request_body, true );
-		$this->assertArrayHasKey( 'event_type', $decoded_body, 'Should include not-null event_type' );
-		$this->assertArrayHasKey( 'session_id', $decoded_body, 'Should include not-null session_id' );
-		$this->assertArrayHasKey( 'ip_address', $decoded_body, 'Should include not-null ip_address' );
-		$this->assertArrayHasKey( 'user_agent', $decoded_body, 'Should include not-null user_agent' );
-		$this->assertArrayNotHasKey( 'email', $decoded_body, 'Should filter out null email' );
-		$this->assertArrayNotHasKey( 'billing_name', $decoded_body, 'Should filter out null billing_name' );
+		$this->assertFalse( $result );
+		$this->assertLogged( 'error', 'status code 500' );
 	}
 }
diff --git a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/BlockedSessionNoticeTest.php b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/BlockedSessionNoticeTest.php
index 376fddfa0b..e4a5e8c548 100644
--- a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/BlockedSessionNoticeTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/BlockedSessionNoticeTest.php
@@ -59,6 +59,8 @@ class BlockedSessionNoticeTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test blocked purchase notice added on checkout.
+	 *
 	 * @testdox maybe_add_blocked_purchase_notice should add notice when session is blocked and on checkout page.
 	 */
 	public function test_blocked_purchase_notice_added_on_checkout(): void {
@@ -75,6 +77,8 @@ class BlockedSessionNoticeTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test blocked purchase notice added on cart.
+	 *
 	 * @testdox maybe_add_blocked_purchase_notice should add notice when session is blocked and on cart page.
 	 */
 	public function test_blocked_purchase_notice_added_on_cart(): void {
@@ -91,6 +95,8 @@ class BlockedSessionNoticeTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test blocked purchase notice not added when session allowed.
+	 *
 	 * @testdox maybe_add_blocked_purchase_notice should not add notice when session is not blocked.
 	 */
 	public function test_blocked_purchase_notice_not_added_when_session_allowed(): void {
@@ -107,6 +113,8 @@ class BlockedSessionNoticeTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test blocked purchase notice prevents duplicates.
+	 *
 	 * @testdox maybe_add_blocked_purchase_notice should not add duplicate notices.
 	 */
 	public function test_blocked_purchase_notice_prevents_duplicates(): void {
@@ -134,6 +142,8 @@ class BlockedSessionNoticeTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test add payment method action displays blocked message.
+	 *
 	 * @testdox Should display generic error notice when before_woocommerce_add_payment_method action fires for blocked sessions.
 	 */
 	public function test_add_payment_method_action_displays_blocked_message(): void {
@@ -150,6 +160,8 @@ class BlockedSessionNoticeTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test add payment method action no message for non blocked session.
+	 *
 	 * @testdox Should not display message when add payment method action fires for non-blocked sessions.
 	 */
 	public function test_add_payment_method_action_no_message_for_non_blocked_session(): void {
@@ -163,6 +175,8 @@ class BlockedSessionNoticeTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test get message html purchase context.
+	 *
 	 * @testdox get_message_html should return purchase-specific message when context is 'purchase'.
 	 */
 	public function test_get_message_html_purchase_context(): void {
@@ -175,6 +189,8 @@ class BlockedSessionNoticeTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test get message html generic context.
+	 *
 	 * @testdox get_message_html should return generic message when context is 'generic' or not specified.
 	 */
 	public function test_get_message_html_generic_context(): void {
@@ -188,6 +204,8 @@ class BlockedSessionNoticeTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test get message plaintext purchase context.
+	 *
 	 * @testdox get_message_plaintext should return purchase-specific message when context is 'purchase'.
 	 */
 	public function test_get_message_plaintext_purchase_context(): void {
@@ -200,6 +218,8 @@ class BlockedSessionNoticeTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test get message plaintext generic context.
+	 *
 	 * @testdox get_message_plaintext should return generic message when context is 'generic' or not specified.
 	 */
 	public function test_get_message_plaintext_generic_context(): void {
diff --git a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CartBlockingTest.php b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CartBlockingTest.php
index 44717c507d..539047e849 100644
--- a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CartBlockingTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CartBlockingTest.php
@@ -84,6 +84,8 @@ class CartBlockingTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test add to cart blocked when session blocked.
+	 *
 	 * @testdox add_to_cart returns false and adds notice when session is blocked.
 	 */
 	public function test_add_to_cart_blocked_when_session_blocked(): void {
@@ -99,6 +101,8 @@ class CartBlockingTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test add to cart allowed when session allowed.
+	 *
 	 * @testdox add_to_cart succeeds when session is allowed.
 	 */
 	public function test_add_to_cart_allowed_when_session_allowed(): void {
@@ -112,6 +116,8 @@ class CartBlockingTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test add to cart allowed when feature disabled.
+	 *
 	 * @testdox add_to_cart succeeds when fraud protection is disabled.
 	 */
 	public function test_add_to_cart_allowed_when_feature_disabled(): void {
@@ -125,6 +131,8 @@ class CartBlockingTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test remove cart item blocked when session blocked.
+	 *
 	 * @testdox remove_cart_item returns false when session is blocked.
 	 */
 	public function test_remove_cart_item_blocked_when_session_blocked(): void {
@@ -142,6 +150,8 @@ class CartBlockingTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test remove cart item allowed when session allowed.
+	 *
 	 * @testdox remove_cart_item succeeds when session is allowed.
 	 */
 	public function test_remove_cart_item_allowed_when_session_allowed(): void {
@@ -156,6 +166,8 @@ class CartBlockingTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test remove cart item allowed when feature disabled.
+	 *
 	 * @testdox remove_cart_item succeeds when fraud protection is disabled.
 	 */
 	public function test_remove_cart_item_allowed_when_feature_disabled(): void {
@@ -169,6 +181,8 @@ class CartBlockingTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test set quantity blocked when session blocked.
+	 *
 	 * @testdox set_quantity returns false when session is blocked.
 	 */
 	public function test_set_quantity_blocked_when_session_blocked(): void {
@@ -186,6 +200,8 @@ class CartBlockingTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test set quantity allowed when session allowed.
+	 *
 	 * @testdox set_quantity succeeds when session is allowed.
 	 */
 	public function test_set_quantity_allowed_when_session_allowed(): void {
@@ -200,6 +216,8 @@ class CartBlockingTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test set quantity allowed when feature disabled.
+	 *
 	 * @testdox set_quantity succeeds when fraud protection is disabled.
 	 */
 	public function test_set_quantity_allowed_when_feature_disabled(): void {
diff --git a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CartEventTrackerTest.php b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CartEventTrackerTest.php
index 79d4f02357..725285f718 100644
--- a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CartEventTrackerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CartEventTrackerTest.php
@@ -8,8 +8,7 @@ declare( strict_types = 1 );
 namespace Automattic\WooCommerce\Tests\Internal\FraudProtection;

 use Automattic\WooCommerce\Internal\FraudProtection\CartEventTracker;
-use Automattic\WooCommerce\Internal\FraudProtection\FraudProtectionDispatcher;
-use Automattic\WooCommerce\Internal\FraudProtection\FraudProtectionController;
+use Automattic\WooCommerce\Internal\FraudProtection\SessionDataCollector;

 /**
  * Tests for CartEventTracker.
@@ -26,18 +25,11 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 	private $sut;

 	/**
-	 * Mock event dispatcher.
+	 * Mock session data collector.
 	 *
-	 * @var FraudProtectionDispatcher|\PHPUnit\Framework\MockObject\MockObject
+	 * @var SessionDataCollector|\PHPUnit\Framework\MockObject\MockObject
 	 */
-	private $mock_dispatcher;
-
-	/**
-	 * Mock fraud protection controller.
-	 *
-	 * @var FraudProtectionController|\PHPUnit\Framework\MockObject\MockObject
-	 */
-	private $mock_controller;
+	private $mock_collector;

 	/**
 	 * Test product.
@@ -57,13 +49,12 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 			wc_load_cart();
 		}

-		// Create mocks.
-		$this->mock_dispatcher = $this->createMock( FraudProtectionDispatcher::class );
-		$this->mock_controller = $this->createMock( FraudProtectionController::class );
+		// Create mock.
+		$this->mock_collector = $this->createMock( SessionDataCollector::class );

 		// Create system under test.
 		$this->sut = new CartEventTracker();
-		$this->sut->init( $this->mock_dispatcher );
+		$this->sut->init( $this->mock_collector );

 		// Create a test product.
 		$this->test_product = \WC_Helper_Product::create_simple_product();
@@ -73,38 +64,35 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 	}

 	/**
-	 * Test track_cart_page_loaded dispatches event.
-	 * The CartEventTracker::track_cart_page_loaded does not add any event data.
-	 * The data collection is handled by the SessionDataCollector.
-	 * So we only need to test if the dispatcher is called with no event data.
+	 * Test cart page loaded collects data.
+	 *
+	 * @testdox track_cart_page_loaded() collects session data with empty event data.
 	 */
-	public function test_track_cart_page_loaded_dispatches_event(): void {
-		// Mock dispatcher to verify event is dispatched with empty event data.
-		$this->mock_dispatcher
+	public function test_track_cart_page_loaded_collects_data(): void {
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' )
+			->method( 'collect' )
 			->with(
 				$this->equalTo( 'cart_page_loaded' ),
 				$this->equalTo( array() )
 			);

-		// Call the method.
 		$this->sut->track_cart_page_loaded();
 	}

 	/**
-	 * Test track_cart_item_added tracks event.
+	 * Test cart item added collects data.
+	 *
+	 * @testdox track_cart_item_added() collects session data with event details.
 	 */
-	public function test_track_cart_item_added_tracks_event(): void {
-		// Mock the dispatcher to verify dispatch_event is called with event data.
-		$this->mock_dispatcher
+	public function test_track_cart_item_added_collects_data(): void {
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' )
+			->method( 'collect' )
 			->with(
 				$this->equalTo( 'cart_item_added' ),
 				$this->callback(
 					function ( $event_data ) {
-						// Verify the event data structure.
 						$this->assertArrayHasKey( 'action', $event_data );
 						$this->assertEquals( 'item_added', $event_data['action'] );
 						$this->assertArrayHasKey( 'product_id', $event_data );
@@ -116,7 +104,6 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 				)
 			);

-		// Call the method.
 		$this->sut->track_cart_item_added(
 			'test_cart_key',
 			$this->test_product->get_id(),
@@ -126,21 +113,20 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 	}

 	/**
-	 * Test track_cart_item_updated tracks event.
+	 * Test cart item updated collects data.
+	 *
+	 * @testdox track_cart_item_updated() collects session data with quantity change.
 	 */
-	public function test_track_cart_item_updated_tracks_event(): void {
-		// Add item to cart first.
+	public function test_track_cart_item_updated_collects_data(): void {
 		$cart_item_key = WC()->cart->add_to_cart( $this->test_product->get_id(), 1 );

-		// Mock the dispatcher.
-		$this->mock_dispatcher
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' )
+			->method( 'collect' )
 			->with(
 				$this->equalTo( 'cart_item_updated' ),
 				$this->callback(
 					function ( $event_data ) {
-						// Verify the event data structure.
 						$this->assertArrayHasKey( 'action', $event_data );
 						$this->assertEquals( 'item_updated', $event_data['action'] );
 						$this->assertArrayHasKey( 'quantity', $event_data );
@@ -152,7 +138,6 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 				)
 			);

-		// Call the method.
 		$this->sut->track_cart_item_updated(
 			$cart_item_key,
 			5,
@@ -162,21 +147,20 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 	}

 	/**
-	 * Test track_cart_item_removed tracks event.
+	 * Test cart item removed collects data.
+	 *
+	 * @testdox track_cart_item_removed() collects session data.
 	 */
-	public function test_track_cart_item_removed_tracks_event(): void {
-		// Add item to cart.
+	public function test_track_cart_item_removed_collects_data(): void {
 		$cart_item_key = WC()->cart->add_to_cart( $this->test_product->get_id(), 1 );

-		// Mock the dispatcher.
-		$this->mock_dispatcher
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' )
+			->method( 'collect' )
 			->with(
 				$this->equalTo( 'cart_item_removed' ),
 				$this->callback(
 					function ( $event_data ) {
-						// Verify the event data structure.
 						$this->assertArrayHasKey( 'action', $event_data );
 						$this->assertEquals( 'item_removed', $event_data['action'] );
 						return true;
@@ -184,29 +168,26 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 				)
 			);

-		// Remove the item from cart.
 		WC()->cart->remove_cart_item( $cart_item_key );

-		// Call the method directly.
 		$this->sut->track_cart_item_removed( $cart_item_key, WC()->cart );
 	}

 	/**
-	 * Test track_cart_item_restored tracks event.
+	 * Test cart item restored collects data.
+	 *
+	 * @testdox track_cart_item_restored() collects session data.
 	 */
-	public function test_track_cart_item_restored_tracks_event(): void {
-		// Add item to cart.
+	public function test_track_cart_item_restored_collects_data(): void {
 		$cart_item_key = WC()->cart->add_to_cart( $this->test_product->get_id(), 1 );

-		// Mock the dispatcher.
-		$this->mock_dispatcher
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' )
+			->method( 'collect' )
 			->with(
 				$this->equalTo( 'cart_item_restored' ),
 				$this->callback(
 					function ( $event_data ) {
-						// Verify the event data structure.
 						$this->assertArrayHasKey( 'action', $event_data );
 						$this->assertEquals( 'item_restored', $event_data['action'] );
 						return true;
@@ -214,7 +195,6 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 				)
 			);

-		// Call the method directly (simulating restore action).
 		$this->sut->track_cart_item_restored(
 			$cart_item_key,
 			WC()->cart
@@ -222,23 +202,22 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 	}

 	/**
-	 * Test that cart events include variation ID when present.
+	 * Test cart events include variation_id.
+	 *
+	 * @testdox Cart events include variation_id when present.
 	 */
 	public function test_cart_events_include_variation_id(): void {
-		// Create a variable product with variation.
 		$variable_product = \WC_Helper_Product::create_variation_product();
 		$variations       = $variable_product->get_available_variations();
 		$variation_id     = $variations[0]['variation_id'];

-		// Mock the dispatcher to capture event data.
-		$this->mock_dispatcher
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' )
+			->method( 'collect' )
 			->with(
 				$this->equalTo( 'cart_item_added' ),
 				$this->callback(
 					function ( $event_data ) use ( $variation_id ) {
-						// Verify the event data structure includes variation_id.
 						$this->assertArrayHasKey( 'action', $event_data );
 						$this->assertEquals( 'item_added', $event_data['action'] );
 						$this->assertArrayHasKey( 'variation_id', $event_data );
@@ -248,7 +227,6 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 				)
 			);

-		// Call the method with variation ID.
 		$this->sut->track_cart_item_added(
 			'test_cart_key',
 			$variable_product->get_id(),
@@ -256,7 +234,6 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 			$variation_id
 		);

-		// Clean up.
 		$variable_product->delete( true );
 	}

@@ -266,12 +243,10 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 	public function tearDown(): void {
 		parent::tearDown();

-		// Clean up test product.
 		if ( $this->test_product ) {
 			$this->test_product->delete( true );
 		}

-		// Empty cart.
 		WC()->cart->empty_cart();
 	}
 }
diff --git a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CheckoutEventTrackerTest.php b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CheckoutEventTrackerTest.php
index 68c63204f6..9e19678f9c 100644
--- a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CheckoutEventTrackerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CheckoutEventTrackerTest.php
@@ -7,11 +7,7 @@ declare( strict_types = 1 );

 namespace Automattic\WooCommerce\Tests\Internal\FraudProtection;

-use Automattic\WooCommerce\Internal\FraudProtection\ApiClient;
 use Automattic\WooCommerce\Internal\FraudProtection\CheckoutEventTracker;
-use Automattic\WooCommerce\Internal\FraudProtection\DecisionHandler;
-use Automattic\WooCommerce\Internal\FraudProtection\FraudProtectionController;
-use Automattic\WooCommerce\Internal\FraudProtection\FraudProtectionDispatcher;
 use Automattic\WooCommerce\Internal\FraudProtection\SessionDataCollector;

 /**
@@ -28,26 +24,12 @@ class CheckoutEventTrackerTest extends \WC_Unit_Test_Case {
 	 */
 	private $sut;

-	/**
-	 * Mock fraud protection dispatcher.
-	 *
-	 * @var FraudProtectionDispatcher|\PHPUnit\Framework\MockObject\MockObject
-	 */
-	private $mock_dispatcher;
-
 	/**
 	 * Mock session data collector.
 	 *
 	 * @var SessionDataCollector|\PHPUnit\Framework\MockObject\MockObject
 	 */
-	private $mock_session_data_collector;
-
-	/**
-	 * Mock fraud protection controller.
-	 *
-	 * @var FraudProtectionController|\PHPUnit\Framework\MockObject\MockObject
-	 */
-	private $mock_controller;
+	private $mock_collector;

 	/**
 	 * Runs before each test.
@@ -60,14 +42,12 @@ class CheckoutEventTrackerTest extends \WC_Unit_Test_Case {
 			wc_load_cart();
 		}

-		// Create mocks.
-		$this->mock_dispatcher             = $this->createMock( FraudProtectionDispatcher::class );
-		$this->mock_session_data_collector = $this->createMock( SessionDataCollector::class );
-		$this->mock_controller             = $this->createMock( FraudProtectionController::class );
+		// Create mock.
+		$this->mock_collector = $this->createMock( SessionDataCollector::class );

 		// Create system under test.
 		$this->sut = new CheckoutEventTracker();
-		$this->sut->init( $this->mock_dispatcher, $this->mock_session_data_collector );
+		$this->sut->init( $this->mock_collector );
 	}

 	// ========================================
@@ -75,22 +55,19 @@ class CheckoutEventTrackerTest extends \WC_Unit_Test_Case {
 	// ========================================

 	/**
-	 * Test track_checkout_page_loaded dispatches event.
-	 * The CheckoutEventTracker::track_checkout_page_loaded does not add any event data.
-	 * The data collection is handled by the SessionDataCollector.
-	 * So we only need to test if the dispatcher is called with no event data.
+	 * Test checkout page loaded collects data.
+	 *
+	 * @testdox track_checkout_page_loaded() collects session data with empty event data.
 	 */
-	public function test_track_checkout_page_loaded_dispatches_event(): void {
-		// Mock dispatcher to verify event is dispatched with empty event data.
-		$this->mock_dispatcher
+	public function test_track_checkout_page_loaded_collects_data(): void {
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' )
+			->method( 'collect' )
 			->with(
 				$this->equalTo( 'checkout_page_loaded' ),
 				$this->equalTo( array() )
 			);

-		// Call the method.
 		$this->sut->track_checkout_page_loaded();
 	}

@@ -99,22 +76,19 @@ class CheckoutEventTrackerTest extends \WC_Unit_Test_Case {
 	// ========================================

 	/**
-	 * Test track_blocks_checkout_update dispatches event with session data.
-	 * The CheckoutEventTracker::track_blocks_checkout_update does not add any event data.
-	 * The data collection is handled by the SessionDataCollector.
-	 * So we only need to test if the dispatcher is called with no event data.
+	 * Test blocks checkout update collects data.
+	 *
+	 * @testdox track_blocks_checkout_update() collects session data with empty event data.
 	 */
-	public function test_track_blocks_checkout_update_dispatches_event_with_empty_session_data(): void {
-		// Mock dispatcher to verify event is dispatched with empty event data.
-		$this->mock_dispatcher
+	public function test_track_blocks_checkout_update_collects_data(): void {
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' )
+			->method( 'collect' )
 			->with(
 				$this->equalTo( 'checkout_update' ),
 				$this->equalTo( array() )
 			);

-		// Call the method.
 		$this->sut->track_blocks_checkout_update();
 	}

@@ -123,25 +97,22 @@ class CheckoutEventTrackerTest extends \WC_Unit_Test_Case {
 	// ========================================

 	/**
-	 * Test track_shortcode_checkout_field_update schedules event with billing email when billing country changes.
+	 * Test shortcode checkout field update collects data on billing country change.
+	 *
+	 * @testdox track_shortcode_checkout_field_update() collects data when billing country changes.
 	 */
-	public function test_track_shortcode_checkout_field_update_schedules_event_with_billing_email(): void {
-		// Mock feature as enabled.
-		$this->mock_controller->method( 'feature_is_enabled' )->willReturn( true );
-
-		// Mock SessionDataCollector to return different billing country.
-		$this->mock_session_data_collector
+	public function test_track_shortcode_checkout_field_update_collects_data_on_billing_country_change(): void {
+		$this->mock_collector
 			->method( 'get_current_billing_country' )
-			->willReturn( 'CA' ); // Current country is CA.
+			->willReturn( 'CA' );

-		$this->mock_session_data_collector
+		$this->mock_collector
 			->method( 'get_current_shipping_country' )
 			->willReturn( null );

-		// Mock scheduler to verify dispatch_event is called.
-		$this->mock_dispatcher
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' )
+			->method( 'collect' )
 			->with(
 				$this->equalTo( 'checkout_update' ),
 				$this->callback(
@@ -154,43 +125,38 @@ class CheckoutEventTrackerTest extends \WC_Unit_Test_Case {
 				)
 			);

-		// Simulate checkout field update with billing email and country change (CA -> US).
 		$posted_data = 'billing_email=test@example.com&billing_first_name=John&billing_last_name=Doe&billing_country=US';
 		$this->sut->track_shortcode_checkout_field_update( $posted_data );
 	}

 	/**
-	 * Test track_shortcode_checkout_field_update extracts billing fields correctly when country changes.
+	 * Test shortcode checkout field update extracts billing fields.
+	 *
+	 * @testdox track_shortcode_checkout_field_update() extracts billing fields correctly.
 	 */
 	public function test_track_shortcode_checkout_field_update_extracts_billing_fields(): void {
-		// Mock feature as enabled.
-		$this->mock_controller->method( 'feature_is_enabled' )->willReturn( true );
-
-		// Mock SessionDataCollector to return different billing country.
-		$this->mock_session_data_collector
+		$this->mock_collector
 			->method( 'get_current_billing_country' )
-			->willReturn( 'CA' ); // Current country is CA.
+			->willReturn( 'CA' );

-		$this->mock_session_data_collector
+		$this->mock_collector
 			->method( 'get_current_shipping_country' )
 			->willReturn( null );

-		// Mock scheduler to capture event data.
 		$captured_event_data = null;
-		$this->mock_dispatcher
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' )
+			->method( 'collect' )
 			->willReturnCallback(
 				function ( $event_type, $event_data ) use ( &$captured_event_data ) {
 					$captured_event_data = $event_data;
+					return array();
 				}
 			);

-		// Simulate checkout field update with multiple billing fields and country change.
 		$posted_data = 'billing_email=test@example.com&billing_first_name=John&billing_last_name=Doe&billing_country=US&billing_city=New+York';
 		$this->sut->track_shortcode_checkout_field_update( $posted_data );

-		// Verify extracted fields.
 		$this->assertNotNull( $captured_event_data );
 		$this->assertEquals( 'field_update', $captured_event_data['action'] );
 		$this->assertEquals( 'test@example.com', $captured_event_data['billing_email'] );
@@ -201,37 +167,33 @@ class CheckoutEventTrackerTest extends \WC_Unit_Test_Case {
 	}

 	/**
-	 * Test track_shortcode_checkout_field_update extracts shipping fields when ship_to_different_address is set and shipping country changes.
+	 * Test shortcode checkout field update extracts shipping fields.
+	 *
+	 * @testdox track_shortcode_checkout_field_update() extracts shipping fields when ship_to_different_address is set.
 	 */
 	public function test_track_shortcode_checkout_field_update_extracts_shipping_fields(): void {
-		// Mock feature as enabled.
-		$this->mock_controller->method( 'feature_is_enabled' )->willReturn( true );
-
-		// Mock SessionDataCollector to return different shipping country.
-		$this->mock_session_data_collector
+		$this->mock_collector
 			->method( 'get_current_billing_country' )
 			->willReturn( null );

-		$this->mock_session_data_collector
+		$this->mock_collector
 			->method( 'get_current_shipping_country' )
-			->willReturn( 'CA' ); // Current shipping country is CA.
+			->willReturn( 'CA' );

-		// Mock scheduler to capture event data.
 		$captured_event_data = null;
-		$this->mock_dispatcher
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' )
+			->method( 'collect' )
 			->willReturnCallback(
 				function ( $event_type, $event_data ) use ( &$captured_event_data ) {
 					$captured_event_data = $event_data;
+					return array();
 				}
 			);

-		// Simulate checkout field update with shipping fields and country change.
 		$posted_data = 'billing_email=test@example.com&ship_to_different_address=1&shipping_first_name=Jane&shipping_last_name=Smith&shipping_city=Los+Angeles&shipping_country=US';
 		$this->sut->track_shortcode_checkout_field_update( $posted_data );

-		// Verify extracted fields.
 		$this->assertNotNull( $captured_event_data );
 		$this->assertEquals( 'Jane', $captured_event_data['shipping_first_name'] );
 		$this->assertEquals( 'Smith', $captured_event_data['shipping_last_name'] );
@@ -239,37 +201,33 @@ class CheckoutEventTrackerTest extends \WC_Unit_Test_Case {
 	}

 	/**
-	 * Test track_shortcode_checkout_field_update does not extract shipping fields when ship_to_different_address is not set.
+	 * Test shortcode checkout field update skips shipping fields when not different address.
+	 *
+	 * @testdox track_shortcode_checkout_field_update() skips shipping fields when not shipping to different address.
 	 */
 	public function test_track_shortcode_checkout_field_update_skips_shipping_fields_when_not_different_address(): void {
-		// Mock feature as enabled.
-		$this->mock_controller->method( 'feature_is_enabled' )->willReturn( true );
-
-		// Mock SessionDataCollector to return different billing country.
-		$this->mock_session_data_collector
+		$this->mock_collector
 			->method( 'get_current_billing_country' )
-			->willReturn( 'CA' ); // Current billing country is CA.
+			->willReturn( 'CA' );

-		$this->mock_session_data_collector
+		$this->mock_collector
 			->method( 'get_current_shipping_country' )
 			->willReturn( null );

-		// Mock scheduler to capture event data.
 		$captured_event_data = null;
-		$this->mock_dispatcher
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' )
+			->method( 'collect' )
 			->willReturnCallback(
 				function ( $event_type, $event_data ) use ( &$captured_event_data ) {
 					$captured_event_data = $event_data;
+					return array();
 				}
 			);

-		// Simulate checkout field update without ship_to_different_address but with billing country change.
 		$posted_data = 'billing_email=test@example.com&billing_country=US&shipping_first_name=Jane&shipping_last_name=Smith';
 		$this->sut->track_shortcode_checkout_field_update( $posted_data );

-		// Verify shipping fields are not extracted.
 		$this->assertNotNull( $captured_event_data );
 		$this->assertArrayNotHasKey( 'shipping_first_name', $captured_event_data );
 		$this->assertArrayNotHasKey( 'shipping_last_name', $captured_event_data );
@@ -280,221 +238,112 @@ class CheckoutEventTrackerTest extends \WC_Unit_Test_Case {
 	// ========================================

 	/**
-	 * Test event is dispatched when billing country changes.
-	 */
-	public function test_event_dispatched_when_billing_country_changes(): void {
-		// Mock SessionDataCollector to return different billing country.
-		$this->mock_session_data_collector
-			->method( 'get_current_billing_country' )
-			->willReturn( 'CA' ); // Current country is CA.
-
-		$this->mock_session_data_collector
-			->method( 'get_current_shipping_country' )
-			->willReturn( null );
-
-		// Expect event to be dispatched once.
-		$this->mock_dispatcher
-			->expects( $this->once() )
-			->method( 'dispatch_event' )
-			->with(
-				$this->equalTo( 'checkout_update' ),
-				$this->callback(
-					function ( $event_data ) {
-						return isset( $event_data['billing_country'] ) && 'US' === $event_data['billing_country'];
-					}
-				)
-			);
-
-		// Posted data with billing country changing from CA to US.
-		$posted_data = 'billing_email=test@example.com&billing_country=US';
-		$this->sut->track_shortcode_checkout_field_update( $posted_data );
-	}
-
-	/**
-	 * Test event is dispatched when shipping country changes.
-	 */
-	public function test_event_dispatched_when_shipping_country_changes(): void {
-		// Mock SessionDataCollector to return current countries.
-		$this->mock_session_data_collector
-			->method( 'get_current_billing_country' )
-			->willReturn( 'US' ); // Current billing country matches posted.
-
-		$this->mock_session_data_collector
-			->method( 'get_current_shipping_country' )
-			->willReturn( 'CA' ); // Current shipping country is CA.
-
-		// Expect event to be dispatched once.
-		$this->mock_dispatcher
-			->expects( $this->once() )
-			->method( 'dispatch_event' )
-			->with(
-				$this->equalTo( 'checkout_update' ),
-				$this->callback(
-					function ( $event_data ) {
-						return isset( $event_data['shipping_country'] ) && 'US' === $event_data['shipping_country'];
-					}
-				)
-			);
-
-		// Posted data with shipping country changing from CA to US.
-		$posted_data = 'billing_country=US&ship_to_different_address=1&shipping_country=US';
-		$this->sut->track_shortcode_checkout_field_update( $posted_data );
-	}
-
-	/**
-	 * Test event is NOT dispatched when neither country changes.
+	 * Test no collection when no country changes.
+	 *
+	 * @testdox Event is NOT collected when neither country changes.
 	 */
-	public function test_event_not_dispatched_when_no_country_changes(): void {
-		// Mock SessionDataCollector to return same countries as posted.
-		$this->mock_session_data_collector
+	public function test_no_collection_when_no_country_changes(): void {
+		$this->mock_collector
 			->method( 'get_current_billing_country' )
-			->willReturn( 'US' ); // Same as posted.
+			->willReturn( 'US' );

-		$this->mock_session_data_collector
+		$this->mock_collector
 			->method( 'get_current_shipping_country' )
-			->willReturn( 'US' ); // Same as posted.
+			->willReturn( 'US' );

-		// Expect event to NOT be dispatched.
-		$this->mock_dispatcher
+		$this->mock_collector
 			->expects( $this->never() )
-			->method( 'dispatch_event' );
+			->method( 'collect' );

-		// Posted data with no country changes.
 		$posted_data = 'billing_email=test@example.com&billing_country=US&shipping_country=US';
 		$this->sut->track_shortcode_checkout_field_update( $posted_data );
 	}

 	/**
-	 * Test event is NOT dispatched when only non-country fields change.
+	 * Test no collection when only non-country fields change.
+	 *
+	 * @testdox Event is NOT collected when only non-country fields change.
 	 */
-	public function test_event_not_dispatched_when_only_non_country_fields_change(): void {
-		// Mock SessionDataCollector to return countries.
-		$this->mock_session_data_collector
+	public function test_no_collection_when_only_non_country_fields_change(): void {
+		$this->mock_collector
 			->method( 'get_current_billing_country' )
 			->willReturn( 'US' );

-		$this->mock_session_data_collector
+		$this->mock_collector
 			->method( 'get_current_shipping_country' )
 			->willReturn( null );

-		// Expect event to NOT be dispatched.
-		$this->mock_dispatcher
+		$this->mock_collector
 			->expects( $this->never() )
-			->method( 'dispatch_event' );
+			->method( 'collect' );

-		// Posted data with only non-country fields (email, name, phone).
 		$posted_data = 'billing_email=test@example.com&billing_first_name=John&billing_phone=1234567890';
 		$this->sut->track_shortcode_checkout_field_update( $posted_data );
 	}

 	/**
-	 * Test event is NOT dispatched when ship_to_different_address is not set and current shipping matches billing.
-	 */
-	public function test_event_not_dispatched_when_shipping_already_matches_billing(): void {
-		// Mock SessionDataCollector: shipping already matches billing.
-		$this->mock_session_data_collector
-			->method( 'get_current_billing_country' )
-			->willReturn( 'US' );
-
-		$this->mock_session_data_collector
-			->method( 'get_current_shipping_country' )
-			->willReturn( 'US' ); // Already matches billing - no change.
-
-		// Expect event to NOT be dispatched (no effective change).
-		$this->mock_dispatcher
-			->expects( $this->never() )
-			->method( 'dispatch_event' );
-
-		// Posted data with NO ship_to_different_address flag, billing stays US.
-		$posted_data = 'billing_country=US&billing_email=test@example.com';
-		$this->sut->track_shortcode_checkout_field_update( $posted_data );
-	}
-
-	/**
-	 * Test event is dispatched when billing country changes from null.
+	 * Test collection when billing country changes from null.
+	 *
+	 * @testdox Event is collected when billing country changes from null.
 	 */
-	public function test_event_dispatched_when_billing_country_changes_from_null(): void {
-		// Mock SessionDataCollector to return null for current billing country.
-		$this->mock_session_data_collector
+	public function test_collection_when_billing_country_changes_from_null(): void {
+		$this->mock_collector
 			->method( 'get_current_billing_country' )
-			->willReturn( null ); // No current billing country.
+			->willReturn( null );

-		$this->mock_session_data_collector
+		$this->mock_collector
 			->method( 'get_current_shipping_country' )
 			->willReturn( null );

-		// Expect event to be dispatched.
-		$this->mock_dispatcher
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' );
+			->method( 'collect' );

-		// Posted data with billing country (first time setting).
 		$posted_data = 'billing_email=test@example.com&billing_country=US';
 		$this->sut->track_shortcode_checkout_field_update( $posted_data );
 	}

 	/**
-	 * Test event is dispatched when user unchecks ship_to_different_address and current shipping country differs from billing.
+	 * Test collection when ship_to_different_address unchecked with different countries.
 	 *
-	 * Scenario: User had different shipping address with different country (e.g., shipping=CA, billing=US),
-	 * then unchecks "ship to different address". The effective shipping country changes from CA to US.
+	 * @testdox Event is collected when ship_to_different_address unchecked with different countries.
 	 */
-	public function test_event_dispatched_when_ship_to_different_address_unchecked_with_different_countries(): void {
-		// Mock SessionDataCollector: billing=US, shipping=CA (previously different).
-		$this->mock_session_data_collector
+	public function test_collection_when_ship_to_different_address_unchecked_with_different_countries(): void {
+		$this->mock_collector
 			->method( 'get_current_billing_country' )
 			->willReturn( 'US' );

-		$this->mock_session_data_collector
+		$this->mock_collector
 			->method( 'get_current_shipping_country' )
-			->willReturn( 'CA' ); // Was different.
+			->willReturn( 'CA' );

-		// Expect event to be dispatched (shipping effectively changed from CA to US).
-		$this->mock_dispatcher
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' )
+			->method( 'collect' )
 			->with(
 				$this->equalTo( 'checkout_update' ),
 				$this->anything()
 			);

-		// Posted data: ship_to_different_address NOT set (unchecked), billing country is US.
 		$posted_data = 'billing_country=US&billing_email=test@example.com';
 		$this->sut->track_shortcode_checkout_field_update( $posted_data );
 	}

-	/**
-	 * Test event is NOT dispatched when user unchecks ship_to_different_address but countries are already the same.
-	 */
-	public function test_event_not_dispatched_when_ship_to_different_address_unchecked_with_same_countries(): void {
-		// Mock SessionDataCollector: billing=US, shipping=US (already same).
-		$this->mock_session_data_collector
-			->method( 'get_current_billing_country' )
-			->willReturn( 'US' );
-
-		$this->mock_session_data_collector
-			->method( 'get_current_shipping_country' )
-			->willReturn( 'US' ); // Same as billing.
-
-		// Expect event to NOT be dispatched (no effective change).
-		$this->mock_dispatcher
-			->expects( $this->never() )
-			->method( 'dispatch_event' );
-
-		// Posted data: ship_to_different_address NOT set, billing country is US.
-		$posted_data = 'billing_country=US&billing_email=test@example.com';
-		$this->sut->track_shortcode_checkout_field_update( $posted_data );
-	}
+	// ========================================
+	// Order Placed Tests
+	// ========================================

 	/**
-	 * Test track_order_placed dispatches event with correct data structure.
+	 * Test track order placed collects data.
+	 *
+	 * @testdox track_order_placed() collects session data with order details.
 	 */
-	public function test_track_order_placed_dispatches_event(): void {
+	public function test_track_order_placed_collects_data(): void {
 		$order = \WC_Helper_Order::create_order();

-		$this->mock_dispatcher
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' )
+			->method( 'collect' )
 			->with(
 				$this->equalTo( 'order_placed' ),
 				$this->callback(
@@ -513,7 +362,6 @@ class CheckoutEventTrackerTest extends \WC_Unit_Test_Case {

 		$this->sut->track_order_placed( $order->get_id(), $order );

-		// Clean up.
 		$order->delete( true );
 	}
 }
diff --git a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/DecisionHandlerTest.php b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/DecisionHandlerTest.php
index 50b95db534..1039a21728 100644
--- a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/DecisionHandlerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/DecisionHandlerTest.php
@@ -54,6 +54,8 @@ class DecisionHandlerTest extends WC_Unit_Test_Case {
 	}

 	/**
+	 * Test apply allow decision.
+	 *
 	 * @testdox Should apply allow decision and update session to allowed when session is not blocked.
 	 */
 	public function test_apply_allow_decision(): void {
@@ -71,6 +73,8 @@ class DecisionHandlerTest extends WC_Unit_Test_Case {
 	}

 	/**
+	 * Test allow decision does not overwrite blocked session.
+	 *
 	 * @testdox Should preserve blocked session status when allow decision is received.
 	 *
 	 * This prevents race conditions where emptying the cart during block_session
@@ -91,6 +95,8 @@ class DecisionHandlerTest extends WC_Unit_Test_Case {
 	}

 	/**
+	 * Test apply block decision.
+	 *
 	 * @testdox Should apply block decision and update session to blocked.
 	 */
 	public function test_apply_block_decision(): void {
@@ -104,6 +110,8 @@ class DecisionHandlerTest extends WC_Unit_Test_Case {
 	}

 	/**
+	 * Test invalid decision defaults to allow.
+	 *
 	 * @testdox Should default to allow for invalid decision and log warning.
 	 */
 	public function test_invalid_decision_defaults_to_allow(): void {
@@ -122,6 +130,8 @@ class DecisionHandlerTest extends WC_Unit_Test_Case {
 	}

 	/**
+	 * Test filter can override block to allow.
+	 *
 	 * @testdox Should allow filter to override decision from block to allow.
 	 */
 	public function test_filter_can_override_block_to_allow(): void {
@@ -147,6 +157,8 @@ class DecisionHandlerTest extends WC_Unit_Test_Case {
 	}

 	/**
+	 * Test filter can override allow to block.
+	 *
 	 * @testdox Should allow filter to override decision from allow to block.
 	 */
 	public function test_filter_can_override_allow_to_block(): void {
@@ -168,6 +180,8 @@ class DecisionHandlerTest extends WC_Unit_Test_Case {
 	}

 	/**
+	 * Test filter invalid return uses original decision.
+	 *
 	 * @testdox Should reject invalid filter return value and use original decision.
 	 */
 	public function test_filter_invalid_return_uses_original_decision(): void {
diff --git a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/FraudProtectionDispatcherTest.php b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/FraudProtectionDispatcherTest.php
deleted file mode 100644
index 8771c9d4b5..0000000000
--- a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/FraudProtectionDispatcherTest.php
+++ /dev/null
@@ -1,329 +0,0 @@
-<?php
-/**
- * FraudProtectionDispatcherTest class file.
- *
- * @package WooCommerce\Tests
- */
-
-declare( strict_types = 1 );
-
-namespace Automattic\WooCommerce\Tests\Internal\FraudProtection;
-
-use Automattic\WooCommerce\Internal\FraudProtection\ApiClient;
-use Automattic\WooCommerce\Internal\FraudProtection\DecisionHandler;
-use Automattic\WooCommerce\Internal\FraudProtection\FraudProtectionController;
-use Automattic\WooCommerce\Internal\FraudProtection\FraudProtectionDispatcher;
-use Automattic\WooCommerce\Internal\FraudProtection\SessionDataCollector;
-
-/**
- * Tests for FraudProtectionDispatcher.
- *
- * @covers \Automattic\WooCommerce\Internal\FraudProtection\FraudProtectionDispatcher
- */
-class FraudProtectionDispatcherTest extends \WC_Unit_Test_Case {
-
-	/**
-	 * The system under test.
-	 *
-	 * @var FraudProtectionDispatcher
-	 */
-	private $sut;
-
-	/**
-	 * Mock API client.
-	 *
-	 * @var ApiClient|\PHPUnit\Framework\MockObject\MockObject
-	 */
-	private $api_client_mock;
-
-	/**
-	 * Mock decision handler.
-	 *
-	 * @var DecisionHandler|\PHPUnit\Framework\MockObject\MockObject
-	 */
-	private $decision_handler_mock;
-
-	/**
-	 * Mock fraud protection controller.
-	 *
-	 * @var FraudProtectionController|\PHPUnit\Framework\MockObject\MockObject
-	 */
-	private $controller_mock;
-
-	/**
-	 * Mock session data collector.
-	 *
-	 * @var SessionDataCollector|\PHPUnit\Framework\MockObject\MockObject
-	 */
-	private $data_collector_mock;
-
-	/**
-	 * Runs before each test.
-	 */
-	public function setUp(): void {
-		parent::setUp();
-
-		// Create mocks.
-		$this->api_client_mock       = $this->createMock( ApiClient::class );
-		$this->decision_handler_mock = $this->createMock( DecisionHandler::class );
-		$this->controller_mock       = $this->createMock( FraudProtectionController::class );
-		$this->data_collector_mock   = $this->createMock( SessionDataCollector::class );
-
-		// By default, feature is enabled.
-		$this->controller_mock->method( 'feature_is_enabled' )->willReturn( true );
-
-		// Create dispatcher and inject mocks.
-		$this->sut = new FraudProtectionDispatcher();
-		$this->sut->init(
-			$this->api_client_mock,
-			$this->decision_handler_mock,
-			$this->controller_mock,
-			$this->data_collector_mock
-		);
-	}
-
-	/**
-	 * Test that dispatch_event collects session data and sends event to API and applies decision.
-	 */
-	public function test_dispatch_event_sends_to_api_and_applies_decision(): void {
-		$event_type = 'test_event';
-		$event_data = array(
-			'action'     => 'test_action',
-			'product_id' => 123,
-		);
-
-		$collected_data = array(
-			'session'    => array( 'session_id' => 'test-session-123' ),
-			'action'     => 'test_action',
-			'product_id' => 123,
-		);
-
-		// Expect data collector to be called with event type and event data.
-		$this->data_collector_mock
-			->expects( $this->once() )
-			->method( 'collect' )
-			->with( $event_type, $event_data )
-			->willReturn( $collected_data );
-
-		// Expect API client to be called with the collected data.
-		$this->api_client_mock
-			->expects( $this->once() )
-			->method( 'send_event' )
-			->with(
-				$this->equalTo( $event_type ),
-				$this->callback(
-					function ( $data ) use ( $collected_data ) {
-						// Verify the payload structure.
-						$this->assertArrayHasKey( 'session', $data );
-						$this->assertEquals( 'test-session-123', $data['session']['session_id'] );
-						$this->assertEquals( 'test_action', $data['action'] );
-						$this->assertEquals( 123, $data['product_id'] );
-						return true;
-					}
-				)
-			)
-			->willReturn( ApiClient::DECISION_ALLOW );
-
-		// Expect decision handler to be called with the decision and collected data.
-		$this->decision_handler_mock
-			->expects( $this->once() )
-			->method( 'apply_decision' )
-			->with( ApiClient::DECISION_ALLOW, $collected_data );
-
-		// Call dispatch_event with event data.
-		$this->sut->dispatch_event( $event_type, $event_data );
-	}
-
-	/**
-	 * Test that dispatch_event handles data without session gracefully.
-	 */
-	public function test_dispatch_event_handles_missing_session_data(): void {
-		$event_type     = 'test_event';
-		$event_data     = array( 'invalid' => 'data_without_session' );
-		$collected_data = array( 'invalid' => 'data_without_session' );
-
-		// Expect data collector to be called.
-		$this->data_collector_mock
-			->expects( $this->once() )
-			->method( 'collect' )
-			->with( $event_type, $event_data )
-			->willReturn( $collected_data );
-
-		// Expect API client to be called with the collected data.
-		$this->api_client_mock
-			->expects( $this->once() )
-			->method( 'send_event' )
-			->with(
-				$this->equalTo( $event_type ),
-				$this->callback(
-					function ( $data ) {
-						// Verify the payload has the invalid key.
-						$this->assertArrayHasKey( 'invalid', $data );
-						$this->assertEquals( 'data_without_session', $data['invalid'] );
-						// Session key should not exist or be empty.
-						$this->assertFalse( isset( $data['session']['session_id'] ) || ! empty( $data['session']['session_id'] ) );
-						return true;
-					}
-				)
-			)
-			->willReturn( ApiClient::DECISION_ALLOW );
-
-		// Expect decision handler to be called with collected data.
-		$this->decision_handler_mock
-			->expects( $this->once() )
-			->method( 'apply_decision' )
-			->with( ApiClient::DECISION_ALLOW, $collected_data );
-
-		// Call dispatch_event - should handle gracefully.
-		$this->sut->dispatch_event( $event_type, $event_data );
-	}
-
-	/**
-	 * Test that dispatch_event respects block decisions.
-	 */
-	public function test_dispatch_event_applies_block_decision(): void {
-		$event_type = 'cart_item_added';
-		$event_data = array(
-			'action'     => 'item_added',
-			'product_id' => 456,
-		);
-
-		$collected_data = array(
-			'session'    => array( 'session_id' => 'test' ),
-			'action'     => 'item_added',
-			'product_id' => 456,
-		);
-
-		// Expect data collector to be called.
-		$this->data_collector_mock
-			->expects( $this->once() )
-			->method( 'collect' )
-			->with( $event_type, $event_data )
-			->willReturn( $collected_data );
-
-		// API returns block decision.
-		$this->api_client_mock
-			->expects( $this->once() )
-			->method( 'send_event' )
-			->with(
-				$this->equalTo( $event_type ),
-				$this->callback(
-					function ( $data ) {
-						// Verify the payload structure for cart event.
-						$this->assertArrayHasKey( 'session', $data );
-						$this->assertEquals( 'test', $data['session']['session_id'] );
-						$this->assertEquals( 'item_added', $data['action'] );
-						$this->assertEquals( 456, $data['product_id'] );
-						return true;
-					}
-				)
-			)
-			->willReturn( ApiClient::DECISION_BLOCK );
-
-		// Expect decision handler to be called with block decision and collected data.
-		$this->decision_handler_mock
-			->expects( $this->once() )
-			->method( 'apply_decision' )
-			->with( ApiClient::DECISION_BLOCK, $collected_data );
-
-		// Call dispatch_event.
-		$this->sut->dispatch_event( $event_type, $event_data );
-	}
-
-	/**
-	 * Test that dispatch_event doesn't send events when feature is disabled.
-	 */
-	public function test_dispatch_event_skips_when_feature_disabled(): void {
-		// Create fresh API and decision handler mocks that should never be called.
-		$api_client_mock = $this->createMock( ApiClient::class );
-		$api_client_mock->expects( $this->never() )->method( 'send_event' );
-
-		$decision_handler_mock = $this->createMock( DecisionHandler::class );
-		$decision_handler_mock->expects( $this->never() )->method( 'apply_decision' );
-
-		// Create data collector mock that should never be called.
-		$data_collector_mock = $this->createMock( SessionDataCollector::class );
-		$data_collector_mock->expects( $this->never() )->method( 'collect' );
-
-		// Create controller mock with feature disabled.
-		$controller_mock = $this->createMock( FraudProtectionController::class );
-		$controller_mock->expects( $this->once() )
-			->method( 'feature_is_enabled' )
-			->willReturn( false );
-
-		// Create new dispatcher with feature disabled.
-		$sut = new FraudProtectionDispatcher();
-		$sut->init( $api_client_mock, $decision_handler_mock, $controller_mock, $data_collector_mock );
-
-		$event_type = 'test_event';
-		$event_data = array( 'product_id' => 123 );
-
-		// Call dispatch_event - should bail early without calling data collector, API or decision handler.
-		$sut->dispatch_event( $event_type, $event_data );
-	}
-
-	/**
-	 * Test that dispatch_event applies filter to collected data.
-	 */
-	public function test_dispatch_event_applies_filter_to_data(): void {
-		$event_type = 'test_event';
-		$event_data = array(
-			'foo' => 'bar',
-		);
-
-		$collected_data = array(
-			'session' => array( 'session_id' => 'test' ),
-			'foo'     => 'bar',
-		);
-
-		// Expect data collector to be called.
-		$this->data_collector_mock
-			->expects( $this->once() )
-			->method( 'collect' )
-			->with( $event_type, $event_data )
-			->willReturn( $collected_data );
-
-		// Add a filter that modifies the data.
-		add_filter(
-			'woocommerce_fraud_protection_event_data',
-			function ( $data, $type ) use ( $event_type ) {
-				$this->assertEquals( $event_type, $type );
-				$data['filtered'] = true;
-				return $data;
-			},
-			10,
-			2
-		);
-
-		// Expect API client to receive the filtered data.
-		$this->api_client_mock
-			->expects( $this->once() )
-			->method( 'send_event' )
-			->with(
-				$this->equalTo( $event_type ),
-				$this->callback(
-					function ( $data ) {
-						// Verify the original data is preserved.
-						$this->assertArrayHasKey( 'session', $data );
-						$this->assertEquals( 'test', $data['session']['session_id'] );
-						$this->assertEquals( 'bar', $data['foo'] );
-						// Verify the filter added the 'filtered' key.
-						$this->assertArrayHasKey( 'filtered', $data );
-						$this->assertTrue( $data['filtered'] );
-						return true;
-					}
-				)
-			)
-			->willReturn( ApiClient::DECISION_ALLOW );
-
-		$this->decision_handler_mock
-			->expects( $this->once() )
-			->method( 'apply_decision' );
-
-		// Call dispatch_event.
-		$this->sut->dispatch_event( $event_type, $event_data );
-
-		// Clean up filter.
-		remove_all_filters( 'woocommerce_fraud_protection_event_data' );
-	}
-}
diff --git a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/PaymentMethodEventTrackerTest.php b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/PaymentMethodEventTrackerTest.php
index eb32ea0450..e508862c7a 100644
--- a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/PaymentMethodEventTrackerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/PaymentMethodEventTrackerTest.php
@@ -10,15 +10,15 @@ declare( strict_types=1 );
 namespace Automattic\WooCommerce\Tests\Internal\FraudProtection;

 use Automattic\WooCommerce\Internal\FraudProtection\PaymentMethodEventTracker;
-use Automattic\WooCommerce\RestApi\UnitTests\LoggerSpyTrait;
+use Automattic\WooCommerce\Internal\FraudProtection\SessionDataCollector;

 /**
  * Tests for the PaymentMethodEventTracker class.
+ *
+ * @covers \Automattic\WooCommerce\Internal\FraudProtection\PaymentMethodEventTracker
  */
 class PaymentMethodEventTrackerTest extends \WC_Unit_Test_Case {

-	use LoggerSpyTrait;
-
 	/**
 	 * The System Under Test.
 	 *
@@ -27,11 +27,11 @@ class PaymentMethodEventTrackerTest extends \WC_Unit_Test_Case {
 	private $sut;

 	/**
-	 * Mock fraud protection dispatcher.
+	 * Mock session data collector.
 	 *
-	 * @var \Automattic\WooCommerce\Internal\FraudProtection\FraudProtectionDispatcher|\PHPUnit\Framework\MockObject\MockObject
+	 * @var SessionDataCollector|\PHPUnit\Framework\MockObject\MockObject
 	 */
-	private $mock_dispatcher;
+	private $mock_collector;

 	/**
 	 * Setup test.
@@ -39,52 +39,37 @@ class PaymentMethodEventTrackerTest extends \WC_Unit_Test_Case {
 	public function setUp(): void {
 		parent::setUp();

-		// Set jetpack_activation_source option to prevent "Cannot use bool as array" error
-		// in Jetpack Connection Manager's apply_activation_source_to_args method.
-		update_option( 'jetpack_activation_source', array( '', '' ) );
-
-		// Enable the fraud protection feature.
-		update_option( 'woocommerce_feature_fraud_protection_enabled', 'yes' );
+		// Create mock.
+		$this->mock_collector = $this->createMock( SessionDataCollector::class );

-		$container = wc_get_container();
-		$container->reset_all_resolved();
-
-		$this->sut = $container->get( PaymentMethodEventTracker::class );
+		// Create system under test with mock.
+		$this->sut = new PaymentMethodEventTracker();
+		$this->sut->init( $this->mock_collector );
 	}

 	/**
-	 * Test add payment method page loaded event tracking.
+	 * Test add payment method page loaded collects data.
 	 *
-	 * @testdox Should track add payment method page loaded event.
+	 * @testdox track_add_payment_method_page_loaded() collects session data with empty event data.
 	 */
-	public function test_track_add_payment_method_page_loaded_dispatches_event(): void {
-		// Create mock dispatcher.
-		$this->mock_dispatcher = $this->createMock( \Automattic\WooCommerce\Internal\FraudProtection\FraudProtectionDispatcher::class );
-
-		// Create system under test with mock dispatcher.
-		$sut = new PaymentMethodEventTracker();
-		$sut->init( $this->mock_dispatcher );
-
-		// Mock dispatcher to verify event is dispatched with empty event data.
-		$this->mock_dispatcher
+	public function test_track_add_payment_method_page_loaded_collects_data(): void {
+		$this->mock_collector
 			->expects( $this->once() )
-			->method( 'dispatch_event' )
+			->method( 'collect' )
 			->with(
 				$this->equalTo( 'add_payment_method_page_loaded' ),
 				$this->equalTo( array() )
 			);

-		// Call the method.
-		$sut->track_add_payment_method_page_loaded();
+		$this->sut->track_add_payment_method_page_loaded();
 	}

 	/**
-	 * Test payment method added event tracking.
+	 * Test payment method added collects data.
 	 *
-	 * @testdox Should track payment method added event.
+	 * @testdox track_payment_method_added() collects session data with token details.
 	 */
-	public function test_handle_payment_method_added(): void {
-
+	public function test_track_payment_method_added_collects_data(): void {
 		$user_id = $this->factory->user->create();

 		$token = new \WC_Payment_Token_CC();
@@ -97,37 +82,70 @@ class PaymentMethodEventTrackerTest extends \WC_Unit_Test_Case {
 		$token->set_user_id( $user_id );
 		$token->save();

-		// Verify that the event was sent to the API with correct payload.
-		$this->assertLogged(
-			'info',
-			'Sending fraud protection event: payment_method_added',
-			array(
-				'source'  => 'woo-fraud-protection',
-				'payload' => array(
-					'event_type' => 'payment_method_added',
-					'event_data' => array(
-						'action'     => 'added',
-						'token_id'   => $token->get_id(),
-						'gateway_id' => 'stripe',
-						'card_type'  => 'visa',
-						'card_last4' => '4242',
-					),
-				),
-			)
-		);
+		$this->mock_collector
+			->expects( $this->once() )
+			->method( 'collect' )
+			->with(
+				$this->equalTo( 'payment_method_added' ),
+				$this->callback(
+					function ( $event_data ) use ( $token ) {
+						$this->assertArrayHasKey( 'action', $event_data );
+						$this->assertEquals( 'added', $event_data['action'] );
+						$this->assertArrayHasKey( 'token_id', $event_data );
+						$this->assertEquals( $token->get_id(), $event_data['token_id'] );
+						$this->assertArrayHasKey( 'token_type', $event_data );
+						$this->assertArrayHasKey( 'gateway_id', $event_data );
+						$this->assertEquals( 'stripe', $event_data['gateway_id'] );
+						$this->assertArrayHasKey( 'card_type', $event_data );
+						$this->assertEquals( 'visa', $event_data['card_type'] );
+						$this->assertArrayHasKey( 'card_last4', $event_data );
+						$this->assertEquals( '4242', $event_data['card_last4'] );
+						return true;
+					}
+				)
+			);
+
+		$this->sut->track_payment_method_added( $token->get_id(), $token );
+
+		$token->delete();
 	}

 	/**
-	 * Cleanup after test.
+	 * Test payment method added includes expiry for CC tokens.
+	 *
+	 * @testdox track_payment_method_added() includes expiry info for CC tokens.
 	 */
-	public function tearDown(): void {
-		parent::tearDown();
+	public function test_track_payment_method_added_includes_expiry_for_cc_tokens(): void {
+		$user_id = $this->factory->user->create();
+
+		$token = new \WC_Payment_Token_CC();
+		$token->set_token( 'test_token_456' );
+		$token->set_gateway_id( 'stripe' );
+		$token->set_card_type( 'mastercard' );
+		$token->set_last4( '5555' );
+		$token->set_expiry_month( '06' );
+		$token->set_expiry_year( '2028' );
+		$token->set_user_id( $user_id );
+		$token->save();
+
+		$this->mock_collector
+			->expects( $this->once() )
+			->method( 'collect' )
+			->with(
+				$this->equalTo( 'payment_method_added' ),
+				$this->callback(
+					function ( $event_data ) {
+						$this->assertArrayHasKey( 'expiry_month', $event_data );
+						$this->assertEquals( '06', $event_data['expiry_month'] );
+						$this->assertArrayHasKey( 'expiry_year', $event_data );
+						$this->assertEquals( '2028', $event_data['expiry_year'] );
+						return true;
+					}
+				)
+			);

-		// Clean up options.
-		delete_option( 'woocommerce_feature_fraud_protection_enabled' );
-		delete_option( 'jetpack_activation_source' );
+		$this->sut->track_payment_method_added( $token->get_id(), $token );

-		// Reset container.
-		wc_get_container()->reset_all_resolved();
+		$token->delete();
 	}
 }
diff --git a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/SessionDataCollectorTest.php b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/SessionDataCollectorTest.php
index 1150eb8a34..f3e3d64f10 100644
--- a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/SessionDataCollectorTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/SessionDataCollectorTest.php
@@ -48,65 +48,106 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {

 		// Disable taxes before adding products to cart.
 		update_option( 'woocommerce_calc_taxes', 'no' );
+
+		// Clear any existing session data before each test.
+		WC()->session->set( 'fraud_protection_collected_data', null );
+	}
+
+	/**
+	 * Helper method to collect data and retrieve event from session.
+	 *
+	 * Events only contain: event_type, timestamp, event_data.
+	 * For full data (session, customer, order, addresses), use get_collected_data().
+	 *
+	 * @param string|null $event_type Optional event type.
+	 * @param array       $event_data Optional event data.
+	 * @return array The collected event data from session.
+	 */
+	private function collect_and_get_event( ?string $event_type = null, array $event_data = array() ): array {
+		$this->sut->collect( $event_type, $event_data );
+		$stored_data = WC()->session->get( 'fraud_protection_collected_data' );
+		return $stored_data[0] ?? array();
 	}

 	/**
-	 * Test that collect() method returns properly structured nested array with 9 top-level keys.
+	 * Helper method to collect data and retrieve full response via get_collected_data().
+	 *
+	 * Returns: wc_version, session, customer, shipping_address, billing_address, collected_events.
+	 *
+	 * @param string|null $event_type Optional event type.
+	 * @param array       $event_data Optional event data.
+	 * @return array The full collected data response.
 	 */
-	public function test_collect_returns_properly_structured_nested_array() {
-		$result = $this->sut->collect();
+	private function collect_and_get_data( ?string $event_type = null, array $event_data = array() ): array {
+		$this->sut->collect( $event_type, $event_data );
+		return $this->sut->get_collected_data();
+	}
+
+	/**
+	 * @testdox collect() stores properly structured event with 3 top-level keys.
+	 */
+	public function test_collect_stores_properly_structured_event(): void {
+		$event = $this->collect_and_get_event();
+
+		$this->assertIsArray( $event );
+		$this->assertArrayHasKey( 'event_type', $event );
+		$this->assertArrayHasKey( 'timestamp', $event );
+		$this->assertArrayHasKey( 'event_data', $event );
+		$this->assertCount( 3, $event );
+	}
+
+	/**
+	 * @testdox get_collected_data() returns properly structured response with 7 top-level keys.
+	 */
+	public function test_get_collected_data_returns_properly_structured_response(): void {
+		$result = $this->collect_and_get_data();

 		$this->assertIsArray( $result );
-		$this->assertArrayHasKey( 'event_type', $result );
-		$this->assertArrayHasKey( 'timestamp', $result );
 		$this->assertArrayHasKey( 'wc_version', $result );
 		$this->assertArrayHasKey( 'session', $result );
 		$this->assertArrayHasKey( 'customer', $result );
 		$this->assertArrayHasKey( 'order', $result );
 		$this->assertArrayHasKey( 'shipping_address', $result );
 		$this->assertArrayHasKey( 'billing_address', $result );
-		$this->assertArrayHasKey( 'event_data', $result );
-		$this->assertCount( 9, $result );
+		$this->assertArrayHasKey( 'collected_events', $result );
+		$this->assertCount( 7, $result );
 	}

 	/**
 	 * Test that collect() accepts event_type and event_data parameters.
 	 */
-	public function test_collect_accepts_event_type_and_event_data_parameters() {
+	public function test_collect_accepts_event_type_and_event_data_parameters(): void {
 		$event_type = 'checkout_started';
 		$event_data = array(
 			'page'   => 'checkout',
 			'source' => 'test',
 		);

-		$result = $this->sut->collect( $event_type, $event_data );
+		$event = $this->collect_and_get_event( $event_type, $event_data );

-		$this->assertEquals( $event_type, $result['event_type'] );
-		$this->assertEquals( $event_data, $result['event_data'] );
+		$this->assertEquals( $event_type, $event['event_type'] );
+		$this->assertEquals( $event_data, $event['event_data'] );
 	}

 	/**
-	 * Test graceful degradation when session is unavailable.
+	 * @testdox collect() degrades gracefully when session is unavailable.
 	 */
-	public function test_graceful_degradation_when_session_unavailable() {
+	public function test_graceful_degradation_when_session_unavailable(): void {
 		// This test verifies that collect() doesn't throw exceptions even if session is unavailable.
 		// We can't easily simulate session being unavailable in unit tests without mocking,
-		// but we can verify that calling collect() returns a valid structure.
-		$result = $this->sut->collect();
+		// but we can verify that calling collect() stores valid event structure.
+		$event = $this->collect_and_get_event();

-		$this->assertIsArray( $result );
-		$this->assertCount( 9, $result );
-		// All sections should be initialized even if session unavailable.
-		$this->assertIsArray( $result['session'] );
-		$this->assertIsArray( $result['customer'] );
-		$this->assertIsArray( $result['order'] );
+		$this->assertIsArray( $event );
+		$this->assertCount( 3, $event );
 	}

 	/**
-	 * Test wc_version field is included in collected data.
+	 * Test wc_version field is included in get_collected_data response.
 	 */
-	public function test_wc_version_is_included() {
-		$result = $this->sut->collect();
+	public function test_wc_version_is_included(): void {
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data();

 		$this->assertEquals( WC()->version, $result['wc_version'] );
 	}
@@ -114,17 +155,17 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 	/**
 	 * Test timestamp format is UTC (gmdate format).
 	 */
-	public function test_timestamp_format_is_utc() {
-		$result = $this->sut->collect();
+	public function test_timestamp_format_is_utc(): void {
+		$event = $this->collect_and_get_event();

-		$this->assertArrayHasKey( 'timestamp', $result );
-		$this->assertNotEmpty( $result['timestamp'] );
+		$this->assertArrayHasKey( 'timestamp', $event );
+		$this->assertNotEmpty( $event['timestamp'] );

 		// Verify timestamp is in Y-m-d H:i:s format.
-		$this->assertMatchesRegularExpression( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $result['timestamp'] );
+		$this->assertMatchesRegularExpression( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $event['timestamp'] );

 		// Verify timestamp is recent (within last 10 seconds).
-		$timestamp       = strtotime( $result['timestamp'] );
+		$timestamp       = strtotime( $event['timestamp'] );
 		$current_time    = time();
 		$time_difference = abs( $current_time - $timestamp );
 		$this->assertLessThanOrEqual( 10, $time_difference, 'Timestamp should be recent (within 10 seconds)' );
@@ -133,31 +174,35 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 	/**
 	 * Test that collect() uses default values when parameters not provided.
 	 */
-	public function test_collect_uses_default_values_when_parameters_not_provided() {
-		$result = $this->sut->collect();
+	public function test_collect_uses_default_values_when_parameters_not_provided(): void {
+		$event = $this->collect_and_get_event();

-		$this->assertNull( $result['event_type'] );
-		$this->assertEquals( array(), $result['event_data'] );
+		$this->assertNull( $event['event_type'] );
+		$this->assertEquals( array(), $event['event_data'] );
 	}

 	/**
-	 * Test that nested sections are initialized as arrays.
+	 * @testdox Nested sections are initialized as arrays.
 	 */
-	public function test_nested_sections_initialized_as_arrays() {
-		$result = $this->sut->collect();
+	public function test_nested_sections_initialized_as_arrays(): void {
+		$result = $this->collect_and_get_data();

 		$this->assertIsArray( $result['session'] );
 		$this->assertIsArray( $result['customer'] );
 		$this->assertIsArray( $result['order'] );
 		$this->assertIsArray( $result['shipping_address'] );
 		$this->assertIsArray( $result['billing_address'] );
+		$this->assertIsArray( $result['collected_events'] );
+
+		$this->assertCount( 1, $result['collected_events'] );
 	}

 	/**
 	 * Test session data includes all 6 required fields.
 	 */
-	public function test_session_data_includes_all_required_fields() {
-		$result = $this->sut->collect();
+	public function test_session_data_includes_all_required_fields(): void {
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data();

 		$this->assertIsArray( $result['session'] );
 		$this->assertArrayHasKey( 'session_id', $result['session'] );
@@ -171,8 +216,9 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 	/**
 	 * Test session_id is retrieved from SessionClearanceManager.
 	 */
-	public function test_session_id_retrieved_from_session_clearance_manager() {
-		$result = $this->sut->collect();
+	public function test_session_id_retrieved_from_session_clearance_manager(): void {
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data();

 		$this->assertArrayHasKey( 'session_id', $result['session'] );
 		// Session ID should be a string when session is available.
@@ -185,7 +231,7 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 	/**
 	 * Test email collection fallback chain for logged-in user.
 	 */
-	public function test_email_collection_for_logged_in_user() {
+	public function test_email_collection_for_logged_in_user(): void {
 		// Create a test user and log them in.
 		$user_id = $this->factory->user->create(
 			array(
@@ -194,7 +240,8 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 		);
 		wp_set_current_user( $user_id );

-		$result = $this->sut->collect();
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data();

 		$this->assertArrayHasKey( 'email', $result['session'] );
 		$this->assertEquals( 'testuser@example.com', $result['session']['email'] );
@@ -203,7 +250,7 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 	/**
 	 * Test email collection from WC_Customer when user not logged in.
 	 */
-	public function test_email_collection_from_wc_customer() {
+	public function test_email_collection_from_wc_customer(): void {
 		// Ensure no user is logged in.
 		wp_set_current_user( 0 );

@@ -212,7 +259,8 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 			WC()->customer->set_billing_email( 'customer@example.com' );
 		}

-		$result = $this->sut->collect();
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data();

 		$this->assertArrayHasKey( 'email', $result['session'] );
 		// Email should be from customer object if available.
@@ -224,8 +272,9 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 	/**
 	 * Test customer data includes all 4 required fields.
 	 */
-	public function test_customer_data_includes_all_required_fields() {
-		$result = $this->sut->collect();
+	public function test_customer_data_includes_all_required_fields(): void {
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data();

 		$this->assertIsArray( $result['customer'] );
 		$this->assertArrayHasKey( 'first_name', $result['customer'] );
@@ -237,13 +286,14 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 	/**
 	 * Test customer name collection from WC_Customer.
 	 */
-	public function test_customer_name_collection_from_wc_customer() {
+	public function test_customer_name_collection_from_wc_customer(): void {
 		if ( isset( WC()->customer ) ) {
 			WC()->customer->set_billing_first_name( 'John' );
 			WC()->customer->set_billing_last_name( 'Doe' );
 		}

-		$result = $this->sut->collect();
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data();

 		$this->assertArrayHasKey( 'first_name', $result['customer'] );
 		$this->assertArrayHasKey( 'last_name', $result['customer'] );
@@ -257,7 +307,7 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 	/**
 	 * Test customer data fallback to session when WC_Customer not available.
 	 */
-	public function test_customer_data_fallback_to_session() {
+	public function test_customer_data_fallback_to_session(): void {
 		// Ensure no user is logged in.
 		wp_set_current_user( 0 );

@@ -277,7 +327,8 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 		$original_customer = WC()->customer;
 		WC()->customer     = null;

-		$result = $this->sut->collect();
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data();

 		// Restore original customer.
 		WC()->customer = $original_customer;
@@ -293,7 +344,7 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 	/**
 	 * Test lifetime_order_count field exists and uses WC_Customer::get_order_count().
 	 */
-	public function test_lifetime_order_count_for_registered_customer() {
+	public function test_lifetime_order_count_for_registered_customer(): void {
 		// Create a test user.
 		$user_id = $this->factory->user->create(
 			array(
@@ -310,7 +361,8 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 		WC()->customer->set_billing_last_name( 'Doe' );
 		WC()->customer->set_billing_email( 'customer@example.com' );

-		$result = $this->sut->collect();
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data();

 		// Verify lifetime_order_count field exists and returns a valid integer.
 		// In test environment, the method returns 0 because the cache is not automatically
@@ -323,7 +375,7 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 	/**
 	 * Test graceful degradation when customer data unavailable.
 	 */
-	public function test_graceful_degradation_when_customer_data_unavailable() {
+	public function test_graceful_degradation_when_customer_data_unavailable(): void {
 		// Ensure no user is logged in.
 		wp_set_current_user( 0 );

@@ -334,7 +386,8 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 			WC()->customer->set_billing_email( '' );
 		}

-		$result = $this->sut->collect();
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data();

 		// Should return customer section with fields, even if empty/null.
 		$this->assertIsArray( $result['customer'] );
@@ -345,14 +398,17 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 	}

 	/**
-	 * Test order data includes all required fields with proper structure.
+	 * @testdox Order data includes all required fields with proper structure when order_id is provided.
 	 */
-	public function test_order_data_includes_all_required_fields() {
-		// Add a product to cart.
+	public function test_order_data_includes_all_required_fields(): void {
 		$product = \WC_Helper_Product::create_simple_product();
 		WC()->cart->add_to_cart( $product->get_id(), 1 );

-		$result = $this->sut->collect();
+		$order = wc_create_order();
+		$order->save();
+
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data( $order->get_id() );

 		$this->assertIsArray( $result['order'] );
 		$this->assertArrayHasKey( 'order_id', $result['order'] );
@@ -370,13 +426,11 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 	}

 	/**
-	 * Test order totals are collected from cart.
+	 * @testdox Order totals are collected from cart when order_id is provided.
 	 */
-	public function test_order_totals_collected_from_cart() {
-		// Empty cart first to ensure clean state.
+	public function test_order_totals_collected_from_cart(): void {
 		WC()->cart->empty_cart();

-		// Add a product to cart.
 		$product = \WC_Helper_Product::create_simple_product();
 		$product->set_regular_price( 50.00 );
 		$product->save();
@@ -384,39 +438,42 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 		WC()->cart->add_to_cart( $product->get_id(), 2 );
 		WC()->cart->calculate_totals();

-		$result = $this->sut->collect();
+		$order = wc_create_order();
+		$order->save();
+
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data( $order->get_id() );

 		$this->assertArrayHasKey( 'items_total', $result['order'] );
 		$this->assertArrayHasKey( 'total', $result['order'] );
-		// Verify items_total matches expected value.
 		$this->assertEquals( 100.00, $result['order']['items_total'] );
 	}

 	/**
-	 * Test shipping_tax_rate calculation.
+	 * @testdox Shipping tax rate is calculated correctly when order_id is provided.
 	 */
-	public function test_shipping_tax_rate_calculation() {
-		// Add a product to cart.
+	public function test_shipping_tax_rate_calculation(): void {
 		$product = \WC_Helper_Product::create_simple_product();
 		WC()->cart->add_to_cart( $product->get_id(), 1 );

-		$result = $this->sut->collect();
+		$order = wc_create_order();
+		$order->save();
+
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data( $order->get_id() );

 		$this->assertArrayHasKey( 'shipping_tax_rate', $result['order'] );
-		// When shipping total is zero, shipping_tax_rate should be null.
 		if ( 0 === (float) $result['order']['shipping_total'] ) {
 			$this->assertNull( $result['order']['shipping_tax_rate'] );
 		}
 	}

 	/**
-	 * Test cart item data includes all 12 required fields.
+	 * @testdox Cart item data includes all 12 required fields when order_id is provided.
 	 */
-	public function test_cart_item_includes_all_required_fields() {
-		// Empty cart first to ensure clean state.
+	public function test_cart_item_includes_all_required_fields(): void {
 		WC()->cart->empty_cart();

-		// Add a product to cart.
 		$product = \WC_Helper_Product::create_simple_product();
 		$product->set_name( 'Test Product' );
 		$product->set_description( 'Test product description' );
@@ -426,7 +483,11 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {

 		WC()->cart->add_to_cart( $product->get_id(), 2 );

-		$result = $this->sut->collect();
+		$order = wc_create_order();
+		$order->save();
+
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data( $order->get_id() );

 		$this->assertArrayHasKey( 'items', $result['order'] );
 		$this->assertIsArray( $result['order']['items'] );
@@ -446,7 +507,6 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 		$this->assertArrayHasKey( 'is_downloadable', $item );
 		$this->assertArrayHasKey( 'attributes', $item );

-		// Verify values match product data.
 		$this->assertEquals( 'Test Product', $item['name'] );
 		$this->assertEquals( 'Test product description', $item['description'] );
 		$this->assertEquals( 'TEST-SKU-123', $item['sku'] );
@@ -457,7 +517,7 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 	/**
 	 * Test billing address includes all required fields.
 	 */
-	public function test_billing_address_includes_all_required_fields() {
+	public function test_billing_address_includes_all_required_fields(): void {
 		// Set billing address data.
 		if ( isset( WC()->customer ) ) {
 			WC()->customer->set_billing_address_1( '123 Main St' );
@@ -468,7 +528,8 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 			WC()->customer->set_billing_postcode( '10001' );
 		}

-		$result = $this->sut->collect();
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data();

 		$this->assertIsArray( $result['billing_address'] );
 		$this->assertArrayHasKey( 'address_1', $result['billing_address'] );
@@ -492,7 +553,7 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 	/**
 	 * Test shipping address includes all required fields.
 	 */
-	public function test_shipping_address_includes_all_required_fields() {
+	public function test_shipping_address_includes_all_required_fields(): void {
 		// Set shipping address data.
 		if ( isset( WC()->customer ) ) {
 			WC()->customer->set_shipping_address_1( '456 Oak Ave' );
@@ -503,7 +564,8 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 			WC()->customer->set_shipping_postcode( '90001' );
 		}

-		$result = $this->sut->collect();
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data();

 		$this->assertIsArray( $result['shipping_address'] );
 		$this->assertArrayHasKey( 'address_1', $result['shipping_address'] );
@@ -525,50 +587,51 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 	}

 	/**
-	 * Test graceful degradation when cart is empty.
+	 * @testdox Order data degrades gracefully when cart is empty and order_id is provided.
 	 */
-	public function test_graceful_degradation_when_cart_is_empty() {
-		// Ensure cart is empty.
+	public function test_graceful_degradation_when_cart_is_empty(): void {
 		WC()->cart->empty_cart();

-		$result = $this->sut->collect();
+		$order = wc_create_order();
+		$order->save();
+
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data( $order->get_id() );

-		// Order section should still exist even with empty cart.
 		$this->assertIsArray( $result['order'] );
 		$this->assertArrayHasKey( 'items', $result['order'] );
 		$this->assertIsArray( $result['order']['items'] );
 		$this->assertEmpty( $result['order']['items'] );

-		// Totals should be zero or null.
 		$this->assertEquals( 0, $result['order']['items_total'] );
 		$this->assertEquals( 0, $result['order']['total'] );
 	}

 	/**
-	 * Test customer_id for guest users.
+	 * @testdox customer_id is set to 'guest' for guest users when order_id is provided.
 	 */
-	public function test_customer_id_for_guest_users() {
-		// Ensure no user is logged in.
+	public function test_customer_id_for_guest_users(): void {
 		wp_set_current_user( 0 );

-		// Reinitialize customer as guest (ID will be 0).
 		WC()->customer = new \WC_Customer( 0, true );

-		// Add a product to cart.
 		$product = \WC_Helper_Product::create_simple_product();
 		WC()->cart->add_to_cart( $product->get_id(), 1 );

-		$result = $this->sut->collect();
+		$order = wc_create_order();
+		$order->save();
+
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data( $order->get_id() );

 		$this->assertArrayHasKey( 'customer_id', $result['order'] );
 		$this->assertEquals( 'guest', $result['order']['customer_id'] );
 	}

 	/**
-	 * Test customer_id for logged-in users.
+	 * @testdox customer_id is set to user ID for logged-in users when order_id is provided.
 	 */
-	public function test_customer_id_for_logged_in_users() {
-		// Create a test user and log them in.
+	public function test_customer_id_for_logged_in_users(): void {
 		$user_id = $this->factory->user->create(
 			array(
 				'user_email' => 'logged-in-user@example.com',
@@ -576,24 +639,25 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 		);
 		wp_set_current_user( $user_id );

-		// Reinitialize customer with logged-in user.
 		WC()->customer = new \WC_Customer( $user_id, true );

-		// Add a product to cart.
 		$product = \WC_Helper_Product::create_simple_product();
 		WC()->cart->add_to_cart( $product->get_id(), 1 );

-		$result = $this->sut->collect();
+		$order = wc_create_order();
+		$order->save();
+
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data( $order->get_id() );

 		$this->assertArrayHasKey( 'customer_id', $result['order'] );
 		$this->assertEquals( $user_id, $result['order']['customer_id'] );
 	}

 	/**
-	 * Test complete collect() output includes all 8 top-level sections with data.
+	 * @testdox get_collected_data() output includes all 7 top-level sections with data.
 	 */
-	public function test_complete_collect_output_includes_all_sections() {
-		// Create a logged-in user.
+	public function test_complete_collect_output_includes_all_sections(): void {
 		$user_id = $this->factory->user->create(
 			array(
 				'user_email' => 'complete-test@example.com',
@@ -601,7 +665,6 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 		);
 		wp_set_current_user( $user_id );

-		// Set customer data.
 		if ( isset( WC()->customer ) ) {
 			WC()->customer->set_billing_first_name( 'Test' );
 			WC()->customer->set_billing_last_name( 'User' );
@@ -611,41 +674,44 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 			WC()->customer->set_billing_country( 'US' );
 		}

-		// Add a product to cart.
 		$product = \WC_Helper_Product::create_simple_product();
 		WC()->cart->add_to_cart( $product->get_id(), 1 );

-		$result = $this->sut->collect( 'checkout_started', array( 'test' => 'data' ) );
+		$order = wc_create_order();
+		$order->save();

-		// Verify all 8 sections exist.
-		$this->assertArrayHasKey( 'event_type', $result );
-		$this->assertArrayHasKey( 'timestamp', $result );
+		$this->sut->collect( 'checkout_started', array( 'test' => 'data' ) );
+		$result = $this->sut->get_collected_data( $order->get_id() );
+
+		$this->assertArrayHasKey( 'wc_version', $result );
 		$this->assertArrayHasKey( 'session', $result );
 		$this->assertArrayHasKey( 'customer', $result );
 		$this->assertArrayHasKey( 'order', $result );
 		$this->assertArrayHasKey( 'shipping_address', $result );
 		$this->assertArrayHasKey( 'billing_address', $result );
-		$this->assertArrayHasKey( 'event_data', $result );
+		$this->assertArrayHasKey( 'collected_events', $result );

-		// Verify sections contain expected data types.
-		$this->assertEquals( 'checkout_started', $result['event_type'] );
-		$this->assertIsString( $result['timestamp'] );
+		$this->assertIsString( $result['wc_version'] );
 		$this->assertIsArray( $result['session'] );
 		$this->assertIsArray( $result['customer'] );
 		$this->assertIsArray( $result['order'] );
 		$this->assertIsArray( $result['shipping_address'] );
 		$this->assertIsArray( $result['billing_address'] );
-		$this->assertEquals( array( 'test' => 'data' ), $result['event_data'] );
+		$this->assertIsArray( $result['collected_events'] );
+
+		$this->assertCount( 1, $result['collected_events'] );
+		$event = $result['collected_events'][0];
+		$this->assertEquals( 'checkout_started', $event['event_type'] );
+		$this->assertIsString( $event['timestamp'] );
+		$this->assertEquals( array( 'test' => 'data' ), $event['event_data'] );
 	}

 	/**
-	 * Test end-to-end data collection with full cart scenario.
+	 * @testdox End-to-end data collection with full cart scenario works correctly.
 	 */
-	public function test_end_to_end_data_collection_with_full_cart() {
-		// Empty cart first.
+	public function test_end_to_end_data_collection_with_full_cart(): void {
 		WC()->cart->empty_cart();

-		// Create logged-in user.
 		$user_id = $this->factory->user->create(
 			array(
 				'user_email' => 'e2e-test@example.com',
@@ -653,13 +719,11 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 		);
 		wp_set_current_user( $user_id );

-		// Create completed order for lifetime count.
-		$order = wc_create_order();
-		$order->set_customer_id( $user_id );
-		$order->set_status( 'completed' );
-		$order->save();
+		$existing_order = wc_create_order();
+		$existing_order->set_customer_id( $user_id );
+		$existing_order->set_status( 'completed' );
+		$existing_order->save();

-		// Set customer data.
 		if ( isset( WC()->customer ) ) {
 			WC()->customer = new \WC_Customer( $user_id, true );
 			WC()->customer->set_billing_first_name( 'John' );
@@ -678,7 +742,6 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 			WC()->customer->set_shipping_postcode( '10001' );
 		}

-		// Add products to cart.
 		$product1 = \WC_Helper_Product::create_simple_product();
 		$product1->set_name( 'Product 1' );
 		$product1->set_regular_price( 100.00 );
@@ -693,94 +756,96 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 		WC()->cart->add_to_cart( $product2->get_id(), 1 );
 		WC()->cart->calculate_totals();

-		// Collect data.
-		$result = $this->sut->collect( 'payment_attempt', array( 'gateway' => 'stripe' ) );
+		$new_order = wc_create_order();
+		$new_order->save();

-		// Verify comprehensive data collection.
-		$this->assertEquals( 'payment_attempt', $result['event_type'] );
-		$this->assertNotEmpty( $result['timestamp'] );
+		$this->sut->collect( 'payment_attempt', array( 'gateway' => 'stripe' ) );
+		$result = $this->sut->get_collected_data( $new_order->get_id() );
+
+		$this->assertArrayHasKey( 'wc_version', $result );
+		$this->assertArrayHasKey( 'collected_events', $result );
+		$this->assertCount( 1, $result['collected_events'] );
+
+		$event = $result['collected_events'][0];
+
+		$this->assertEquals( 'payment_attempt', $event['event_type'] );
+		$this->assertNotEmpty( $event['timestamp'] );

-		// Session data.
 		$this->assertNotEmpty( $result['session']['session_id'] );
 		$this->assertEquals( 'e2e-test@example.com', $result['session']['email'] );

-		// Customer data.
 		$this->assertEquals( 'John', $result['customer']['first_name'] );
 		$this->assertEquals( 'Doe', $result['customer']['last_name'] );
-		// Lifetime order count will be >= 0 (depends on WC_Customer::get_order_count() availability).
 		$this->assertIsInt( $result['customer']['lifetime_order_count'] );
 		$this->assertGreaterThanOrEqual( 0, $result['customer']['lifetime_order_count'] );

-		// Order data.
 		$this->assertGreaterThan( 0, $result['order']['total'] );
 		$this->assertCount( 2, $result['order']['items'] );

-		// Billing address.
 		$this->assertEquals( '123 Test St', $result['billing_address']['address_1'] );
 		$this->assertEquals( 'Test City', $result['billing_address']['city'] );

-		// Shipping address.
 		$this->assertEquals( '456 Ship St', $result['shipping_address']['address_1'] );
 		$this->assertEquals( 'Ship City', $result['shipping_address']['city'] );

-		// Event data.
-		$this->assertEquals( array( 'gateway' => 'stripe' ), $result['event_data'] );
+		$this->assertEquals( array( 'gateway' => 'stripe' ), $event['event_data'] );
 	}

 	/**
-	 * Test graceful degradation across all sections.
+	 * @testdox Graceful degradation across all sections when data is minimal.
 	 */
-	public function test_graceful_degradation_across_all_sections() {
-		// Ensure no user logged in.
+	public function test_graceful_degradation_across_all_sections(): void {
 		wp_set_current_user( 0 );

-		// Reinitialize customer as guest (ID will be 0).
 		WC()->customer = new \WC_Customer( 0, true );

-		// Empty cart.
 		WC()->cart->empty_cart();

-		// Clear customer data.
 		if ( isset( WC()->customer ) ) {
 			WC()->customer->set_billing_first_name( '' );
 			WC()->customer->set_billing_last_name( '' );
 			WC()->customer->set_billing_email( '' );
 		}

-		// Collect should still succeed and return valid structure.
-		$result = $this->sut->collect();
+		$order = wc_create_order();
+		$order->save();
+
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data( $order->get_id() );

-		// Verify structure is intact even with minimal data.
 		$this->assertIsArray( $result );
-		$this->assertCount( 9, $result );
+		$this->assertCount( 7, $result );

-		// All sections should be arrays.
 		$this->assertIsArray( $result['session'] );
 		$this->assertIsArray( $result['customer'] );
 		$this->assertIsArray( $result['order'] );
 		$this->assertIsArray( $result['shipping_address'] );
 		$this->assertIsArray( $result['billing_address'] );
+		$this->assertIsArray( $result['collected_events'] );
+
+		$this->assertCount( 1, $result['collected_events'] );

-		// Key fields should have appropriate defaults.
 		$this->assertEquals( 'guest', $result['order']['customer_id'] );
 		$this->assertEquals( 0, $result['customer']['lifetime_order_count'] );
 		$this->assertEmpty( $result['order']['items'] );
 	}

 	/**
-	 * Test manual triggering only (no automatic hooks).
+	 * @testdox Data collection requires manual triggering (no automatic hooks).
 	 */
-	public function test_manual_triggering_only() {
+	public function test_manual_triggering_only(): void {
 		// This test verifies that SessionDataCollector doesn't automatically
 		// hook into WooCommerce events. It should only collect data when
 		// collect() is explicitly called.

-		// Add a product to cart (should not trigger automatic collection).
 		$product = \WC_Helper_Product::create_simple_product();
 		WC()->cart->add_to_cart( $product->get_id(), 1 );

-		// Verify collect() must be called manually.
-		$result = $this->sut->collect();
+		$order = wc_create_order();
+		$order->save();
+
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data( $order->get_id() );

 		$this->assertIsArray( $result );
 		$this->assertCount( 1, $result['order']['items'] );
@@ -788,4 +853,118 @@ class SessionDataCollectorTest extends \WC_Unit_Test_Case {
 		// No automatic data collection should have occurred.
 		// This is a design verification test - the class should not register hooks.
 	}
+
+	/**
+	 * Test collect stores event data in session.
+	 *
+	 * @testdox collect() stores event data in WooCommerce session under 'fraud_protection_collected_data' key.
+	 */
+	public function test_collect_stores_event_data_in_session(): void {
+		// Collect data with a specific event type.
+		$this->sut->collect( 'cart_page_loaded', array( 'source' => 'test' ) );
+
+		// Verify data was stored in session.
+		$stored_data = WC()->session->get( 'fraud_protection_collected_data' );
+
+		$this->assertIsArray( $stored_data );
+		$this->assertCount( 1, $stored_data );
+		$this->assertEquals( 'cart_page_loaded', $stored_data[0]['event_type'] );
+		$this->assertEquals( array( 'source' => 'test' ), $stored_data[0]['event_data'] );
+	}
+
+	/**
+	 * Test multiple collect calls append data to session.
+	 *
+	 * @testdox Multiple collect() calls append data to session array, preserving event history.
+	 */
+	public function test_multiple_collect_calls_append_data_to_session(): void {
+		// First collect call.
+		$this->sut->collect( 'cart_page_loaded', array() );
+
+		// Second collect call.
+		$this->sut->collect( 'checkout_page_loaded', array() );
+
+		// Third collect call.
+		$this->sut->collect( 'order_placed', array( 'order_id' => 123 ) );
+
+		// Verify all three events are stored.
+		$stored_data = WC()->session->get( 'fraud_protection_collected_data' );
+
+		$this->assertIsArray( $stored_data );
+		$this->assertCount( 3, $stored_data );
+		$this->assertEquals( 'cart_page_loaded', $stored_data[0]['event_type'] );
+		$this->assertEquals( 'checkout_page_loaded', $stored_data[1]['event_type'] );
+		$this->assertEquals( 'order_placed', $stored_data[2]['event_type'] );
+		$this->assertEquals( 123, $stored_data[2]['event_data']['order_id'] );
+	}
+
+	/**
+	 * Test get_collected_data returns structure with empty collected_events when no data collected.
+	 *
+	 * @testdox get_collected_data() returns structure with empty collected_events when no data has been collected.
+	 */
+	public function test_get_collected_data_returns_empty_collected_events_when_no_data_collected(): void {
+		$result = $this->sut->get_collected_data();
+
+		$this->assertIsArray( $result );
+		$this->assertArrayHasKey( 'collected_events', $result );
+		$this->assertEmpty( $result['collected_events'] );
+	}
+
+	/**
+	 * Test get_collected_data returns structure with empty collected_events when session unavailable.
+	 *
+	 * @testdox get_collected_data() returns structure with empty collected_events when session is unavailable.
+	 */
+	public function test_get_collected_data_returns_empty_collected_events_when_session_unavailable(): void {
+		// Store original session.
+		$original_session = WC()->session;
+
+		// Set session to null to simulate unavailability.
+		WC()->session = null;
+
+		$result = $this->sut->get_collected_data();
+
+		// Restore original session.
+		WC()->session = $original_session;
+
+		$this->assertIsArray( $result );
+		$this->assertArrayHasKey( 'collected_events', $result );
+		$this->assertEmpty( $result['collected_events'] );
+	}
+
+	/**
+	 * @testdox get_collected_data() returns empty order array when no order_id is provided.
+	 */
+	public function test_get_collected_data_returns_empty_order_when_no_order_id(): void {
+		$product = \WC_Helper_Product::create_simple_product();
+		WC()->cart->add_to_cart( $product->get_id(), 1 );
+
+		$this->sut->collect();
+		$result = $this->sut->get_collected_data();
+
+		$this->assertArrayHasKey( 'order', $result );
+		$this->assertIsArray( $result['order'] );
+		$this->assertEmpty( $result['order'] );
+	}
+
+	/**
+	 * @testdox get_collected_data() returns collected_events array after collect() is called.
+	 */
+	public function test_get_collected_data_returns_data_after_collect(): void {
+		// Collect some data.
+		$this->sut->collect( 'cart_page_loaded', array( 'source' => 'test' ) );
+		$this->sut->collect( 'checkout_started', array( 'gateway' => 'stripe' ) );
+
+		// Get collected data using the new method.
+		$result = $this->sut->get_collected_data();
+
+		$this->assertIsArray( $result );
+		$this->assertArrayHasKey( 'collected_events', $result );
+		$this->assertCount( 2, $result['collected_events'] );
+		$this->assertEquals( 'cart_page_loaded', $result['collected_events'][0]['event_type'] );
+		$this->assertEquals( array( 'source' => 'test' ), $result['collected_events'][0]['event_data'] );
+		$this->assertEquals( 'checkout_started', $result['collected_events'][1]['event_type'] );
+		$this->assertEquals( array( 'gateway' => 'stripe' ), $result['collected_events'][1]['event_data'] );
+	}
 }