credentialsPath = config('intaleq.fcm_credentials_path'); $this->cachePath = config('intaleq.fcm_cache_path'); } /** * Send localized FCM notification using translation keys (Best for multi-language support) */ /** * Send FCM notification to a specific device token (Raw Text) */ public function sendToDevice(string $token, string $title, string $body, array $data = [], string $category = ''): array { if (empty($token)) { return ['status' => 'error', 'message' => 'Empty token']; } if ($category) { $data['category'] = $category; } $stringData = []; foreach ($data as $key => $value) { $stringData[$key] = is_array($value) ? json_encode($value) : (string) $value; } $payload = [ 'message' => [ 'token' => $token, 'notification' => [ 'title' => $title, 'body' => $body, ], 'data' => $stringData, 'android' => [ 'priority' => 'high', ], 'apns' => [ 'payload' => [ 'aps' => [ 'sound' => 'default', 'badge' => 1, ], ], ], ], ]; return $this->sendRequest($payload); } /** * Send localized FCM notification using translation keys (Best for multi-language support) */ public function sendLocalizedToDevice(string $token, string $titleKey, string $bodyKey, array $data = [], string $category = ''): array { if (empty($token)) { return ['status' => 'error', 'message' => 'Empty token']; } if ($category) { $data['category'] = $category; } $stringData = []; foreach ($data as $key => $value) { $stringData[$key] = is_array($value) ? json_encode($value) : (string) $value; } $payload = [ 'message' => [ 'token' => $token, 'data' => $stringData, 'android' => [ 'priority' => 'high', 'notification' => [ 'title_loc_key' => $titleKey, 'body_loc_key' => $bodyKey, ], ], 'apns' => [ 'payload' => [ 'aps' => [ 'sound' => 'default', 'badge' => 1, 'alert' => [ 'title-loc-key' => $titleKey, 'loc-key' => $bodyKey, ], ], ], ], ], ]; return $this->sendRequest($payload); } /** * Send to FCM topic */ public function sendToTopic(string $topic, string $title, string $body, array $data = []): array { $payload = [ 'message' => [ 'topic' => $topic, 'notification' => [ 'title' => $title, 'body' => $body, ], 'data' => array_map('strval', $data), ], ]; return $this->sendRequest($payload); } private function sendRequest(array $payload): array { $accessToken = $this->getAccessToken(); if (!$accessToken) { return ['status' => 'error', 'message' => 'Failed to get access token']; } $credentials = json_decode(file_get_contents($this->credentialsPath), true); $projectId = $credentials['project_id'] ?? ''; $url = "https://fcm.googleapis.com/v1/projects/{$projectId}/messages:send"; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$accessToken}", 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, ]); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode === 200) { return ['status' => 'success', 'response' => json_decode($result, true)]; } Log::error("[FCM] Error {$httpCode}: {$result}"); return ['status' => 'error', 'code' => $httpCode, 'response' => $result]; } private function getAccessToken(): ?string { // Check cache if (file_exists($this->cachePath)) { $cached = json_decode(file_get_contents($this->cachePath), true); if ($cached && ($cached['expires_at'] ?? 0) > time() + 60) { return $cached['token']; } } if (!file_exists($this->credentialsPath)) return null; $credentials = json_decode(file_get_contents($this->credentialsPath), true); $clientEmail = $credentials['client_email']; $privateKey = $credentials['private_key']; $now = time(); $header = rtrim(strtr(base64_encode(json_encode(['alg' => 'RS256', 'typ' => 'JWT'])), '+/', '-_'), '='); $claim = rtrim(strtr(base64_encode(json_encode([ 'iss' => $clientEmail, 'scope' => 'https://www.googleapis.com/auth/firebase.messaging', 'aud' => 'https://oauth2.googleapis.com/token', 'exp' => $now + 3600, 'iat' => $now, ])), '+/', '-_'), '='); $signature = ''; openssl_sign("{$header}.{$claim}", $signature, $privateKey, 'SHA256'); $jwt = "{$header}.{$claim}." . rtrim(strtr(base64_encode($signature), '+/', '-_'), '='); $ch = curl_init('https://oauth2.googleapis.com/token'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query([ 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', 'assertion' => $jwt, ]), CURLOPT_RETURNTRANSFER => true, ]); $res = curl_exec($ch); curl_close($ch); $token = json_decode($res, true)['access_token'] ?? null; if ($token) { file_put_contents($this->cachePath, json_encode([ 'token' => $token, 'expires_at' => time() + 3500, ])); } return $token; } }