configuredAppKey = getenv('GATEWAY_APP_KEY') ?: 'uruk_gateway_secret_2026'; } /** * Authenticate gateway device app_key */ private function authenticate(string $appKey): bool { return hash_equals($this->configuredAppKey, $appKey); } /** * GET /api/v1/gateway/pending-call or /pending-call.php */ public function pendingCall(Request $request): void { $deviceId = (string)$request->get('device_id'); $appKey = (string)$request->get('app_key'); if (!$this->authenticate($appKey) || empty($deviceId)) { Response::json([ 'task_id' => null, 'phone' => null, 'caller_id' => null, 'otp' => null, 'timeout_seconds' => null, 'error' => 'Unauthorized or missing device_id' ], 401); return; } $pdo = Database::getConnection(); // Update device heartbeat $this->heartbeat($pdo, $deviceId); // Fetch oldest pending FLASH_CALL task $stmt = $pdo->prepare(' SELECT id, target_phone, otp_code, timeout_seconds FROM gateway_tasks WHERE task_type = "FLASH_CALL" AND (assigned_device_id = :dev OR assigned_device_id IS NULL) AND status = "PENDING" AND created_at >= NOW() - INTERVAL 60 SECOND ORDER BY id ASC LIMIT 1 '); $stmt->execute([':dev' => $deviceId]); $task = $stmt->fetch(PDO::FETCH_ASSOC); if ($task) { // Lock task to this device $upd = $pdo->prepare('UPDATE gateway_tasks SET status = "PROCESSING", assigned_device_id = :dev WHERE id = :id'); $upd->execute([':dev' => $deviceId, ':id' => $task['id']]); Response::json([ 'task_id' => (int)$task['id'], 'phone' => $task['target_phone'], 'caller_id' => null, 'otp' => $task['otp_code'], 'timeout_seconds' => (int)($task['timeout_seconds'] ?: 25), ]); return; } Response::json([ 'task_id' => null, 'phone' => null, 'caller_id' => null, 'otp' => null, 'timeout_seconds' => null, ]); } /** * GET /api/v1/gateway/pending-sms or /pending-sms.php */ public function pendingSms(Request $request): void { $deviceId = (string)$request->get('device_id'); $appKey = (string)$request->get('app_key'); if (!$this->authenticate($appKey) || empty($deviceId)) { Response::json([ 'task_id' => null, 'phone' => null, 'caller_id' => null, 'otp' => null, 'timeout_seconds' => null, 'error' => 'Unauthorized or missing device_id' ], 401); return; } $pdo = Database::getConnection(); $this->heartbeat($pdo, $deviceId); $stmt = $pdo->prepare(' SELECT id, target_phone, otp_code, timeout_seconds FROM gateway_tasks WHERE task_type = "SMS" AND (assigned_device_id = :dev OR assigned_device_id IS NULL) AND status = "PENDING" AND created_at >= NOW() - INTERVAL 120 SECOND ORDER BY id ASC LIMIT 1 '); $stmt->execute([':dev' => $deviceId]); $task = $stmt->fetch(PDO::FETCH_ASSOC); if ($task) { $upd = $pdo->prepare('UPDATE gateway_tasks SET status = "PROCESSING", assigned_device_id = :dev WHERE id = :id'); $upd->execute([':dev' => $deviceId, ':id' => $task['id']]); Response::json([ 'task_id' => (int)$task['id'], 'phone' => $task['target_phone'], 'caller_id' => null, 'otp' => $task['otp_code'], 'timeout_seconds' => (int)($task['timeout_seconds'] ?: 30), ]); return; } Response::json([ 'task_id' => null, 'phone' => null, 'caller_id' => null, 'otp' => null, 'timeout_seconds' => null, ]); } /** * POST /api/v1/gateway/call-done or /call-done.php */ public function callDone(Request $request): void { $taskId = (int)$request->get('task_id'); $deviceId = (string)$request->get('device_id'); $appKey = (string)$request->get('app_key'); $result = (string)$request->get('result'); if (!$this->authenticate($appKey)) { Response::json(['success' => false, 'message' => 'Unauthorized'], 401); return; } $pdo = Database::getConnection(); $newStatus = (strtoupper($result) === 'SUCCESS' || strtoupper($result) === 'DONE') ? 'COMPLETED' : 'FAILED'; $stmt = $pdo->prepare(' UPDATE gateway_tasks SET status = :st, completed_at = NOW() WHERE id = :id AND assigned_device_id = :dev '); $stmt->execute([':st' => $newStatus, ':id' => $taskId, ':dev' => $deviceId]); Response::json([ 'success' => true, 'message' => "Call task {$taskId} recorded as {$newStatus}.", 'device_id' => $deviceId, ]); } /** * POST /api/v1/gateway/sms-done or /sms-done.php */ public function smsDone(Request $request): void { $taskId = (int)$request->get('task_id'); $deviceId = (string)$request->get('device_id'); $appKey = (string)$request->get('app_key'); $result = (string)$request->get('result'); if (!$this->authenticate($appKey)) { Response::json(['success' => false, 'message' => 'Unauthorized'], 401); return; } $pdo = Database::getConnection(); $newStatus = (strtoupper($result) === 'SUCCESS' || strtoupper($result) === 'SENT') ? 'COMPLETED' : 'FAILED'; $stmt = $pdo->prepare(' UPDATE gateway_tasks SET status = :st, completed_at = NOW() WHERE id = :id AND assigned_device_id = :dev '); $stmt->execute([':st' => $newStatus, ':id' => $taskId, ':dev' => $deviceId]); Response::json([ 'success' => true, 'message' => "SMS task {$taskId} recorded as {$newStatus}.", 'device_id' => $deviceId, ]); } /** * POST /api/v1/gateway/register-device or /register-device.php */ public function registerDevice(Request $request): void { $deviceId = (string)$request->get('device_id'); $phoneNumber = (string)$request->get('phone_number'); $simSlot = (int)$request->get('sim_slot', 1); $appKey = (string)$request->get('app_key'); if (!$this->authenticate($appKey) || empty($deviceId)) { Response::json(['success' => false, 'message' => 'Unauthorized or missing device_id'], 401); return; } $pdo = Database::getConnection(); $stmt = $pdo->prepare(' INSERT INTO gateway_devices (device_id, phone_number, sim_slot, status, last_heartbeat) VALUES (:did, :phone, :slot, "ACTIVE", NOW()) ON DUPLICATE KEY UPDATE phone_number = VALUES(phone_number), sim_slot = VALUES(sim_slot), status = "ACTIVE", last_heartbeat = NOW() '); $stmt->execute([ ':did' => $deviceId, ':phone' => $phoneNumber, ':slot' => $simSlot, ]); Response::json([ 'success' => true, 'message' => 'Gateway device registered and active.', 'device_id' => $deviceId, ]); } private function heartbeat(PDO $pdo, string $deviceId): void { try { $stmt = $pdo->prepare(' INSERT INTO gateway_devices (device_id, status, last_heartbeat) VALUES (:did, "ACTIVE", NOW()) ON DUPLICATE KEY UPDATE status = "ACTIVE", last_heartbeat = NOW() '); $stmt->execute([':did' => $deviceId]); } catch (\Throwable $e) { // Ignore heartbeat failures if table schema is pending } } }