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.
*/