* DecisionHandler class file.
declare( strict_types=1 );
namespace Automattic\WooCommerce\Internal\FraudProtection;
defined( 'ABSPATH' ) || exit;
* Handles fraud protection decision application.
* This class is responsible for:
* - Applying extension override filters for whitelisting
* - Coordinating with SessionClearanceManager to apply decisions
* @internal This class is part of the internal API and is subject to change without notice.
* Session clearance manager instance.
* @var SessionClearanceManager
private SessionClearanceManager $session_manager;
* Initialize with dependencies.
* @param SessionClearanceManager $session_manager The session clearance manager instance.
final public function init( SessionClearanceManager $session_manager ): void {
$this->session_manager = $session_manager;
* Apply a fraud protection decision.
* This method processes a decision from the API, applies any override filters,
* validates the result, and updates the session status accordingly.
* The input decision is expected to be pre-validated by ApiClient.
* 1. Apply the `woocommerce_fraud_protection_decision` filter for overrides
* 2. Validate the filtered decision (third-party filters may return invalid values)
* 3. Update session status via SessionClearanceManager
* @param string $decision The decision from the API (allow, block).
* @param array<string, mixed> $session_data The session data that was sent to the API.
* @return string The final applied decision after any filter overrides.
public function apply_decision( string $decision, array $session_data ): string {
// Validate input decision and fail open if invalid.
if ( ! $this->is_valid_decision( $decision ) ) {
FraudProtectionController::log(
sprintf( 'Invalid decision "%s" received. Defaulting to "allow".', $decision ),
array( 'session_data' => $session_data )
$decision = ApiClient::DECISION_ALLOW;
$original_decision = $decision;
* Filters the fraud protection decision before it is applied.
* This filter allows extensions to override fraud protection decisions
* to implement custom whitelisting logic. Common use cases:
* - Whitelist specific users (e.g., admins, trusted customers)
* - Whitelist specific conditions (e.g., certain IP ranges, logged-in users)
* - Integrate with external fraud detection services
* Note: This filter can only change the decision to ApiClient::VALID_DECISIONS.
* Any other value will be rejected and the original decision will be used.
* @param string $decision The decision from the API (allow, block).
* @param array<string, mixed> $session_data The session data that was analyzed.
$decision = apply_filters( 'woocommerce_fraud_protection_decision', $decision, $session_data );
// Validate filtered decision (third-party filters may return invalid values).
if ( ! $this->is_valid_decision( $decision ) ) {
FraudProtectionController::log(
sprintf( 'Filter `woocommerce_fraud_protection_decision` returned invalid decision "%s". Using original decision "%s".', $decision, $original_decision ),
'original_decision' => $original_decision,
'filtered_decision' => $decision,
'session_data' => $session_data,
$decision = $original_decision;
// Log if decision was overridden.
if ( $decision !== $original_decision ) {
FraudProtectionController::log(
sprintf( 'Decision overridden by filter `woocommerce_fraud_protection_decision`: "%s" -> "%s"', $original_decision, $decision ),
'original_decision' => $original_decision,
'final_decision' => $decision,
'session_data' => $session_data,
// Apply the decision to the session.
$this->update_session_status( $decision );
* Check if a decision value is valid.
* @param mixed $decision The decision to validate.
* @return bool True if valid, false otherwise.
private function is_valid_decision( $decision ): bool {
if ( ! is_string( $decision ) ) {
return in_array( $decision, ApiClient::VALID_DECISIONS, true );
* Update the session status based on the decision.
* Important: Once a session is blocked, it stays blocked until explicitly reset.
* This prevents race conditions where emptying the cart (done during block_session)
* causes subsequent fraud checks to return "allow" (due to lower cart value),
* which would incorrectly unblock the session.
* @param string $decision The validated decision to apply.
private function update_session_status( string $decision ): void {
// Don't overwrite a blocked session with an allow decision.
// Once blocked, a session should stay blocked until explicitly reset.
if ( ApiClient::DECISION_ALLOW === $decision && $this->session_manager->is_session_blocked() ) {
FraudProtectionController::log(
'Preserving blocked session status. Allow decision not applied to already-blocked session.'
case ApiClient::DECISION_ALLOW:
$this->session_manager->allow_session();
case ApiClient::DECISION_BLOCK:
$this->session_manager->block_session();