595 lines
16 KiB
PHP
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);
|
|
}
|
|
}
|