fix: Fast text WhatsApp OTP by default, 50s timeout for image mode with auto-fallback

This commit is contained in:
Hamza-Ayed
2026-08-26 23:04:25 +03:00
parent cee6265930
commit f3c5f4161e
2 changed files with 40 additions and 19 deletions
+4 -2
View File
@@ -93,12 +93,14 @@ class TestController
$cleanPhone = '962' . substr($cleanPhone, 1); $cleanPhone = '962' . substr($cleanPhone, 1);
} }
$testOtp = (string)random_int(100000, 999999); $testOtp = (string)random_int(100000, 999999);
$type = $queryParams['type'] ?? 'text'; // Fast text mode by default
try { try {
$nabeh = new NabehService(); $nabeh = new NabehService();
$sendResult = $nabeh->sendOtp($cleanPhone, $testOtp, 'image', 'فحص منصة صَقِل'); $sendResult = $nabeh->sendOtp($cleanPhone, $testOtp, $type, 'فحص منصة صَقِل');
$results['live_otp_test'] = [ $results['live_otp_test'] = [
'phone' => $cleanPhone, 'phone' => $cleanPhone,
'code_sent' => $testOtp, 'code_sent' => $testOtp,
'type_used' => $type,
'send_result' => $sendResult 'send_result' => $sendResult
]; ];
} catch (\Exception $e) { } catch (\Exception $e) {
@@ -108,7 +110,7 @@ class TestController
]; ];
} }
} else { } else {
$results['live_otp_test'] = 'To test sending an actual WhatsApp OTP, add ?phone=96279XXXXXXX to this URL'; $results['live_otp_test'] = 'To test sending an actual WhatsApp OTP, add ?phone=96279XXXXXXX&type=text to this URL';
} }
$response->json([ $response->json([
+36 -17
View File
@@ -10,13 +10,15 @@ class NabehService
private string $sendUrl; private string $sendUrl;
private string $email; private string $email;
private string $password; private string $password;
private string $defaultType;
public function __construct() public function __construct()
{ {
$this->authUrl = (string)getenv('NABEH_AUTH_URL'); $this->authUrl = (string)getenv('NABEH_AUTH_URL');
$this->sendUrl = (string)getenv('NABEH_SEND_URL'); $this->sendUrl = (string)getenv('NABEH_SEND_URL');
$this->email = (string)getenv('NABEH_EMAIL'); $this->email = (string)getenv('NABEH_EMAIL');
$this->password = (string)getenv('NABEH_PASSWORD'); $this->password = (string)getenv('NABEH_PASSWORD');
$this->defaultType = (string)(getenv('NABEH_OTP_TYPE') ?: 'text');
$missing = []; $missing = [];
if (empty($this->authUrl)) $missing[] = 'NABEH_AUTH_URL'; if (empty($this->authUrl)) $missing[] = 'NABEH_AUTH_URL';
@@ -60,6 +62,7 @@ class NabehService
CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
]); ]);
$response = curl_exec($ch); $response = curl_exec($ch);
@@ -94,9 +97,10 @@ class NabehService
} }
/** /**
* Send OTP via Nabeh JWT Auth Gateway (WhatsApp Image/Text OTP) * Send OTP via Nabeh JWT Auth Gateway (WhatsApp text/image OTP)
* Text mode is ultra-fast (1-2s). Image mode generates dynamic card (10-25s).
*/ */
public function sendOtp(string $receiver, string $otp, string $method = 'image', string $appName = 'منصة صَقِل'): array public function sendOtp(string $receiver, string $otp, ?string $method = null, string $appName = 'منصة صَقِل'): array
{ {
$bearerToken = $this->getBearerToken(); $bearerToken = $this->getBearerToken();
if (!$bearerToken) { if (!$bearerToken) {
@@ -114,22 +118,26 @@ class NabehService
$phoneRaw = '962' . $phoneRaw; $phoneRaw = '962' . $phoneRaw;
} }
$type = in_array($method, ['text', 'voice', 'image'], true) ? $method : 'image'; // Selected type: provided method > defaultType > 'text'
$selectedType = $method ?: $this->defaultType;
if (!in_array($selectedType, ['text', 'voice', 'image'], true)) {
$selectedType = 'text';
}
// 1. First attempt with image // 1. First attempt with selected type (text or image)
$result = $this->attemptSend($phoneRaw, $type, $otp, $appName, $bearerToken); $result = $this->attemptSend($phoneRaw, $selectedType, $otp, $appName, $bearerToken);
if ($result['success']) { if ($result['success']) {
return $result; return $result;
} }
// 2. Fallback to text if image fails // 2. If image failed or timed out, fallback to instant text OTP
if ($type === 'image') { if ($selectedType === 'image') {
error_log("ℹ️ [Nabeh OTP Fallback] Image failed, retrying with text type for phone {$phoneRaw}..."); error_log("ℹ️ [Nabeh OTP Fallback] Image mode failed/timed out for {$phoneRaw}. Retrying instantly with Text mode...");
$textResult = $this->attemptSend($phoneRaw, 'text', $otp, $appName, $bearerToken); $textResult = $this->attemptSend($phoneRaw, 'text', $otp, $appName, $bearerToken);
if ($textResult['success']) { if ($textResult['success']) {
$textResult['note'] = 'Image OTP timed out, delivered via fast text fallback';
return $textResult; return $textResult;
} }
// Return text attempt with details
return $textResult; return $textResult;
} }
@@ -145,36 +153,43 @@ class NabehService
'message' => "رمز التحقق الخاص بك لمنصة {$appName} هو: *{$otp}* \n الرجاء عدم مشاركته مع أي شخص لحماية حسابك.", 'message' => "رمز التحقق الخاص بك لمنصة {$appName} هو: *{$otp}* \n الرجاء عدم مشاركته مع أي شخص لحماية حسابك.",
]); ]);
$timeout = ($type === 'image') ? 50 : 25; // 50s for image rendering, 25s for text
$ch = curl_init($this->sendUrl); $ch = curl_init($this->sendUrl);
curl_setopt_array($ch, [ curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload, CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 35, CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_HTTPHEADER => [ CURLOPT_HTTPHEADER => [
'Content-Type: application/json', 'Content-Type: application/json',
"Authorization: Bearer {$bearerToken}", "Authorization: Bearer {$bearerToken}",
], ],
]); ]);
$startTime = microtime(true);
$response = curl_exec($ch); $response = curl_exec($ch);
$duration = round(microtime(true) - $startTime, 2);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch); $curlError = curl_error($ch);
curl_close($ch); curl_close($ch);
if ($curlError) { if ($curlError) {
error_log("❌ [Nabeh OTP cURL Error] " . $curlError); error_log("❌ [Nabeh OTP cURL Error (type={$type}, duration={$duration}s)] " . $curlError);
return [ return [
'success' => false, 'success' => false,
'type_used' => $type,
'duration' => "{$duration}s",
'http_code' => $httpCode, 'http_code' => $httpCode,
'error' => 'cURL Connection Error: ' . $curlError, 'error' => 'cURL Error: ' . $curlError,
'response' => null 'response' => null
]; ];
} }
error_log("ℹ️ [Nabeh OTP Response (HTTP {$httpCode})] " . $response); error_log("ℹ️ [Nabeh OTP Response (type={$type}, duration={$duration}s, HTTP {$httpCode})] " . $response);
if ($httpCode === 200 && $response) { if ($httpCode === 200 && $response) {
$decoded = json_decode($response, true); $decoded = json_decode($response, true);
@@ -198,6 +213,8 @@ class NabehService
) { ) {
return [ return [
'success' => true, 'success' => true,
'type_used' => $type,
'duration' => "{$duration}s",
'http_code' => 200, 'http_code' => 200,
'message' => 'تم إرسال رمز التحقق بنجاح عبر الواتساب', 'message' => 'تم إرسال رمز التحقق بنجاح عبر الواتساب',
'raw' => $decoded 'raw' => $decoded
@@ -208,6 +225,8 @@ class NabehService
return [ return [
'success' => false, 'success' => false,
'type_used' => $type,
'duration' => "{$duration}s",
'http_code' => $httpCode, 'http_code' => $httpCode,
'error' => 'Nabeh Gateway rejected request (HTTP ' . $httpCode . ')', 'error' => 'Nabeh Gateway rejected request (HTTP ' . $httpCode . ')',
'response' => $response 'response' => $response