Files
saqel/backend/app/Services/NabehOtpService.php
T

168 lines
6.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Exception;
class NabehOtpService
{
protected string $authUrl;
protected string $sendUrl;
protected ?string $email;
protected ?string $password;
public function __construct()
{
$this->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;
}
}