Commit bd99b7aa64 for woocommerce

commit bd99b7aa642b54d600b68776b8fe703c0eaf5dab
Author: Vasily Belolapotkov <vasily.belolapotkov@automattic.com>
Date:   Sun Jan 11 18:40:15 2026 +0100

    Fraud Protection: Fix session ID extraction in fraud decision logging (#62762)

    * Fix session ID extraction in fraud decision logging

    The session ID was always logged as 'unknown' because it was being
    accessed at the wrong path. The SessionDataCollector returns a nested
    structure with session data at $session_data['session']['session_id'],
    but the code was looking for $session_data['session_id'].

    This fix:
    - Corrects the path to access the nested session ID
    - Adds fail-safe checks for missing or invalid session data
    - Adds unit tests to verify the correct behavior

    * Fix linting alignment issues in ApiClient

    * Rename session_data to event_data in ApiClient::send_event

    ---------

    Co-authored-by: Luiz Reis <luiz.reis@automattic.com>

diff --git a/plugins/woocommerce/src/Internal/FraudProtection/ApiClient.php b/plugins/woocommerce/src/Internal/FraudProtection/ApiClient.php
index b8d95260eb..c1d0609030 100644
--- a/plugins/woocommerce/src/Internal/FraudProtection/ApiClient.php
+++ b/plugins/woocommerce/src/Internal/FraudProtection/ApiClient.php
@@ -74,14 +74,14 @@ class ApiClient {
 	 *
 	 * @since 10.5.0
 	 *
-	 * @param string               $event_type   Type of event being sent (e.g., 'cart_updated', 'checkout_started').
-	 * @param array<string, mixed> $session_data Session data to send to the endpoint.
+	 * @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.
 	 * @return string Decision: "allow" or "block".
 	 */
-	public function send_event( string $event_type, array $session_data ): string {
+	public function send_event( string $event_type, array $event_data ): string {
 		$payload = array_merge(
 			array( 'event_type' => $event_type ),
-			array_filter( $session_data, fn( $value ) => null !== $value )
+			array_filter( $event_data, fn( $value ) => null !== $value )
 		);

 		FraudProtectionController::log(
@@ -129,7 +129,8 @@ class ApiClient {
 			return self::DECISION_ALLOW;
 		}

-		$session_id = $session_data['session_id'] ?? 'unknown';
+		$session    = is_array( $event_data['session'] ?? null ) ? $event_data['session'] : array();
+		$session_id = $session['session_id'] ?? 'unknown';
 		FraudProtectionController::log(
 			'info',
 			sprintf(
diff --git a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/ApiClientTest.php b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/ApiClientTest.php
index 4bdb13bba0..abbbcf51cd 100644
--- a/plugins/woocommerce/tests/php/src/Internal/FraudProtection/ApiClientTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/FraudProtection/ApiClientTest.php
@@ -328,6 +328,119 @@ class ApiClientTest extends WC_Unit_Test_Case {
 		$this->assertLogged( 'error', 'Invalid decision value "challenge"' );
 	}

+	/**
+	 * @testdox Send Event should log session ID from nested session data structure.
+	 */
+	public function test_send_event_logs_session_id_from_nested_structure(): 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',
+			),
+		);
+
+		$result = $this->sut->send_event( 'cart_item_added', $session_data );
+
+		$this->assertSame( ApiClient::DECISION_ALLOW, $result );
+		$this->assertLogged(
+			'info',
+			'Session: nested-test-session-id',
+			array( 'source' => 'woo-fraud-protection' )
+		);
+	}
+
+	/**
+	 * @testdox Send Event should log 'unknown' session ID when session data is missing.
+	 */
+	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',
+			),
+		);
+
+		$result = $this->sut->send_event( 'cart_item_added', $session_data );
+
+		$this->assertSame( ApiClient::DECISION_ALLOW, $result );
+		$this->assertLogged(
+			'info',
+			'Session: unknown',
+			array( 'source' => 'woo-fraud-protection' )
+		);
+	}
+
+	/**
+	 * @testdox Send Event should log 'unknown' session ID when session is not an array.
+	 */
+	public function test_send_event_logs_unknown_when_session_is_not_array(): 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',
+		);
+
+		$result = $this->sut->send_event( 'cart_item_added', $session_data );
+
+		$this->assertSame( ApiClient::DECISION_ALLOW, $result );
+		$this->assertLogged(
+			'info',
+			'Session: unknown',
+			array( 'source' => 'woo-fraud-protection' )
+		);
+	}
+
 	/**
 	 * @testdox Should filter out null values from session data payload.
 	 */