Files
nabeh/backend/app/Services/SiroService.php
2026-06-18 16:46:51 +03:00

595 lines
16 KiB
PHP

<?php
namespace App\Services;
/**
* SiroService
* Handles communication between Nabeh and the Siro ride-hailing platform APIs.
* Supports Syria, Jordan, and Egypt with country detection from phone numbers.
*/
class SiroService
{
private const COUNTRY_SYRIA = 'syria';
private const COUNTRY_JORDAN = 'jordan';
private const COUNTRY_EGYPT = 'egypt';
private static array $countryPrefixes = [
'963' => 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" => <<<EOT
You are an OCR expert for Syrian national ID cards (green card).
### TASK
Analyse the **front side** of the ID and return **raw JSON only** with exactly these keys:
{
"full_name": "",
"national_number": "",
"dob": "YYYY-MM-DD",
"address": ""
}
### RULES
* Read the red number on the bottom of the card.
* Convert any Eastern-Arabic digits to Western-Arabic digits.
* Return valid JSON only no extra keys, no markdown.
EOT,
"id_back" => <<<EOT
أنت خبير OCR مختص ببطاقات الهوية السورية (الوجه الخلفي).
### المطلوب
حلّل صورة الوجه الخلفي للهوية السورية وأعد **JSON صِرف**:
{
"governorate": "",
"address": "",
"gender": "",
"issue_date": "YYYY-MM-DD"
}
### القواعد
1. حوّل أي أرقام عربية شرقية إلى أرقام لاتينية.
2. لا تُرجع أي مفاتيح إضافية أو شروح.
EOT,
"driving_license_front" => <<<EOT
You are an OCR expert for Syrian documents.
### TASK
Analyse the **front side of a Syrian driving licence** and return **clean JSON only**:
{
"name_arabic": "",
"birth_place": "",
"birth_year": "",
"national_number": "",
"civil_registry": "",
"blood_type": ""
}
EOT,
"driving_license_back" => <<<EOT
You are an OCR expert for Syrian driving licences.
### TASK
Analyse the **back side** of a Syrian driving licence:
{
"issue_date": "YYYY-MM-DD",
"expiry_date": "YYYY-MM-DD",
"license_number": "",
"license_category": ""
}
EOT,
"vehicle_license_front" => <<<EOT
You are an OCR expert for Syrian orange vehicle cards.
### TASK
Analyse the **front side**:
{
"car_plate": "",
"owner": "",
"vin": "",
"color": "",
"color_hex": "",
"issue_date": "YYYY-MM-DD",
"inspection_date": "YYYY-MM-DD"
}
EOT,
"vehicle_license_back" => <<<EOT
You are an OCR expert for Syrian orange vehicle cards.
### TASK
Analyse the **back side**:
{
"make": "",
"model": "",
"year": "",
"fuel": "",
"chassis": ""
}
EOT,
];
$jordanPrompts = [
"id_front" => <<<EOT
You are an OCR expert for Jordanian national ID cards.
### TASK
Analyse the **front side** of the Jordanian ID and return **raw JSON only**:
{
"full_name": "",
"national_number": "",
"dob": "YYYY-MM-DD",
"address": ""
}
Extract the national number from the ID. Return valid JSON only.
EOT,
"id_back" => <<<EOT
You are an OCR expert for Jordanian national ID cards.
### TASK
Analyse the **back side** of the Jordanian ID:
{
"governorate": "",
"gender": "",
"issue_date": "YYYY-MM-DD",
"expiry_date": "YYYY-MM-DD"
}
EOT,
"driving_license_front" => <<<EOT
You are an OCR expert for Jordanian driving licences.
### TASK
Analyse the **front side**:
{
"name_arabic": "",
"license_number": "",
"issue_date": "YYYY-MM-DD",
"expiry_date": "YYYY-MM-DD",
"license_category": ""
}
EOT,
"driving_license_back" => <<<EOT
You are an OCR expert for Jordanian driving licences.
### TASK
Analyse the **back side**:
{
"blood_type": "",
"birth_place": "",
"notes": ""
}
EOT,
"vehicle_license_front" => <<<EOT
You are an OCR expert for Jordanian vehicle registration cards.
### TASK
Analyse the **front side**:
{
"car_plate": "",
"owner": "",
"vin": "",
"color": "",
"make": "",
"model": "",
"year": ""
}
EOT,
"vehicle_license_back" => <<<EOT
You are an OCR expert for Jordanian vehicle cards.
### TASK
Analyse the **back side**:
{
"fuel": "",
"chassis": "",
"inspection_date": "YYYY-MM-DD"
}
EOT,
];
$egyptPrompts = [
"id_front" => <<<EOT
You are an OCR expert for Egyptian national ID cards.
### TASK
Analyse the **front side** of the Egyptian ID:
{
"full_name": "",
"national_number": "",
"dob": "YYYY-MM-DD",
"address": ""
}
EOT,
"id_back" => <<<EOT
You are an OCR expert for Egyptian national ID cards.
### TASK
Analyse the **back side**:
{
"governorate": "",
"gender": "",
"issue_date": "YYYY-MM-DD",
"expiry_date": "YYYY-MM-DD"
}
EOT,
"driving_license_front" => <<<EOT
You are an OCR expert for Egyptian driving licences.
### TASK
Analyse the **front side**:
{
"name_arabic": "",
"license_number": "",
"issue_date": "YYYY-MM-DD",
"expiry_date": "YYYY-MM-DD",
"license_category": ""
}
EOT,
"driving_license_back" => <<<EOT
You are an OCR expert for Egyptian driving licences.
### TASK
Analyse the **back side**:
{
"blood_type": "",
"address": "",
"birth_place": ""
}
EOT,
"vehicle_license_front" => <<<EOT
You are an OCR expert for Egyptian vehicle registration cards.
### TASK
Analyse the **front side**:
{
"car_plate": "",
"owner": "",
"vin": "",
"color": "",
"make": "",
"model": "",
"year": ""
}
EOT,
"vehicle_license_back" => <<<EOT
You are an OCR expert for Egyptian vehicle cards.
### TASK
Analyse the **back side**:
{
"fuel": "",
"chassis": "",
"registration_date": "YYYY-MM-DD"
}
EOT,
];
return match ($country) {
self::COUNTRY_JORDAN => $jordanPrompts,
self::COUNTRY_EGYPT => $egyptPrompts,
default => $syriaPrompts,
};
}
/**
* Fetch recent rides for a user from Siro
*/
public static function getUserRides(string $country, string $phone, int $limit = 5): ?array
{
$apiUrl = self::getApiUrl($country);
$apiKey = getenv('NABEH_API_KEY') ?: '';
$ridesUrl = $apiUrl . '/nabeh/get_user_rides.php';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ridesUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'phone' => $phone,
'limit' => $limit,
]));
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] getUserRides failed: HTTP {$httpCode} - {$res}");
return null;
}
$data = json_decode($res, true);
return $data['rides'] ?? $data['data'] ?? null;
}
/**
* Submit a complaint with AI analysis via Siro
*/
public static function submitComplaint(string $country, string $phone, string $rideId, string $complaintText): ?array
{
$apiUrl = self::getApiUrl($country);
$apiKey = getenv('NABEH_API_KEY') ?: '';
$complaintUrl = $apiUrl . '/nabeh/submit_complaint.php';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $complaintUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'phone' => $phone,
'ride_id' => $rideId,
'complaint_text' => $complaintText,
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: ' . $apiKey,
'Content-Type: application/json',
]);
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] submitComplaint failed: HTTP {$httpCode} - {$res}");
return null;
}
return json_decode($res, true);
}
}