From 926d8bc4a5300b61b86801fefaac67476f8918a5 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Wed, 17 Jun 2026 18:23:29 +0300 Subject: [PATCH] Update Siro integration: NABEH_API_KEY, nabeh/ folder paths, /api/siro/ routes --- .../app/Core/Flows/DriverRegistrationFlow.php | 289 +++++----- backend/app/Services/SiroService.php | 520 ++++++++++++++++++ backend/public/index.php | 149 +++++ 3 files changed, 816 insertions(+), 142 deletions(-) create mode 100644 backend/app/Services/SiroService.php diff --git a/backend/app/Core/Flows/DriverRegistrationFlow.php b/backend/app/Core/Flows/DriverRegistrationFlow.php index 4502fe4..3b66250 100644 --- a/backend/app/Core/Flows/DriverRegistrationFlow.php +++ b/backend/app/Core/Flows/DriverRegistrationFlow.php @@ -3,166 +3,72 @@ namespace App\Core\Flows; use App\Services\GeminiService; +use App\Services\SiroService; use App\Models\DriverOcrData; use App\Models\ChatbotRule; use App\Models\DriverReminder; +use App\Core\Database; /** * DriverRegistrationFlow * Handles step-by-step driver and vehicle registration using Gemini OCR. + * Integrates with Siro platform for driver registration across Syria, Jordan, and Egypt. */ class DriverRegistrationFlow extends BaseFlow { - private array $prompts = [ - "id_front" => << << << << << << 'id_front', + 'id_back' => 'id_back', + 'driving_license_front' => 'driver_license_front', + 'driving_license_back' => 'driver_license_back', + 'vehicle_license_front' => 'car_license_front', + 'vehicle_license_back' => 'car_license_back', + 'criminal_record' => 'criminal_record', ]; + public function __construct() + { + $this->prompts = SiroService::getDocumentPrompts($this->country); + } + public function handleStep(string $step, array $messageData, array &$context): FlowResult { $text = isset($messageData['body']) ? trim($messageData['body']) : ''; $phone = $messageData['phone']; $companyId = $context['company_id'] ?? 1; + // Detect country from phone number + $this->country = SiroService::detectCountry($phone); + $context['country'] = $this->country; + + // Set country-specific prompts + $this->prompts = SiroService::getDocumentPrompts($this->country); + + // Country name in Arabic for messages + $countryNames = [ + 'syria' => 'سوريا', + 'jordan' => 'الأردن', + 'egypt' => 'مصر', + ]; + $countryName = $countryNames[$this->country] ?? 'سوريا'; + + // App name based on country + $appNames = [ + 'syria' => 'سيرو', + 'jordan' => 'سيرو', + 'egypt' => 'سيرو', + ]; + $appName = $appNames[$this->country] ?? 'سيرو'; + // If currently postponed and user sends a message, resume the flow if ($step === 'postponed') { - // Cancel active reminder $activeReminder = DriverReminder::findActive($companyId, $phone); if ($activeReminder) { DriverReminder::update($activeReminder['id'], ['status' => 'cancelled']); } - // Restore previous step $step = $context['previous_step'] ?? 'ask_name'; } @@ -188,7 +94,6 @@ EOT $context['postpone_count'] = $postponeCount; $context['previous_step'] = $step; - // Schedule reminder $scheduledAt = date('Y-m-d H:i:s', strtotime("+{$hours} hours")); DriverReminder::saveReminder([ 'company_id' => $companyId, @@ -209,7 +114,7 @@ EOT switch ($step) { case 'start': return new FlowResult( - "أهلاً بك كابتن في خدمة تسجيل كباتن تطبيق انطلق 🚖.\nيرجى إرسال اسمك الثلاثي الكامل للبدء:", + "أهلاً بك كابتن في خدمة تسجيل كباتن تطبيق {$appName} في {$countryName} 🚖.\nيرجى إرسال اسمك الثلاثي الكامل للبدء:", "ask_name" ); @@ -306,6 +211,17 @@ EOT return new FlowResult("عذراً، فشل حفظ الصورة. الرجاء إعادة إرسال صورة الوثيقة:", "criminal_record"); } + // Upload criminal record to Siro + $fullPath = __DIR__ . '/../../../../public' . $imageUrl; + $criminalSiroUrl = SiroService::uploadDocument( + $this->country, + SiroService::formatPhone($phone, $this->country), + 'criminal_record', + $fullPath, + $messageData['imageMimeType'] + ); + $context['criminal_record_siro_url'] = $criminalSiroUrl; + // Securely save registration data to local database try { DriverOcrData::saveSecure([ @@ -332,11 +248,83 @@ EOT return new FlowResult("عذراً، حدث خطأ أثناء حفظ طلبك في قاعدة البيانات. يرجى المحاولة مرة أخرى لاحقاً.", "criminal_record"); } - return new FlowResult( - "شكراً لك كابتن، لقد تم استلام كافة المستندات والتحقق منها بنجاح. سيقوم فريق خدمة عملاء انطلق بمراجعة طلبك وتفعيل حسابك بأسرع وقت ممكن. يومك سعيد! 🚖", - "finished", - true - ); + // Register driver in Siro with Siro-hosted URLs + $docUrls = [ + 'id_front' => $context['id_front_siro_url'] ?? '', + 'id_back' => $context['id_back_siro_url'] ?? '', + 'driving_license_front' => $context['driving_license_front_siro_url'] ?? '', + 'driving_license_back' => $context['driving_license_back_siro_url'] ?? '', + 'vehicle_license_front' => $context['vehicle_license_front_siro_url'] ?? '', + 'vehicle_license_back' => $context['vehicle_license_back_siro_url'] ?? '', + 'criminal_record' => $criminalSiroUrl ?? '', + ]; + + $idOcr = $context['id_front_ocr'] ?? []; + $vlOcr = $context['vehicle_license_front_ocr'] ?? []; + $vlbOcr = $context['vehicle_license_back_ocr'] ?? []; + $dlOcr = $context['driving_license_front_ocr'] ?? []; + + $formattedPhone = SiroService::formatPhone($phone, $this->country); + $driverId = 'DRV' . date('YmdHis') . rand(100, 999); + + $driverData = [ + 'phone' => $formattedPhone, + 'password' => substr(md5($formattedPhone . time()), 0, 12), + 'first_name' => explode(' ', $context['name'] ?? '')[0] ?? $context['name'], + 'last_name' => implode(' ', array_slice(explode(' ', $context['name'] ?? ''), 1)) ?: $context['name'], + 'name_arabic' => $context['name'] ?? '', + 'national_number' => $idOcr['national_number'] ?? '', + 'birthdate' => $idOcr['dob'] ?? '', + 'address' => $idOcr['address'] ?? '', + 'id' => $driverId, + ]; + + $carData = [ + 'vin' => $vlbOcr['chassis'] ?? $vlOcr['vin'] ?? '', + 'car_plate' => $vlOcr['car_plate'] ?? '', + 'make' => $vlbOcr['make'] ?? '', + 'model' => $vlbOcr['model'] ?? '', + 'year' => $vlbOcr['year'] ?? '', + 'color' => $vlOcr['color'] ?? '', + 'color_hex' => $vlOcr['color_hex'] ?? '#000000', + 'owner' => $vlOcr['owner'] ?? '', + 'fuel' => $vlbOcr['fuel'] ?? '', + 'expiration_date' => $vlOcr['issue_date'] ?? '', + 'vehicle_category_id' => 1, + ]; + + $syroResult = SiroService::registerDriver($driverData, $carData, $docUrls, $this->country); + + $appNames = [ + 'syria' => 'سيرو', + 'jordan' => 'سيرو', + 'egypt' => 'سيرو', + ]; + $appName = $appNames[$this->country] ?? 'سيرو'; + + if ($syroResult && ($syroResult['status'] ?? '') === 'success') { + DriverOcrData::saveSecure([ + 'company_id' => $companyId, + 'phone' => $phone, + 'name' => $context['name'], + 'status' => 'registered' + ]); + + return new FlowResult( + "شكراً لك كابتن، تم تسجيلك بنجاح في تطبيق {$appName} 🚖✅\nسيتم مراجعة طلبك من قبل فريق الخدمة وتفعيل حسابك قريباً. يومك سعيد!", + "finished", + true + ); + } else { + $errMsg = $syroResult['message'] ?? 'خطأ غير معروف'; + error_log("[Registration Flow] Siro registration failed: " . json_encode($syroResult)); + + return new FlowResult( + "تم حفظ مستنداتك بنجاح في نظامنا. لكن حدث تأخير في تسجيلك على تطبيق {$appName}. سيقوم فريقنا بمراجعة بياناتك وتفعيل حسابك في أقرب وقت. شكراً لصبرك! 🙏", + "finished", + true + ); + } default: return new FlowResult("خطأ في تحديد خطوة المسار.", "finished", true); @@ -365,6 +353,23 @@ EOT return new FlowResult("عذراً، فشل حفظ الصورة. الرجاء إعادة المحاولة وإرسال الصورة:", $step); } + // Upload to Siro and store the signed URL + $fullPath = __DIR__ . '/../../../../public' . $imageUrl; + $siroUrl = SiroService::uploadDocument( + $this->country, + SiroService::formatPhone($messageData['phone'], $this->country), + $this->stepToDocType[$step] ?? $step, + $fullPath, + $messageData['imageMimeType'] + ); + if ($siroUrl) { + $context[$step . '_siro_url'] = $siroUrl; + error_log("[DriverRegistrationFlow] Uploaded {$step} to Siro: {$siroUrl}"); + } else { + $context[$step . '_siro_url'] = null; + error_log("[DriverRegistrationFlow] Warning: Failed to upload {$step} to Siro, using local URL"); + } + $companyId = $context['company_id'] ?? 1; // Check subscription limit for OCR diff --git a/backend/app/Services/SiroService.php b/backend/app/Services/SiroService.php new file mode 100644 index 0000000..7bf183f --- /dev/null +++ b/backend/app/Services/SiroService.php @@ -0,0 +1,520 @@ + self::COUNTRY_SYRIA, + '00963' => self::COUNTRY_SYRIA, + '962' => self::COUNTRY_JORDAN, + '00962' => self::COUNTRY_JORDAN, + '20' => self::COUNTRY_EGYPT, + '0020' => self::COUNTRY_EGYPT, + ]; + + private static array $countryPhonePrefixes = [ + self::COUNTRY_SYRIA => '963', + self::COUNTRY_JORDAN => '962', + self::COUNTRY_EGYPT => '20', + ]; + + private static array $countryEnvKeys = [ + self::COUNTRY_SYRIA => 'SIRO_API_URL_SYRIA', + self::COUNTRY_JORDAN => 'SIRO_API_URL_JORDAN', + self::COUNTRY_EGYPT => 'SIRO_API_URL_EGYPT', + ]; + + /** + * Detect country from phone number + */ + public static function detectCountry(string $phone): string + { + $cleaned = preg_replace('/[^0-9]/', '', $phone); + foreach (self::$countryPrefixes as $prefix => $country) { + if (strpos($cleaned, $prefix) === 0) { + return $country; + } + } + return self::COUNTRY_SYRIA; + } + + /** + * Get country-specific API base URL + */ + public static function getApiUrl(string $country): string + { + $envKey = self::$countryEnvKeys[$country] ?? self::$countryEnvKeys[self::COUNTRY_SYRIA]; + return rtrim(getenv($envKey) ?: 'https://api-syria.siromove.com/siro', '/'); + } + + /** + * Format phone number to international format for the given country + */ + public static function formatPhone(string $phone, string $country): string + { + $cleaned = preg_replace('/[^0-9]/', '', $phone); + $prefix = self::$countryPhonePrefixes[$country] ?? '963'; + + if (strpos($cleaned, $prefix) === 0) { + return $cleaned; + } + if (strpos($cleaned, '00' . $prefix) === 0) { + return $prefix . substr($cleaned, strlen($prefix) + 2); + } + if (strpos($cleaned, '0') === 0) { + return $prefix . substr($cleaned, 1); + } + return $prefix . $cleaned; + } + + /** + * Upload a document image to Siro and return the signed URL + */ + public static function uploadDocument( + string $country, + string $driverId, + string $docType, + string $imagePath, + string $imageMimeType + ): ?string { + $apiUrl = self::getApiUrl($country); + $apiKey = getenv('NABEH_API_KEY') ?: ''; + $uploadUrl = rtrim($apiUrl, '/') . '/nabeh/upload_document.php'; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $uploadUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + + $headers = [ + 'X-API-Key: ' . $apiKey, + ]; + + $body = [ + 'driver_id' => $driverId, + 'doc_type' => $docType, + ]; + + if (file_exists($imagePath)) { + $body['file'] = new \CURLFile($imagePath, $imageMimeType); + } else { + error_log("[SiroService] Image not found: {$imagePath}"); + return null; + } + + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + + $res = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpCode !== 200) { + error_log("[SiroService] Upload failed for {$docType}: HTTP {$httpCode} - {$res}"); + return null; + } + + $data = json_decode($res, true); + $fileUrl = $data['message']['file_url'] ?? null; + + if (!$fileUrl) { + error_log("[SiroService] Upload response missing file_url: {$res}"); + } + + return $fileUrl; + } + + /** + * Register a driver in Siro via the simplified Nabeh integration endpoint + */ + public static function registerDriver(array $driverData, array $carData, array $documentUrls, string $country): ?array + { + $apiUrl = self::getApiUrl($country); + $apiKey = getenv('NABEH_API_KEY') ?: ''; + $registerUrl = $apiUrl . '/nabeh/register.php'; + + $payload = array_merge($driverData, $carData, [ + 'id_front_url' => $documentUrls['id_front'] ?? '', + 'id_back_url' => $documentUrls['id_back'] ?? '', + 'driver_license_front_url' => $documentUrls['driving_license_front'] ?? '', + 'driver_license_back_url' => $documentUrls['driving_license_back'] ?? '', + 'vehicle_license_front_url' => $documentUrls['vehicle_license_front'] ?? '', + 'vehicle_license_back_url' => $documentUrls['vehicle_license_back'] ?? '', + 'criminal_record_url' => $documentUrls['criminal_record'] ?? '', + 'profile_picture' => $documentUrls['profile_picture'] ?? '', + ]); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $registerUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'X-API-Key: ' . $apiKey, + 'Content-Type: application/json', + ]); + curl_setopt($ch, CURLOPT_TIMEOUT, 60); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + + $res = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpCode !== 200) { + error_log("[SiroService] Registration failed: HTTP {$httpCode} - {$res}"); + return null; + } + + return json_decode($res, true); + } + + /** + * Check driver registration status in Siro + */ + public static function checkDriverStatus(string $phone, string $country): ?array + { + $apiUrl = self::getApiUrl($country); + $apiKey = getenv('NABEH_API_KEY') ?: ''; + $statusUrl = $apiUrl . '/nabeh/driver_status.php'; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $statusUrl . '?phone=' . urlencode($phone)); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'X-API-Key: ' . $apiKey, + ]); + curl_setopt($ch, CURLOPT_TIMEOUT, 15); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + + $res = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpCode !== 200) { + return null; + } + + return json_decode($res, true); + } + + /** + * Query Siro platform for driver info, trips, stats, or trip details + */ + public static function querySiro(string $country, array $params): ?array + { + $apiUrl = self::getApiUrl($country); + $apiKey = getenv('NABEH_API_KEY') ?: ''; + $queryUrl = $apiUrl . '/nabeh/query.php'; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $queryUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params)); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'X-API-Key: ' . $apiKey, + 'Content-Type: application/json', + ]); + curl_setopt($ch, CURLOPT_TIMEOUT, 15); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + + $res = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpCode !== 200) { + error_log("[SiroService] Query failed: HTTP {$httpCode} - {$res}"); + return null; + } + + return json_decode($res, true); + } + + /** + * Get OCR prompts for a specific country + */ + public static function getDocumentPrompts(string $country): array + { + $syriaPrompts = [ + "id_front" => << << << << << << << << << << << << << << << << << << $jordanPrompts, + self::COUNTRY_EGYPT => $egyptPrompts, + default => $syriaPrompts, + }; + } +} diff --git a/backend/public/index.php b/backend/public/index.php index feb7e81..1d45177 100644 --- a/backend/public/index.php +++ b/backend/public/index.php @@ -160,6 +160,155 @@ $router->post('/api/integrations/woocommerce/disconnect', [\App\Controllers\WooC $router->post('/api/webhooks/woocommerce', [\App\Controllers\WooCommerceController::class, 'webhook']); +// ============================================ +// Siro Integration API Endpoints +// ============================================ + +// Siro Driver Info - Returns real-time driver data to Siro +$router->post('/api/siro/driver-info', function ($request, $response) { + $apiKey = getenv('NABEH_API_KEY'); + $incomingKey = $request->getHeader('x-api-key') ?? ''; + + if (empty($apiKey) || $incomingKey !== $apiKey) { + $response->status(401)->json([ + 'status' => 'error', + 'message' => 'Unauthorized' + ]); + return; + } + + $body = $request->getBody(); + $phone = $body['phone'] ?? ''; + + if (empty($phone)) { + $response->status(400)->json([ + 'status' => 'error', + 'message' => 'Missing phone number' + ]); + return; + } + + // Find driver OCR data + $hash = \App\Core\Security::blindIndex($phone); + $record = \App\Core\Database::selectOne( + "SELECT * FROM driver_ocr_data WHERE phone_hash = ? LIMIT 1", + [$hash] + ); + + $response->json([ + 'status' => 'success', + 'data' => $record ? \App\Models\DriverOcrData::decryptRecord($record) : null + ]); +}); + +// Siro Registration Status Check +$router->get('/api/siro/registration-status', function ($request, $response) { + $apiKey = getenv('NABEH_API_KEY'); + $incomingKey = $request->getHeader('x-api-key') ?? ''; + + if (empty($apiKey) || $incomingKey !== $apiKey) { + $response->status(401)->json([ + 'status' => 'error', + 'message' => 'Unauthorized' + ]); + return; + } + + $phone = $request->get('phone') ?? ''; + + if (empty($phone)) { + $response->status(400)->json([ + 'status' => 'error', + 'message' => 'Missing phone parameter' + ]); + return; + } + + $hash = \App\Core\Security::blindIndex($phone); + $record = \App\Core\Database::selectOne( + "SELECT id, name, status, created_at, updated_at FROM driver_ocr_data WHERE phone_hash = ? LIMIT 1", + [$hash] + ); + + if (!$record) { + $response->json([ + 'status' => 'success', + 'data' => null, + 'message' => 'No registration found for this phone' + ]); + return; + } + + $response->json([ + 'status' => 'success', + 'data' => $record + ]); +}); + +// Siro Webhook - Receives driver activation confirmations from Siro +$router->post('/api/siro/webhook', function ($request, $response) { + $apiKey = getenv('NABEH_API_KEY'); + $incomingKey = $request->getHeader('x-api-key') ?? ''; + + if (empty($apiKey) || $incomingKey !== $apiKey) { + $response->status(401)->json([ + 'status' => 'error', + 'message' => 'Unauthorized' + ]); + return; + } + + $body = $request->getBody(); + $phone = $body['phone'] ?? ''; + $syroDriverId = $body['driver_id'] ?? ''; + $event = $body['event'] ?? ''; + $status = $body['status'] ?? ''; + + if (empty($phone) || empty($event)) { + $response->status(400)->json([ + 'status' => 'error', + 'message' => 'Missing required fields: phone, event' + ]); + return; + } + + error_log("[Siro Webhook] Event: {$event}, Phone: {$phone}, DriverID: {$syroDriverId}, Status: {$status}"); + + $hash = \App\Core\Security::blindIndex($phone); + + if ($event === 'driver_activated' && $status === 'actives') { + \App\Core\Database::execute( + "UPDATE driver_ocr_data SET status = 'registered', syro_driver_id = ? WHERE phone_hash = ?", + [$syroDriverId, $hash] + ); + + $response->json([ + 'status' => 'success', + 'message' => 'Driver status updated' + ]); + return; + } + + if ($event === 'driver_rejected') { + \App\Core\Database::execute( + "UPDATE driver_ocr_data SET status = 'rejected' WHERE phone_hash = ?", + [$hash] + ); + + $response->json([ + 'status' => 'success', + 'message' => 'Driver rejected' + ]); + return; + } + + $response->json([ + 'status' => 'success', + 'message' => 'Event received' + ]); +}); + + // Mock External API for Entaleq Driver Info (Used to fetch real-time driver data) $router->post('/api/external/driver-info', function ($request, $response) { $body = $request->getBody();