Commit c589c101dc for woocommerce

commit c589c101dc95e703dbf670024b60c73e5602331a
Author: Leonardo Lopes de Albuquerque <leonardo.albuquerque@automattic.com>
Date:   Sat Jan 10 17:56:39 2026 -0300

    [Fraud Protection] Add EventDispatcher to send data to WPCom api and trigger the decision handler (#62688)

    * Add SessionClearanceManager for fraud protection

    Implements session status management for the WooCommerce fraud protection
    feature. This class tracks three session states (pending, allowed, blocked)
    and provides the foundation for fraud detection decision enforcement.

    Key features:
    - Session status management (pending, allowed, blocked)
    - Cart emptying on blocked sessions
    - Logging via FraudProtectionController helper
    - DEFAULT_STATUS constant for consistent default behavior
    - Comprehensive unit tests (9 tests, 20 assertions)

    Closes WOOSUBS-1246

    🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

    * Address coderabbit comments

    * fix lint error

    * Address phpstan error

    * Add SessionDataCollector foundation class and tests

    Create SessionDataCollector class with basic structure to collect session data for fraud protection. This initial implementation establishes the class foundation with proper nested data structure, dependency injection, and comprehensive unit tests.

    - Add SessionDataCollector class with collect() method
    - Implement nested data structure with 9 top-level sections
    - Add dependency injection for SessionClearanceManager
    - Add UTC timestamp generation
    - Create 6 focused unit tests covering foundation requirements
    - Follow WooCommerce coding standards and patterns

    Part of Task Group 1 for WOOSUBS-1248 (Session Data Collection Framework).

    * Improve SessionDataCollector dependency injection and data collection

    - Update to use init() pattern instead of constructor for dependency injection (follows WooCommerce standards)
    - Add session data fallback for customer data collection (first_name, last_name, billing_email)
    - Reorder email fallback chain to check WC_Customer before session data
    - Add test for customer data session fallback
    - Fix PHPCS issues: replace short ternaries, fix array/variable alignment, remove trailing whitespace

    All tests passing (15 tests, 64 assertions).

    * Implement order, cart, and address data collection (Task Group 3)

    Add comprehensive order, cart item, and address data collection methods to SessionDataCollector with multiple improvements based on WooCommerce best practices.

    Implementation:
    - Add get_order_data() method collecting 11 order fields including totals, currency, and cart hash
    - Add get_cart_items() method extracting 12 detailed fields per cart item
    - Add get_billing_address() and get_shipping_address() methods with 6 fields each
    - Add get_product_category_names() helper for cleaner category extraction
    - Update collect() method to populate order and address sections

    Key improvements:
    - Use WC()->customer->get_id() for customer_id instead of user login check (proper WooCommerce abstraction)
    - Use get_subtotal() for items_total (before discounts) instead of get_cart_contents_total() (after discounts)
    - Use get_total('edit') for total instead of manual calculation (more reliable)
    - Use wc_get_product_terms() for categories with caching (WooCommerce helper vs WordPress get_term)
    - Return all product categories as comma-separated list (better fraud detection data)
    - Extract category logic to reusable helper method (cleaner code)

    Testing:
    - Add 9 new focused tests for order, cart, and address data (24 total tests, 137 assertions)
    - Fix cart state isolation between tests (add empty_cart() calls)
    - Add customer reinitialization in logged-in user test
    - All tests passing

    Part of Task Group 3 for WOOSUBS-1248 (Session Data Collection Framework).

    * Add payment data structure and complete SessionDataCollector (Task Group 4)

    Complete SessionDataCollector implementation with payment data structure and comprehensive integration testing. All data collection methods now implemented with graceful degradation throughout.

    Implementation:
    - Add get_payment_data() method with 11 payment fields (gateway name/type populated, card details null for future gateway extensions)
    - Add get_product_category_names() helper for cleaner category extraction
    - Update collect() to populate payment section and extract order_id from event_data
    - Update get_order_data() to accept order_id parameter from event_data

    Integration testing:
    - Add 6 comprehensive integration tests (35 total tests, 202 assertions)
    - Test payment data structure with all 11 required fields
    - Test complete collect() output with all 9 sections
    - Test end-to-end data collection with full cart scenario
    - Test graceful degradation across all sections (empty cart, no user, minimal data)
    - Test manual triggering verification
    - Fix graceful degradation test to properly initialize guest customer

    Data collection improvements:
    - Order ID now extracted from event_data when provided by calling code
    - Category extraction uses wc_get_product_terms() with caching (WooCommerce helper)
    - Returns all product categories as comma-separated list (better fraud detection data)
    - Comprehensive PHPDoc comments throughout

    All acceptance criteria met:
    - Payment structure complete with gateway extension placeholders
    - All 9 sections implemented and tested
    - Graceful degradation verified end-to-end
    - Manual triggering only (no automatic hooks in SessionDataCollector)
    - All tests passing (35 tests, 202 assertions)

    Part of Task Group 4 for WOOSUBS-1248 (Session Data Collection Framework).

    * Added Cart Event Tracker class to capture cart events

    * Added test for the CartEventTracker class

    * Add SessionDataCollector foundation class and tests

    Create SessionDataCollector class with basic structure to collect session data for fraud protection. This initial implementation establishes the class foundation with proper nested data structure, dependency injection, and comprehensive unit tests.

    - Add SessionDataCollector class with collect() method
    - Implement nested data structure with 9 top-level sections
    - Add dependency injection for SessionClearanceManager
    - Add UTC timestamp generation
    - Create 6 focused unit tests covering foundation requirements
    - Follow WooCommerce coding standards and patterns

    Part of Task Group 1 for WOOSUBS-1248 (Session Data Collection Framework).

    * Improve SessionDataCollector dependency injection and data collection

    - Update to use init() pattern instead of constructor for dependency injection (follows WooCommerce standards)
    - Add session data fallback for customer data collection (first_name, last_name, billing_email)
    - Reorder email fallback chain to check WC_Customer before session data
    - Add test for customer data session fallback
    - Fix PHPCS issues: replace short ternaries, fix array/variable alignment, remove trailing whitespace

    All tests passing (15 tests, 64 assertions).

    * Implement order, cart, and address data collection (Task Group 3)

    Add comprehensive order, cart item, and address data collection methods to SessionDataCollector with multiple improvements based on WooCommerce best practices.

    Implementation:
    - Add get_order_data() method collecting 11 order fields including totals, currency, and cart hash
    - Add get_cart_items() method extracting 12 detailed fields per cart item
    - Add get_billing_address() and get_shipping_address() methods with 6 fields each
    - Add get_product_category_names() helper for cleaner category extraction
    - Update collect() method to populate order and address sections

    Key improvements:
    - Use WC()->customer->get_id() for customer_id instead of user login check (proper WooCommerce abstraction)
    - Use get_subtotal() for items_total (before discounts) instead of get_cart_contents_total() (after discounts)
    - Use get_total('edit') for total instead of manual calculation (more reliable)
    - Use wc_get_product_terms() for categories with caching (WooCommerce helper vs WordPress get_term)
    - Return all product categories as comma-separated list (better fraud detection data)
    - Extract category logic to reusable helper method (cleaner code)

    Testing:
    - Add 9 new focused tests for order, cart, and address data (24 total tests, 137 assertions)
    - Fix cart state isolation between tests (add empty_cart() calls)
    - Add customer reinitialization in logged-in user test
    - All tests passing

    Part of Task Group 3 for WOOSUBS-1248 (Session Data Collection Framework).

    * Add payment data structure and complete SessionDataCollector (Task Group 4)

    Complete SessionDataCollector implementation with payment data structure and comprehensive integration testing. All data collection methods now implemented with graceful degradation throughout.

    Implementation:
    - Add get_payment_data() method with 11 payment fields (gateway name/type populated, card details null for future gateway extensions)
    - Add get_product_category_names() helper for cleaner category extraction
    - Update collect() to populate payment section and extract order_id from event_data
    - Update get_order_data() to accept order_id parameter from event_data

    Integration testing:
    - Add 6 comprehensive integration tests (35 total tests, 202 assertions)
    - Test payment data structure with all 11 required fields
    - Test complete collect() output with all 9 sections
    - Test end-to-end data collection with full cart scenario
    - Test graceful degradation across all sections (empty cart, no user, minimal data)
    - Test manual triggering verification
    - Fix graceful degradation test to properly initialize guest customer

    Data collection improvements:
    - Order ID now extracted from event_data when provided by calling code
    - Category extraction uses wc_get_product_terms() with caching (WooCommerce helper)
    - Returns all product categories as comma-separated list (better fraud detection data)
    - Comprehensive PHPDoc comments throughout

    All acceptance criteria met:
    - Payment structure complete with gateway extension placeholders
    - All 9 sections implemented and tested
    - Graceful degradation verified end-to-end
    - Manual triggering only (no automatic hooks in SessionDataCollector)
    - All tests passing (35 tests, 202 assertions)

    Part of Task Group 4 for WOOSUBS-1248 (Session Data Collection Framework).

    * Remove todo comment in order to calm down the linter

    * Use get_order_count on customer for optimized order count retrieval with caching support

    * Add ApiClient for Fraud Protection API communication

    Implements WOOSUBS-1247: Creates an API client that sends session data
    to the WPCOM fraud protection endpoint via Jetpack Connection.

    Key features:
    - Fail-open pattern: returns "allow" on all error conditions
    - Comprehensive error logging for debugging
    - 30s timeout for API requests
    - Validates verdict responses (allow/block)

    Also adds LoggerSpyTrait for cleaner log assertions in tests.

    * Fix lint and PHPStan errors

    * Fixed empty customer $lifetime_order_count

    * Added comment about why we're reloading the customer

    * Now we always create a unique session id

    * Fixed session hash name

    * Removed unused imports

    * Removed unecessary dependency

    * Updated isset check to the null coallese operator

    * Casting $old_quantity to int

    * Added check to prevent sending cart update events when the number of items didn't change

    * Disabled taxes to prevent fraud tests from failing when checking the cart totals

    * Lint fixes

    * Added extra checks for when the decoded json is not an array or is null

    * Added microtime to the sku so each product under test is unique

    * Fixed lint errors

    * Added class to hook into payment method actions to track fraud data

    * Changed the log method so it's possible to override it during test running

    * Added test to check if the PaymentMethodEventTracker works as expected

    * Revert "Changed the log method so it's possible to override it during test running"

    This reverts commit b9fd1f501777cbe0f5aa1ad93c36029830a0f9c0.

    * Updated test to use the LoggerSpyTrait

    * Removed unecessary dependency

    * phpcs fixes

    * Fixed lint error about todo task

    * Added hooks for payment methods to add their customer data when a payment method is selected.

    * Added tests

    * Added hooks documentation

    * trigger tests

    * Fixed lint errors

    * phpstan fix

    * Added class to hook into the shortcode checkout

    * Added tracking to the shortcode payment method select event

    * Added tests for PaymentMethodHelper class

    * Added tests for the CheckoutEventTracker class

    * Created a centralized tracker for all classes

    * Fixed tests after refactoring the track_event methods

    * Fixed shipping methods event tracking key

    * Removed unecessary test

    * Removed unecessary batching code

    * Removed batching and fixed the scheduled action to contain valid customer data

    * Added a custom query to remove the correct scheduled actions for a session

    * Created method with a custom query to remove previous unexecuted scheduled actions

    * Linting and phpstan fixes

    * Linting and phpstan fixes

    * phpstan fixes

    * more phpstan fixes

    * Fixed test missing session setup

    * Now the event listener that tracks payment method changes is only going to be added when fraud protection is enabled

    * Test fixes

    * Test fix

    * Added hooks to track events that update the customer on the blocks checkout page

    * Created the CheckoutEventScheduler class to re-use the scheduler for block checkout integration.

    * Created BlockCheckoutEventTracker to track block checkout events

    * Renamed CheckoutEventTrackerTest to ShortcodeCheckoutEventTrackerTest

    * Created tests for CheckoutEventScheduler

    * Created tests for BlocksCheckoutEventTracker

    * Fixed ShortCodeCheckoutEventTracker class name and added BlocksCheckoutEventTracker

    * Added code to track payment method selection on blocks checout

    * Added tests for the BlocksCheckoutEventTracker

    * Added test for the FraudProtectionPaymentMethodSelected class

    * Added js test for the payment method switch tracker

    * Lint fixes

    * More lint fixes

    * Fixed MSW errors

    * More lint fixes

    * More lint fixes

    * More lint fixes

    * added EventTracker so payment methods can hook into it to add data

    * Added EventTracker class tests

    * Removed FraudProtectionTracker and it's tests as it's not needed anymore

    * Removed unecessary hook

    * Removed woocommerce_new_payment_token manual trigger to rely on the core calling it

    * Fixed sut and testdox

    * Removed unecessary tests for woocommerce_payment_token_add_failed

    * Fixed hook comment

    * Added explanation of when the woocommerce_fraud_protection_payment_data hooks are called

    * Fixed tests to use the woocommerce hook system instead of manually calling the hooks

    * Removed unecessary space

    * Updated test to use assertLogged instead of manually checking each log key

    * Fix array alignment in PaymentMethodEventTrackerTest

    * Removed unecessary hook and related tests and documentation

    * Removed whitespace

    * Fixed PaymentMethodEventTracker expected data format

    * Lint fixes

    * Reverted msw related chagnes

    * Fixed test that was loading msw when it was not needed

    * Removed unecessary scheduled action to track the checkout form data

    * Removed whitespace

    * Removed whitespace

    * Removed payment method change tracking

    * Fixed broken tests and removed unused code

    * Removed reference to inexisting class

    * Renamed event_tracker to event_dispatcher

    * Removed payment method selection tracker

    * Removed whitespace

    * Recovered missing files after broken merge

    * Fixed comments mentioning tracking instead of dispatching

    * Updated tracker to dispatcher

    * Integrated FraudProtectionDispatcher with the apiClient and DecisionHandler

    * Fixed phpstan and linting errors

    * Merged default payment data with data that has been previously collected

    * Removed left over data

    * Fixed function call

    * Reverted function call

    * Reverted headers_sent check

    * Moved method that gets payment gateway name by it's id to the WC_Payment_Gateways class

    * Fixed linting and phpstan errors

    * Fixed linting and phpstan errors

    * Merged both blocks and shortcode checkout event tracker classes

    * Now the shipping method and payment methods are tracked when the customer updates the blocks checkout form

    * Fixed failing tests because of headers already sent

    * Added more billing and shipping data to the SessionDataCollector

    * Moved from using hooks to calling the checkout update tracking function directly

    * Added comment

    * Update test to check track_blocks_checkout_update

    * Added shipping selected event tracker

    * Removed unecessary methods

    * php stan and lint errors fix

    * Fixed call function

    * Fixed linting errors

    * Removed unecessary tests and methods.

    * Fixed tests after changes on address key changes

    * Removed get_shipping_method_names

    * Fixed error when there is no payment method selected in the shortcode checkout

    * Fixed address fields name

    * Fixed comment

    * Removed unecessary package

    * Removed billing email

    * Moved the collect_data call to the dispatcher so it doesn't need to be called on every event

    * Updated FraudProtectionDispatcher to send the whole collected data to the decision handler

    * Fixed linting errors

    * Removed unecessary function

    * Prevented fatal error when shipping_method is not available

    * Update plugins/woocommerce/src/Internal/FraudProtection/CheckoutEventTracker.php

    Co-authored-by: Vasily Belolapotkov <vasily.belolapotkov@automattic.com>

    * Renamed checkout event

    * Removed blocks checkout shipping method tracker

    * Removed package annotation

    * Removed duplicated test

    * Reverted calculate totals removal

    * Removed shipping data

    * Lint fix

    ---------

    Co-authored-by: Vasily Belolapotkov <vasily.belolapotkov@automattic.com>
    Co-authored-by: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
    Co-authored-by: Luiz Reis <luiz.reis@automattic.com>

diff --git a/plugins/woocommerce/src/Internal/FraudProtection/CartEventTracker.php b/plugins/woocommerce/src/Internal/FraudProtection/CartEventTracker.php
index dab6e489bf..615241e047 100644
--- a/plugins/woocommerce/src/Internal/FraudProtection/CartEventTracker.php
+++ b/plugins/woocommerce/src/Internal/FraudProtection/CartEventTracker.php
@@ -15,9 +15,8 @@ defined( 'ABSPATH' ) || exit;
  * Tracks cart events for fraud protection analysis.
  *
  * This class hooks into WooCommerce cart events (add, update, remove, restore)
- * and triggers comprehensive event tracking with full session context. It orchestrates
- * the event tracking by collecting session data and preparing it for the fraud
- * protection service.
+ * and triggers fraud protection event dispatching. Event-specific data is passed
+ * to the dispatcher which handles session data collection internally.
  *
  * @since 10.5.0
  * @internal This class is part of the internal API and is subject to change without notice.
@@ -31,13 +30,6 @@ class CartEventTracker implements RegisterHooksInterface {
 	 */
 	private FraudProtectionDispatcher $dispatcher;

-	/**
-	 * Session data collector instance.
-	 *
-	 * @var SessionDataCollector
-	 */
-	private SessionDataCollector $data_collector;
-
 	/**
 	 * Fraud protection controller instance.
 	 *
@@ -51,16 +43,13 @@ class CartEventTracker implements RegisterHooksInterface {
 	 * @internal
 	 *
 	 * @param FraudProtectionDispatcher $dispatcher               The fraud protection dispatcher instance.
-	 * @param SessionDataCollector      $data_collector              The session data collector instance.
 	 * @param FraudProtectionController $fraud_protection_controller The fraud protection controller instance.
 	 */
 	final public function init(
 		FraudProtectionDispatcher $dispatcher,
-		SessionDataCollector $data_collector,
 		FraudProtectionController $fraud_protection_controller
 	): void {
 		$this->dispatcher                  = $dispatcher;
-		$this->data_collector              = $data_collector;
 		$this->fraud_protection_controller = $fraud_protection_controller;
 	}

@@ -87,7 +76,7 @@ class CartEventTracker implements RegisterHooksInterface {
 	/**
 	 * Handle cart item added event.
 	 *
-	 * Triggers fraud protection event tracking when an item is added to the cart.
+	 * Triggers fraud protection event dispatching when an item is added to the cart.
 	 *
 	 * @internal
 	 *
@@ -107,31 +96,14 @@ class CartEventTracker implements RegisterHooksInterface {
 			$variation_id
 		);

-		// Collect comprehensive session data.
-		try {
-			$collected_data = $this->data_collector->collect( 'cart_item_added', $event_data );
-			$this->dispatcher->dispatch_event( 'cart_item_added', $collected_data );
-		} catch ( \Exception $e ) {
-			// Log error but don't break functionality.
-			FraudProtectionController::log(
-				'error',
-				sprintf(
-					'Failed to collect session data for cart event: %s | Error: %s',
-					'cart_item_added',
-					$e->getMessage()
-				),
-				array(
-					'event_type' => 'cart_item_added',
-					'exception'  => $e,
-				)
-			);
-		}
+		// Trigger event dispatching.
+		$this->dispatcher->dispatch_event( 'cart_item_added', $event_data );
 	}

 	/**
 	 * Handle cart item quantity updated event.
 	 *
-	 * Triggers fraud protection event tracking when cart item quantity is updated.
+	 * Triggers fraud protection event dispatching when cart item quantity is updated.
 	 *
 	 * @internal
 	 *
@@ -161,31 +133,14 @@ class CartEventTracker implements RegisterHooksInterface {
 		// Add old quantity for context.
 		$event_data['old_quantity'] = (int) $old_quantity;

-		// Collect comprehensive session data.
-		try {
-			$collected_data = $this->data_collector->collect( 'cart_item_updated', $event_data );
-			$this->dispatcher->dispatch_event( 'cart_item_updated', $collected_data );
-		} catch ( \Exception $e ) {
-			// Log error but don't break functionality.
-			FraudProtectionController::log(
-				'error',
-				sprintf(
-					'Failed to collect session data for cart event: %s | Error: %s',
-					'cart_item_updated',
-					$e->getMessage()
-				),
-				array(
-					'event_type' => 'cart_item_updated',
-					'exception'  => $e,
-				)
-			);
-		}
+		// Trigger event dispatching.
+		$this->dispatcher->dispatch_event( 'cart_item_updated', $event_data );
 	}

 	/**
 	 * Handle cart item removed event.
 	 *
-	 * Triggers fraud protection event tracking when an item is removed from the cart.
+	 * Triggers fraud protection event dispatching when an item is removed from the cart.
 	 *
 	 * @internal
 	 *
@@ -211,31 +166,14 @@ class CartEventTracker implements RegisterHooksInterface {
 			$variation_id
 		);

-		// Collect comprehensive session data.
-		try {
-			$collected_data = $this->data_collector->collect( 'cart_item_removed', $event_data );
-			$this->dispatcher->dispatch_event( 'cart_item_removed', $collected_data );
-		} catch ( \Exception $e ) {
-			// Log error but don't break functionality.
-			FraudProtectionController::log(
-				'error',
-				sprintf(
-					'Failed to collect session data for cart event: %s | Error: %s',
-					'cart_item_removed',
-					$e->getMessage()
-				),
-				array(
-					'event_type' => 'cart_item_removed',
-					'exception'  => $e,
-				)
-			);
-		}
+		// Trigger event dispatching.
+		$this->dispatcher->dispatch_event( 'cart_item_removed', $event_data );
 	}

 	/**
 	 * Handle cart item restored event.
 	 *
-	 * Triggers fraud protection event tracking when a removed item is restored to the cart.
+	 * Triggers fraud protection event dispatching when a removed item is restored to the cart.
 	 *
 	 * @internal
 	 *
@@ -261,25 +199,8 @@ class CartEventTracker implements RegisterHooksInterface {
 			$variation_id
 		);

-		// Collect comprehensive session data.
-		try {
-			$collected_data = $this->data_collector->collect( 'cart_item_restored', $event_data );
-			$this->dispatcher->dispatch_event( 'cart_item_restored', $collected_data );
-		} catch ( \Exception $e ) {
-			// Log error but don't break functionality.
-			FraudProtectionController::log(
-				'error',
-				sprintf(
-					'Failed to collect session data for cart event: %s | Error: %s',
-					'cart_item_restored',
-					$e->getMessage()
-				),
-				array(
-					'event_type' => 'cart_item_restored',
-					'exception'  => $e,
-				)
-			);
-		}
+		// Trigger event dispatching.
+		$this->dispatcher->dispatch_event( 'cart_item_restored', $event_data );
 	}

 	/**
@@ -287,7 +208,7 @@ class CartEventTracker implements RegisterHooksInterface {
 	 *
 	 * Prepares the cart event data including action type, product details,
 	 * and current cart state. This data will be merged with comprehensive
-	 * session data during event tracking.
+	 * session data during event dispatching.
 	 *
 	 * @param string $action       Action type (item_added, item_updated, item_removed, item_restored).
 	 * @param int    $product_id   Product ID.
diff --git a/plugins/woocommerce/src/Internal/FraudProtection/CheckoutEventTracker.php b/plugins/woocommerce/src/Internal/FraudProtection/CheckoutEventTracker.php
index d4247a3f02..88d9bd4edc 100644
--- a/plugins/woocommerce/src/Internal/FraudProtection/CheckoutEventTracker.php
+++ b/plugins/woocommerce/src/Internal/FraudProtection/CheckoutEventTracker.php
@@ -15,8 +15,8 @@ defined( 'ABSPATH' ) || exit;
  * Tracks checkout events for fraud protection analysis.
  *
  * This class hooks into both WooCommerce Blocks (Store API) and traditional
- * shortcode checkout events, triggering comprehensive event tracking with
- * full session context for fraud protection analysis.
+ * shortcode checkout events, triggering fraud protection event dispatching.
+ * Event-specific data is passed to the dispatcher which handles session data collection internally.
  *
  * @since 10.5.0
  * @internal This class is part of the internal API and is subject to change without notice.
@@ -37,13 +37,6 @@ class CheckoutEventTracker implements RegisterHooksInterface {
 	 */
 	private FraudProtectionController $fraud_protection_controller;

-	/**
-	 * Session data collector instance.
-	 *
-	 * @var SessionDataCollector
-	 */
-	private SessionDataCollector $data_collector;
-
 	/**
 	 * Initialize with dependencies.
 	 *
@@ -51,16 +44,13 @@ class CheckoutEventTracker implements RegisterHooksInterface {
 	 *
 	 * @param FraudProtectionDispatcher $dispatcher The fraud protection dispatcher instance.
 	 * @param FraudProtectionController $fraud_protection_controller The fraud protection controller instance.
-	 * @param SessionDataCollector      $data_collector The session data collector instance.
 	 */
 	final public function init(
 		FraudProtectionDispatcher $dispatcher,
-		FraudProtectionController $fraud_protection_controller,
-		SessionDataCollector $data_collector
+		FraudProtectionController $fraud_protection_controller
 	): void {
 		$this->dispatcher                  = $dispatcher;
 		$this->fraud_protection_controller = $fraud_protection_controller;
-		$this->data_collector              = $data_collector;
 	}

 	/**
@@ -92,12 +82,11 @@ class CheckoutEventTracker implements RegisterHooksInterface {
 	 * @return void
 	 */
 	public function track_blocks_checkout_update(): void {
-		// The data collector already has up to date customer data, so we don't need to pass it as a parameter.
-		// At this point we don't have any payment or shipping data, so we don't need to pass it as a parameter either.
-		$collected_data = $this->data_collector->collect( 'checkout_update', array() );
-		$this->dispatcher->dispatch_event( 'checkout_update', $collected_data );
+		// 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() );
 	}

+
 	/**
 	 * Handle shortcode checkout field update event.
 	 *
@@ -115,7 +104,7 @@ class CheckoutEventTracker implements RegisterHooksInterface {
 			parse_str( $posted_data, $data );
 		}

-		// Build and dispatch the event (traditional checkout includes payment/shipping methods).
+		// Build and dispatch the event.
 		$event_data = $this->format_checkout_event_data( 'field_update', $data );
 		$this->dispatcher->dispatch_event( 'checkout_field_update', $event_data );
 	}
@@ -124,7 +113,6 @@ class CheckoutEventTracker implements RegisterHooksInterface {
 	 * Build checkout event-specific data.
 	 *
 	 * Prepares the checkout event data including action type and any changed fields.
-	 * This data will be merged with comprehensive session data during event tracking.
 	 *
 	 * @param string $action Action type (field_update, store_api_update).
 	 * @param array  $collected_event_data Posted form data or event context (may include session data).
diff --git a/plugins/woocommerce/src/Internal/FraudProtection/FraudProtectionDispatcher.php b/plugins/woocommerce/src/Internal/FraudProtection/FraudProtectionDispatcher.php
index 50ad869794..73637a2b2a 100644
--- a/plugins/woocommerce/src/Internal/FraudProtection/FraudProtectionDispatcher.php
+++ b/plugins/woocommerce/src/Internal/FraudProtection/FraudProtectionDispatcher.php
@@ -13,7 +13,8 @@ defined( 'ABSPATH' ) || exit;
  * Centralized fraud protection event dispatcher.
  *
  * This class provides a unified interface for dispatching fraud protection events.
- * It logs events for the fraud protection service using already-collected data.
+ * 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.
@@ -21,44 +22,118 @@ defined( 'ABSPATH' ) || exit;
 class FraudProtectionDispatcher {

 	/**
-	 * Track fraud protection event with already-collected data.
+	 * API client instance.
 	 *
-	 * This method accepts fully-collected event data (including session context)
-	 * and logs it for the fraud protection service.
+	 * @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  $collected_data Fully-collected event data including session context.
+	 * @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 $collected_data ): void {
+	public function dispatch_event( string $event_type, array $event_data = array() ): void {
 		try {
-			// phpcs:ignore Generic.Commenting.Todo.TaskFound
-			// TODO: Once EventTracker/API client is implemented (WOOSUBS-1249), call it here:
-			// $event_tracker = wc_get_container()->get( EventTracker::class );
-			// $event_tracker->track( $event_type, $collected_data );
-			//
-			// For now, log the event for debugging and verification.
-			FraudProtectionController::log(
-				'info',
-				sprintf(
-					'Fraud protection event tracked: %s | Session ID: %s',
-					$event_type,
-					$collected_data['session']['session_id'] ?? 'N/A'
-				),
-				array(
-					'event_type'     => $event_type,
-					'collected_data' => $collected_data,
-				)
-			);
+			// 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 track fraud protection event: %s | Error: %s',
+					'Failed to dispatch fraud protection event: %s | Error: %s',
 					$event_type,
 					$e->getMessage()
 				),
diff --git a/plugins/woocommerce/src/Internal/FraudProtection/PaymentMethodEventTracker.php b/plugins/woocommerce/src/Internal/FraudProtection/PaymentMethodEventTracker.php
index 61b8229ef2..f01ff48842 100644
--- a/plugins/woocommerce/src/Internal/FraudProtection/PaymentMethodEventTracker.php
+++ b/plugins/woocommerce/src/Internal/FraudProtection/PaymentMethodEventTracker.php
@@ -1,8 +1,6 @@
 <?php
 /**
  * PaymentMethodEventTracker class file.
- *
- * @package WooCommerce\Classes
  */

 declare( strict_types=1 );
@@ -17,9 +15,8 @@ defined( 'ABSPATH' ) || exit;
  * Tracks payment method events for fraud protection analysis.
  *
  * This class hooks into WooCommerce payment method events in My Account
- * (add, update, set default, delete, add failed) and triggers comprehensive event
- * tracking with full session context. It orchestrates the event tracking by collecting
- * session data and preparing it for the fraud protection service.
+ * (add, update, set default, delete) and triggers fraud protection event dispatching.
+ * Event-specific data is passed to the dispatcher which handles session data collection internally.
  *
  * @since 10.5.0
  * @internal This class is part of the internal API and is subject to change without notice.
@@ -33,13 +30,6 @@ class PaymentMethodEventTracker implements RegisterHooksInterface {
 	 */
 	private FraudProtectionDispatcher $dispatcher;

-	/**
-	 * Session data collector instance.
-	 *
-	 * @var SessionDataCollector
-	 */
-	private SessionDataCollector $data_collector;
-
 	/**
 	 * Fraud protection controller instance.
 	 *
@@ -53,16 +43,13 @@ class PaymentMethodEventTracker implements RegisterHooksInterface {
 	 * @internal
 	 *
 	 * @param FraudProtectionDispatcher $dispatcher                     The fraud protection dispatcher instance.
-	 * @param SessionDataCollector      $data_collector              The session data collector instance.
 	 * @param FraudProtectionController $fraud_protection_controller The fraud protection controller instance.
 	 */
 	final public function init(
 		FraudProtectionDispatcher $dispatcher,
-		SessionDataCollector $data_collector,
 		FraudProtectionController $fraud_protection_controller
 	): void {
 		$this->dispatcher                  = $dispatcher;
-		$this->data_collector              = $data_collector;
 		$this->fraud_protection_controller = $fraud_protection_controller;
 	}

@@ -97,25 +84,8 @@ class PaymentMethodEventTracker implements RegisterHooksInterface {
 	public function handle_payment_method_added( $token_id, $token ): void {
 		$event_data = $this->build_payment_method_event_data( 'added', $token );

-		// Collect comprehensive session data.
-		try {
-			$collected_data = $this->data_collector->collect( 'payment_method_added', $event_data );
-			$this->dispatcher->dispatch_event( 'payment_method_added', $collected_data );
-		} catch ( \Exception $e ) {
-			// Log error but don't break functionality.
-			FraudProtectionController::log(
-				'error',
-				sprintf(
-					'Failed to collect session data for payment method event: %s | Error: %s',
-					'payment_method_added',
-					$e->getMessage()
-				),
-				array(
-					'event_type' => 'payment_method_added',
-					'exception'  => $e,
-				)
-			);
-		}
+		// Trigger event dispatching.
+		$this->dispatcher->dispatch_event( 'payment_method_added', $event_data );
 	}

 	/**
@@ -137,25 +107,8 @@ class PaymentMethodEventTracker implements RegisterHooksInterface {

 		$event_data = $this->build_payment_method_event_data( 'updated', $token );

-		// Collect comprehensive session data.
-		try {
-			$collected_data = $this->data_collector->collect( 'payment_method_updated', $event_data );
-			$this->dispatcher->dispatch_event( 'payment_method_updated', $collected_data );
-		} catch ( \Exception $e ) {
-			// Log error but don't break functionality.
-			FraudProtectionController::log(
-				'error',
-				sprintf(
-					'Failed to collect session data for payment method event: %s | Error: %s',
-					'payment_method_updated',
-					$e->getMessage()
-				),
-				array(
-					'event_type' => 'payment_method_updated',
-					'exception'  => $e,
-				)
-			);
-		}
+		// Trigger event dispatching.
+		$this->dispatcher->dispatch_event( 'payment_method_updated', $event_data );
 	}

 	/**
@@ -171,25 +124,8 @@ class PaymentMethodEventTracker implements RegisterHooksInterface {
 	public function handle_payment_method_set_default( $token_id, $token ): void {
 		$event_data = $this->build_payment_method_event_data( 'set_default', $token );

-		// Collect comprehensive session data.
-		try {
-			$collected_data = $this->data_collector->collect( 'payment_method_set_default', $event_data );
-			$this->dispatcher->dispatch_event( 'payment_method_set_default', $collected_data );
-		} catch ( \Exception $e ) {
-			// Log error but don't break functionality.
-			FraudProtectionController::log(
-				'error',
-				sprintf(
-					'Failed to collect session data for payment method event: %s | Error: %s',
-					'payment_method_set_default',
-					$e->getMessage()
-				),
-				array(
-					'event_type' => 'payment_method_set_default',
-					'exception'  => $e,
-				)
-			);
-		}
+		// Trigger event dispatching.
+		$this->dispatcher->dispatch_event( 'payment_method_set_default', $event_data );
 	}

 	/**
@@ -205,25 +141,8 @@ class PaymentMethodEventTracker implements RegisterHooksInterface {
 	public function handle_payment_method_deleted( $token_id, $token ): void {
 		$event_data = $this->build_payment_method_event_data( 'deleted', $token );

-		// Collect comprehensive session data.
-		try {
-			$collected_data = $this->data_collector->collect( 'payment_method_deleted', $event_data );
-			$this->dispatcher->dispatch_event( 'payment_method_deleted', $collected_data );
-		} catch ( \Exception $e ) {
-			// Log error but don't break functionality.
-			FraudProtectionController::log(
-				'error',
-				sprintf(
-					'Failed to collect session data for payment method event: %s | Error: %s',
-					'payment_method_deleted',
-					$e->getMessage()
-				),
-				array(
-					'event_type' => 'payment_method_deleted',
-					'exception'  => $e,
-				)
-			);
-		}
+		// Trigger event dispatching.
+		$this->dispatcher->dispatch_event( 'payment_method_deleted', $event_data );
 	}

 	/**
diff --git a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CartEventTrackerTest.php b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CartEventTrackerTest.php
index 7bc33f73d7..da92b7d3de 100644
--- a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CartEventTrackerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CartEventTrackerTest.php
@@ -9,7 +9,6 @@ namespace Automattic\WooCommerce\Tests\Internal\FraudProtection;

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

 /**
@@ -27,19 +26,12 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 	private $sut;

 	/**
-	 * Mock fraud protection tracker.
+	 * Mock event dispatcher.
 	 *
 	 * @var FraudProtectionDispatcher|\PHPUnit\Framework\MockObject\MockObject
 	 */
 	private $mock_dispatcher;

-	/**
-	 * Mock session data collector.
-	 *
-	 * @var SessionDataCollector|\PHPUnit\Framework\MockObject\MockObject
-	 */
-	private $mock_data_collector;
-
 	/**
 	 * Mock fraud protection controller.
 	 *
@@ -66,15 +58,13 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 		}

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

 		// Create system under test.
 		$this->sut = new CartEventTracker();
 		$this->sut->init(
 			$this->mock_dispatcher,
-			$this->mock_data_collector,
 			$this->mock_controller
 		);

@@ -123,27 +113,24 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 	 * Test handle_track_cart_item_added tracks event.
 	 */
 	public function test_handle_track_cart_item_added_tracks_event(): void {
-		$collected_data = array(
-			'action'          => 'item_added',
-			'product_id'      => $this->test_product->get_id(),
-			'quantity'        => 2,
-			'cart_item_count' => 1,
-			'session'         => array( 'session_id' => 'test-session' ),
-		);
-
-		// Mock data collector to return collected data.
-		$this->mock_data_collector
-			->expects( $this->once() )
-			->method( 'collect' )
-			->willReturn( $collected_data );
-
-		// Mock the tracker to verify track_event is called with collected data.
+		// Mock the dispatcher to verify dispatch_event is called with event data.
 		$this->mock_dispatcher
 			->expects( $this->once() )
 			->method( 'dispatch_event' )
 			->with(
 				$this->equalTo( 'cart_item_added' ),
-				$this->equalTo( $collected_data )
+				$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 );
+						$this->assertEquals( $this->test_product->get_id(), $event_data['product_id'] );
+						$this->assertArrayHasKey( 'quantity', $event_data );
+						$this->assertEquals( 2, $event_data['quantity'] );
+						return true;
+					}
+				)
 			);

 		// Call the handler.
@@ -164,26 +151,24 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 		// Add item to cart first.
 		$cart_item_key = WC()->cart->add_to_cart( $this->test_product->get_id(), 1 );

-		$collected_data = array(
-			'action'       => 'item_updated',
-			'old_quantity' => 1,
-			'quantity'     => 5,
-			'session'      => array( 'session_id' => 'test-session' ),
-		);
-
-		// Mock data collector.
-		$this->mock_data_collector
-			->expects( $this->once() )
-			->method( 'collect' )
-			->willReturn( $collected_data );
-
-		// Mock the tracker.
+		// Mock the dispatcher.
 		$this->mock_dispatcher
 			->expects( $this->once() )
 			->method( 'dispatch_event' )
 			->with(
 				$this->equalTo( 'cart_item_updated' ),
-				$this->equalTo( $collected_data )
+				$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 );
+						$this->assertEquals( 5, $event_data['quantity'] );
+						$this->assertArrayHasKey( 'old_quantity', $event_data );
+						$this->assertEquals( 1, $event_data['old_quantity'] );
+						return true;
+					}
+				)
 			);

 		// Call the handler.
