<?php
 namespace App\Services\Fraud\Checks; use App\Models\Order; use App\Models\OrderFraudCheck; use Carbon\Carbon; class DeviceFingerprintCheck extends BaseFraudCheck { protected int $priority = 15; protected int $maxDeviceOrdersPerDay = 15; protected int $maxClientsPerDevice = 3; public function isEnabled(): bool { if (!empty($this->moduleConfig)) { return !empty($this->moduleConfig['fingerprint_check_enabled']); } return $this->enabled; } public function getName(): string { return 'Device Fingerprint Check'; } public function getType(): string { return OrderFraudCheck::TYPE_DEVICE_FINGERPRINT; } public function getDescription(): string { return 'Checks device fingerprint for suspicious patterns and multiple account usage'; } public function check(Order $order): array { if (!empty($this->moduleConfig)) { $this->maxDeviceOrdersPerDay = (int) ($this->moduleConfig['max_device_orders_per_day'] ?? $this->maxDeviceOrdersPerDay); $this->maxClientsPerDevice = (int) ($this->moduleConfig['max_clients_per_device'] ?? $this->maxClientsPerDevice); } $deviceFingerprint = $order->device_fingerprint; if (!$deviceFingerprint) { return $this->skipped('No device fingerprint provided'); } $now = Carbon::now(); $deviceOrdersLast24h = Order::where('device_fingerprint', $deviceFingerprint) ->where('created_at', '>=', $now->copy()->subDay()) ->where('id', '!=', $order->id) ->count(); $uniqueClientsOnDevice = Order::where('device_fingerprint', $deviceFingerprint) ->where('id', '!=', $order->id) ->distinct('client_id') ->count('client_id'); $deviceOrderStats = Order::where('device_fingerprint', $deviceFingerprint) ->selectRaw('payment_status, COUNT(*) as count') ->groupBy('payment_status') ->get() ->pluck('count', 'payment_status') ->toArray(); $totalDeviceOrders = array_sum($deviceOrderStats); $failedOrdersCount = $deviceOrderStats['failed'] ?? 0; $failedOrdersRatio = $totalDeviceOrders > 0 ? ($failedOrdersCount / $totalDeviceOrders) * 100 : 0; $data = [ 'device_fingerprint' => $deviceFingerprint, 'device_orders_last_24h' => $deviceOrdersLast24h, 'unique_clients_on_device' => $uniqueClientsOnDevice, 'total_device_orders' => $totalDeviceOrders, 'failed_orders_count' => $failedOrdersCount, 'failed_orders_ratio' => round($failedOrdersRatio, 2), 'device_order_stats' => $deviceOrderStats, 'thresholds' => [ 'max_device_orders_per_day' => $this->maxDeviceOrdersPerDay, 'max_clients_per_device' => $this->maxClientsPerDevice, ], ]; if ($failedOrdersRatio >= 70 && $totalDeviceOrders >= 5) { return $this->failed( 90, "Device has very high failed payment ratio: {$failedOrdersRatio}%", $data ); } if ($deviceOrdersLast24h >= $this->maxDeviceOrdersPerDay) { return $this->failed( 85, "Device exceeded maximum orders per day: {$deviceOrdersLast24h}/{$this->maxDeviceOrdersPerDay}", $data ); } if ($uniqueClientsOnDevice >= $this->maxClientsPerDevice) { return $this->failed( 75, "Device used by too many different clients: {$uniqueClientsOnDevice}/{$this->maxClientsPerDevice}", $data ); } if ($failedOrdersRatio >= 40 && $totalDeviceOrders >= 5) { return $this->warning( 55, "Device has moderate failed payment ratio: {$failedOrdersRatio}%", $data ); } if ($deviceOrdersLast24h >= ($this->maxDeviceOrdersPerDay * 0.6)) { return $this->warning( 35, "Device has high order frequency: {$deviceOrdersLast24h} orders in last 24h", $data ); } return $this->passed('Device fingerprint looks normal', $data); } } 