authUrl = config('services.nabeh.auth_url', 'https://nabeh.intaleqapp.com/api/auth/login'); $this->sendUrl = config('services.nabeh.send_url', 'https://nabeh.intaleqapp.com/api/otp/send'); $this->email = config('services.nabeh.email', env('NABEH_EMAIL')); $this->password = config('services.nabeh.password', env('NABEH_PASSWORD')); } /** * Retrieve Nabeh JWT Bearer Token, caching it in Redis for 24 hours. */ public function getBearerToken(): ?string { // 1. Try fetching from Redis first try { $cachedToken = Redis::get('nabeh_bearer_token'); if ($cachedToken) { return (string)$cachedToken; } } catch (Exception $e) { Log::warning("⚠️ [Nabeh Auth Redis] Error reading token: " . $e->getMessage()); } // 2. Token not cached, authenticate via Nabeh Login API if (!$this->email || !$this->password) { Log::error("❌ [Nabeh Auth] Missing NABEH_EMAIL or NABEH_PASSWORD environment variables."); return null; } try { $response = Http::timeout(10)->post($this->authUrl, [ 'email' => $this->email, 'password' => $this->password, ]); if ($response->successful()) { $decoded = $response->json(); $token = $decoded['token'] ?? $decoded['message']['token'] ?? $decoded['jwt'] ?? $decoded['access_token'] ?? null; if ($token) { // 3. Cache token in Redis for 24h try { Redis::setex('nabeh_bearer_token', 86400, (string)$token); Log::info("[Nabeh Auth] Token cached in Redis successfully."); } catch (Exception $e) { Log::warning("⚠️ [Nabeh Auth Redis Cache Save] Error saving token: " . $e->getMessage()); } return (string)$token; } Log::error("❌ [Nabeh Auth Login Failed] Response without token: " . $response->body()); } else { Log::error("❌ [Nabeh Auth Login Failed] Status: " . $response->status() . " | Body: " . $response->body()); } } catch (Exception $e) { Log::error("❌ [Nabeh Auth Exception] " . $e->getMessage()); } return null; } /** * Send OTP via Nabeh JWT Auth Gateway (WhatsApp Image/Text OTP) * * @param string $receiver Recipient phone number * @param string $otp 4-6 digit verification code * @param string $method image | text | voice * @param string $appName Application name to appear in the message * @return bool */ public function sendOtp(string $receiver, string $otp, string $method = 'image', string $appName = 'منصة صَقِل'): bool { $bearerToken = $this->getBearerToken(); if (!$bearerToken) { Log::error("⚠️ [Nabeh OTP] Failed to obtain dynamic JWT Bearer token."); return false; } // Strip symbols from phone $phoneRaw = preg_replace('/\D+/', '', $receiver); // Type mapping (Image OTP card is default for Nabeh) $type = in_array($method, ['text', 'voice', 'image'], true) ? $method : 'image'; // First attempt with the chosen type $success = $this->attemptSend($phoneRaw, $type, $otp, $appName, $bearerToken); if ($success) { return true; } // Fallback: if image failed, retry with text if ($type === 'image') { Log::info("ℹ️ [Nabeh OTP Fallback] Image failed, retrying with text type..."); return $this->attemptSend($phoneRaw, 'text', $otp, $appName, $bearerToken); } return false; } /** * Internal helper: single OTP send attempt to Nabeh */ protected function attemptSend(string $phone, string $type, string $otp, string $appName, string $bearerToken): bool { $payload = [ 'phone' => $phone, 'type' => $type, 'code' => $otp, 'message' => "رمز التحقق الخاص بك لمنصة {$appName} هو: *{code}* \n الرجاء عدم مشاركته مع أي شخص لحماية حسابك.", ]; try { $response = Http::withToken($bearerToken) ->timeout(10) ->post($this->sendUrl, $payload); Log::info("ℹ️ [Nabeh OTP Response type={$type}] " . $response->body()); if ($response->successful()) { $decoded = $response->json(); if ($decoded) { $statusStr = strtolower((string)($decoded['status'] ?? '')); $msgStr = strtolower((string)($decoded['message'] ?? '')); $errStr = strtolower((string)($decoded['error'] ?? '')); if ( !empty($decoded['success']) || in_array($statusStr, ['success', 'ok', 'true', '200', 'sent', 'queued', '1'], true) || ($decoded['status'] ?? false) === true || ($decoded['code'] ?? 0) === 200 || !empty($decoded['message_id']) || !empty($decoded['id']) || !empty($decoded['token']) || str_contains($msgStr, 'success') || str_contains($msgStr, 'sent') || str_contains($msgStr, 'تم') || str_contains($errStr, 'via gateway') ) { return true; } } Log::warning("⚠️ [Nabeh OTP Response not matching success] " . $response->body()); } else { Log::error("❌ [Nabeh OTP Request Failed] Status: " . $response->status() . " | Body: " . $response->body()); } } catch (Exception $e) { Log::error("❌ [Nabeh OTP Exception] " . $e->getMessage()); } return false; } }