@@ -202,24 +187,20 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 		// Add item to cart.
 		$cart_item_key = WC()->cart->add_to_cart( $this->test_product->get_id(), 1 );

-		$collected_data = array(
-			'action'  => 'item_removed',
-			'session' => array( 'session_id' => 'test-session' ),
-		);
-
-		// Mock data collector.
-		$this->mock_data_collector
-			->expects( $this->once() )
-			->method( 'collect' )
-			->willReturn( $collected_data );
-
-		// Mock the tracker.
+		// Mock the dispatcher.
 		$this->mock_dispatcher
 			->expects( $this->once() )
 			->method( 'dispatch_event' )
 			->with(
 				$this->equalTo( 'cart_item_removed' ),
-				$this->equalTo( $collected_data )
+				$this->callback(
+					function ( $event_data ) {
+						// Verify the event data structure.
+						$this->assertArrayHasKey( 'action', $event_data );
+						$this->assertEquals( 'item_removed', $event_data['action'] );
+						return true;
+					}
+				)
 			);

 		// Remove the item from cart.
@@ -236,24 +217,20 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 		// Add item to cart.
 		$cart_item_key = WC()->cart->add_to_cart( $this->test_product->get_id(), 1 );

-		$collected_data = array(
-			'action'  => 'item_restored',
-			'session' => array( 'session_id' => 'test-session' ),
-		);
-
-		// Mock data collector.
-		$this->mock_data_collector
-			->expects( $this->once() )
-			->method( 'collect' )
-			->willReturn( $collected_data );
-
-		// Mock the tracker.
+		// Mock the dispatcher.
 		$this->mock_dispatcher
 			->expects( $this->once() )
 			->method( 'dispatch_event' )
 			->with(
 				$this->equalTo( 'cart_item_restored' ),
-				$this->equalTo( $collected_data )
+				$this->callback(
+					function ( $event_data ) {
+						// Verify the event data structure.
+						$this->assertArrayHasKey( 'action', $event_data );
+						$this->assertEquals( 'item_restored', $event_data['action'] );
+						return true;
+					}
+				)
 			);

 		// Call the handler directly (simulating restore action).
@@ -272,25 +249,22 @@ class CartEventTrackerTest extends \WC_Unit_Test_Case {
 		$variations       = $variable_product->get_available_variations();
 		$variation_id     = $variations[0]['variation_id'];

-		$collected_data = array(
-			'action'       => 'item_added',
-			'variation_id' => $variation_id,
-			'session'      => array( 'session_id' => 'test-session' ),
-		);
-
-		// Mock data collector.
-		$this->mock_data_collector
-			->expects( $this->once() )
-			->method( 'collect' )
-			->willReturn( $collected_data );
-
-		// Mock the tracker to capture event data.
+		// Mock the dispatcher to capture event data.
 		$this->mock_dispatcher
 			->expects( $this->once() )
 			->method( 'dispatch_event' )
 			->with(
 				$this->equalTo( 'cart_item_added' ),
-				$this->equalTo( $collected_data )
+				$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 );
+						$this->assertEquals( $variation_id, $event_data['variation_id'] );
+						return true;
+					}
+				)
 			);

 		// Call the handler with variation ID.
diff --git a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CheckoutEventTrackerTest.php b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CheckoutEventTrackerTest.php
index 4d8a827188..5406b8aa92 100644
--- a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CheckoutEventTrackerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/CheckoutEventTrackerTest.php
@@ -7,7 +7,9 @@ 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;
@@ -40,13 +42,6 @@ class CheckoutEventTrackerTest extends \WC_Unit_Test_Case {
 	 */
 	private $mock_controller;

-	/**
-	 * Mock session data collector.
-	 *
-	 * @var SessionDataCollector|\PHPUnit\Framework\MockObject\MockObject
-	 */
-	private $mock_data_collector;
-
 	/**
 	 * Runs before each test.
 	 */
@@ -59,16 +54,14 @@ class CheckoutEventTrackerTest extends \WC_Unit_Test_Case {
 		}

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

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

@@ -106,63 +99,24 @@ 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.
 	 */
-	public function test_track_blocks_checkout_update_dispatches_event_with_session_data(): void {
-		// Mock data collector to return session data.
-		$session_data = array(
-			'session_id'       => 'test_session_123',
-			'billing_email'    => 'test@example.com',
-			'billing_address'  => array(
-				'first_name' => 'John',
-				'last_name'  => 'Doe',
-			),
-			'shipping_address' => array(
-				'city' => 'New York',
-			),
-		);
-		$this->mock_data_collector
-			->expects( $this->once() )
-			->method( 'collect' )
-			->with(
-				$this->equalTo( 'checkout_update' ),
-				$this->equalTo( array() )
-			)
-			->willReturn( $session_data );
-
-		// Mock dispatcher to verify event is dispatched.
+	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
 			->expects( $this->once() )
 			->method( 'dispatch_event' )
 			->with(
 				$this->equalTo( 'checkout_update' ),
-				$this->equalTo( $session_data )
+				$this->equalTo( array() )
 			);

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

-	/**
-	 * Test track_blocks_checkout_update can be called directly without hooks.
-	 */
-	public function test_track_blocks_checkout_update_works_without_hooks(): void {
-		// Mock data collector to return minimal session data.
-		$session_data = array(
-			'session_id' => 'test_session_456',
-		);
-		$this->mock_data_collector
-			->method( 'collect' )
-			->willReturn( $session_data );
-
-		// Mock dispatcher to verify event is dispatched.
-		$this->mock_dispatcher
-			->expects( $this->once() )
-			->method( 'dispatch_event' );
-
-		// Call the method directly (as done from CartUpdateCustomer endpoint).
-		$this->sut->track_blocks_checkout_update();
-	}
-
 	// ========================================
 	// Shortcode Checkout Tests
 	// ========================================
diff --git a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/FraudProtectionDispatcherTest.php b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/FraudProtectionDispatcherTest.php
index e8d62afbcd..8771c9d4b5 100644
--- a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/FraudProtectionDispatcherTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/FraudProtectionDispatcherTest.php
@@ -1,14 +1,19 @@
 <?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\RestApi\UnitTests\LoggerSpyTrait;
+use Automattic\WooCommerce\Internal\FraudProtection\SessionDataCollector;

 /**
  * Tests for FraudProtectionDispatcher.
@@ -17,8 +22,6 @@ use Automattic\WooCommerce\RestApi\UnitTests\LoggerSpyTrait;
  */
 class FraudProtectionDispatcherTest extends \WC_Unit_Test_Case {

-	use LoggerSpyTrait;
-
 	/**
 	 * The system under test.
 	 *
@@ -26,100 +29,301 @@ class FraudProtectionDispatcherTest extends \WC_Unit_Test_Case {
 	 */
 	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 system under test.
+		// 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 track_event logs collected data successfully.
+	 * Test that dispatch_event collects session data and sends event to API and applies decision.
 	 */
-	public function test_track_event_logs_collected_data_successfully(): void {
-		$event_type     = 'test_event';
+	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,
 		);

-		// Call track_event.
-		$this->sut->dispatch_event( $event_type, $collected_data );
+		// 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 );

-		// Verify the log was captured.
-		$this->assertCount( 1, $this->captured_logs );
-		$this->assertEquals( 'info', $this->captured_logs[0]['level'] );
-		$this->assertStringContainsString( 'test_event', $this->captured_logs[0]['message'] );
-		$this->assertStringContainsString( 'test-session-123', $this->captured_logs[0]['message'] );
-		$this->assertEquals( 'test_event', $this->captured_logs[0]['context']['event_type'] );
-		$this->assertEquals( $collected_data, $this->captured_logs[0]['context']['collected_data'] );
+		// 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 track_event handles data without session gracefully.
+	 * Test that dispatch_event handles data without session gracefully.
 	 */
-	public function test_track_event_handles_data_without_session_gracefully(): void {
+	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' );

-		// Call track_event with data without session - should handle gracefully and log with N/A.
-		$this->sut->dispatch_event( $event_type, $collected_data );
+		// 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 );

-		// Verify the log was captured with N/A for session ID.
-		$this->assertCount( 1, $this->captured_logs );
-		$this->assertEquals( 'info', $this->captured_logs[0]['level'] );
-		$this->assertStringContainsString( 'test_event', $this->captured_logs[0]['message'] );
-		$this->assertStringContainsString( 'N/A', $this->captured_logs[0]['message'] );
-		$this->assertEquals( 'test_event', $this->captured_logs[0]['context']['event_type'] );
-		$this->assertEquals( $collected_data, $this->captured_logs[0]['context']['collected_data'] );
+		// Call dispatch_event - should handle gracefully.
+		$this->sut->dispatch_event( $event_type, $event_data );
 	}

 	/**
-	 * Test track_event accepts various data structures.
+	 * Test that dispatch_event respects block decisions.
 	 */
-	public function test_track_event_accepts_various_data_structures(): void {
-		$event_type     = 'cart_item_added';
+	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,
-			'quantity'   => 2,
 		);

-		// Call track_event.
-		$this->sut->dispatch_event( $event_type, $collected_data );
-
-		// Verify the log was captured with all data.
-		$this->assertCount( 1, $this->captured_logs );
-		$this->assertEquals( 'info', $this->captured_logs[0]['level'] );
-		$this->assertStringContainsString( 'cart_item_added', $this->captured_logs[0]['message'] );
-		$this->assertEquals( 'cart_item_added', $this->captured_logs[0]['context']['event_type'] );
-		$this->assertEquals( $collected_data, $this->captured_logs[0]['context']['collected_data'] );
-		$this->assertEquals( 'item_added', $this->captured_logs[0]['context']['collected_data']['action'] );
-		$this->assertEquals( 456, $this->captured_logs[0]['context']['collected_data']['product_id'] );
-		$this->assertEquals( 2, $this->captured_logs[0]['context']['collected_data']['quantity'] );
+		// 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 track_event works with empty collected data.
+	 * Test that dispatch_event doesn't send events when feature is disabled.
 	 */
-	public function test_track_event_works_with_empty_collected_data(): void {
-		$event_type     = 'test_event';
-		$collected_data = array();
-
-		// Call track_event with empty data.
-		$this->sut->dispatch_event( $event_type, $collected_data );
-
-		// Verify the log was captured even with empty data.
-		$this->assertCount( 1, $this->captured_logs );
-		$this->assertEquals( 'info', $this->captured_logs[0]['level'] );
-		$this->assertStringContainsString( 'test_event', $this->captured_logs[0]['message'] );
-		$this->assertStringContainsString( 'N/A', $this->captured_logs[0]['message'] );
-		$this->assertEquals( 'test_event', $this->captured_logs[0]['context']['event_type'] );
-		$this->assertEquals( $collected_data, $this->captured_logs[0]['context']['collected_data'] );
+	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 c5a267d802..c1636572b1 100644
--- a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/PaymentMethodEventTrackerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/PaymentMethodEventTrackerTest.php
@@ -42,6 +42,8 @@ class PaymentMethodEventTrackerTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test that hooks are registered when feature is enabled.
+	 *
 	 * @testdox Should register hooks when feature is enabled.
 	 */
 	public function test_hooks_registered_when_feature_enabled(): void {
@@ -54,6 +56,8 @@ class PaymentMethodEventTrackerTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test that hooks are not registered when feature is disabled.
+	 *
 	 * @testdox Should not register hooks when feature is disabled.
 	 */
 	public function test_hooks_not_registered_when_feature_disabled(): void {
@@ -72,6 +76,8 @@ class PaymentMethodEventTrackerTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test payment method added event tracking.
+	 *
 	 * @testdox Should track payment method added event.
 	 */
 	public function test_handle_payment_method_added(): void {
@@ -89,17 +95,20 @@ 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',
-			'payment_method_added',
+			'Sending fraud protection event: payment_method_added',
 			array(
-				'source'         => 'woo-fraud-protection',
-				'event_type'     => 'payment_method_added',
-				'collected_data' => 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',
 					),
 				),
 			)
@@ -107,6 +116,8 @@ class PaymentMethodEventTrackerTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test payment method updated event tracking.
+	 *
 	 * @testdox Should track payment method updated event.
 	 */
 	public function test_handle_payment_method_updated(): void {
@@ -128,17 +139,19 @@ class PaymentMethodEventTrackerTest extends \WC_Unit_Test_Case {
 		$token->set_expiry_year( '2027' );
 		$token->save();

+		// Verify that the event was sent to the API with correct payload.
 		$this->assertLogged(
 			'info',
-			'payment_method_updated',
+			'Sending fraud protection event: payment_method_updated',
 			array(
-				'source'         => 'woo-fraud-protection',
-				'event_type'     => 'payment_method_updated',
-				'collected_data' => array(
+				'source'  => 'woo-fraud-protection',
+				'payload' => array(
+					'event_type' => 'payment_method_updated',
 					'event_data' => array(
 						'action'     => 'updated',
 						'token_id'   => $token->get_id(),
 						'gateway_id' => 'stripe',
+						'card_type'  => 'mastercard',
 					),
 				),
 			)
@@ -146,6 +159,8 @@ class PaymentMethodEventTrackerTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test payment method set as default event tracking.
+	 *
 	 * @testdox Should track payment method set as default event.
 	 */
 	public function test_handle_payment_method_set_default(): void {
@@ -181,13 +196,14 @@ class PaymentMethodEventTrackerTest extends \WC_Unit_Test_Case {
 		// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment, WooCommerce.Commenting.CommentHooks.MissingSinceComment
 		do_action( 'woocommerce_payment_token_set_default', $token2->get_id(), $token2 );

+		// Verify that the event was sent to the API with correct payload.
 		$this->assertLogged(
 			'info',
-			'payment_method_set_default',
+			'Sending fraud protection event: payment_method_set_default',
 			array(
-				'source'         => 'woo-fraud-protection',
-				'event_type'     => 'payment_method_set_default',
-				'collected_data' => array(
+				'source'  => 'woo-fraud-protection',
+				'payload' => array(
+					'event_type' => 'payment_method_set_default',
 					'event_data' => array(
 						'action'     => 'set_default',
 						'token_id'   => $token2->get_id(),
@@ -200,6 +216,8 @@ class PaymentMethodEventTrackerTest extends \WC_Unit_Test_Case {
 	}

 	/**
+	 * Test payment method deleted event tracking.
+	 *
 	 * @testdox Should track payment method deleted event.
 	 */
 	public function test_handle_payment_method_deleted(): void {
@@ -220,13 +238,14 @@ class PaymentMethodEventTrackerTest extends \WC_Unit_Test_Case {
 		// Delete the token to trigger the 'deleted' event.
 		\WC_Payment_Tokens::delete( $token->get_id() );

+		// Verify that the event was sent to the API with correct payload.
 		$this->assertLogged(
 			'info',
-			'payment_method_deleted',
+			'Sending fraud protection event: payment_method_deleted',
 			array(
-				'source'         => 'woo-fraud-protection',
-				'event_type'     => 'payment_method_deleted',
-				'collected_data' => array(
+				'source'  => 'woo-fraud-protection',
+				'payload' => array(
+					'event_type' => 'payment_method_deleted',
 					'event_data' => array(
 						'action'     => 'deleted',
 						'token_id'   => $token->get_id(),