getMessage()); printFailure("Database connection failed"); exit; } // ================================================================================= // 🛠️ دالة مساعدة: إرسال الرحلة لسوق السائقين (Marketplace Broadcast) // ================================================================================= function broadcastRideToMarket($rideId, $lat, $lng, $payloadData) { $url = getenv('LOCATION_SOCKET_URL'); $INTERNAL_KEY = function_exists('getInternalSocketKey') ? getInternalSocketKey() : ''; $marketPayload = [ 'id' => (string)$rideId, 'start_lat' => $lat, 'start_lng' => $lng, 'price' => $payloadData[2], 'carType' => $payloadData[31], 'startName' => $payloadData[29], 'endName' => $payloadData[30], 'distance' => $payloadData[11], 'duration' => $payloadData[15], 'passengerRate' => $payloadData[33], ]; $postData = [ 'action' => 'market_new_ride', 'payload' => $marketPayload ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT_MS, 200); if ($INTERNAL_KEY) { curl_setopt($ch, CURLOPT_HTTPHEADER, ["x-internal-key: $INTERNAL_KEY"]); } curl_exec($ch); curl_close($ch); } error_log("[add_ride] Request started. passenger_id=" . ($_POST['passenger_id'] ?? '?')); // ── 1. Input ─────────────────────────────────────────────────── $start_location = filterRequest("start_location"); $end_location = filterRequest("end_location"); $price = filterRequest("price"); $price_token = filterRequest("price_token"); // Force passenger_id from JWT — never trust user-supplied passenger_id $passenger_id = $user_id; $driver_id = filterRequest("driver_id") ?: 0; $status = filterRequest("status"); $price_for_driver = filterRequest("price_for_driver"); $price_for_passenger = filterRequest("price_for_passenger"); $distance = filterRequest("distance"); $carType = filterRequest("carType"); $passenger_name = filterRequest("passenger_name"); $passenger_phone = filterRequest("passenger_phone"); $passenger_token = filterRequest("passenger_token"); $passenger_email = filterRequest("passenger_email"); $passenger_wallet = filterRequest("passenger_wallet"); $passenger_rating = filterRequest("passenger_rating"); $start_name_loc = filterRequest("start_name"); $end_name_loc = filterRequest("end_name"); $duration_text = filterRequest("duration_text"); $distance_text = filterRequest("distance_text"); $is_wallet = filterRequest("is_wallet"); $has_steps = filterRequest("has_steps"); $step0 = filterRequest("step0"); $step1 = filterRequest("step1"); $step2 = filterRequest("step2"); $step3 = filterRequest("step3"); $step4 = filterRequest("step4"); // Helper to compare coordinates (allowing slight GPS precision drift up to ~500m) function coordsMatch($coordStr1, $coordStr2, $tolerance = 0.005) { if (empty($coordStr1) || empty($coordStr2)) return false; $c1 = array_map('floatval', explode(',', $coordStr1)); $c2 = array_map('floatval', explode(',', $coordStr2)); if (count($c1) < 2 || count($c2) < 2) return false; return (abs($c1[0] - $c2[0]) < $tolerance) && (abs($c1[1] - $c2[1]) < $tolerance); } // Validation if (empty($passenger_id) || empty($start_location) || empty($end_location) || empty($price)) { error_log("[add_ride] Validation failed — missing required fields."); printFailure("Missing required fields"); exit; } // SECURE PRICE TOKEN VERIFICATION if (empty($price_token)) { error_log("[add_ride] Security failed — price_token is missing."); printFailure("Secure price token is required"); exit; } $decrypted = isset($encryptionHelper) ? $encryptionHelper->decryptData($price_token) : false; if (!$decrypted) { error_log("[add_ride] Security failed — failed to decrypt price_token."); printFailure("Invalid or tampered price token"); exit; } $tokenData = json_decode($decrypted, true); if (!$tokenData || !isset($tokenData['expires']) || $tokenData['expires'] < time()) { error_log("[add_ride] Security failed — token is expired or invalid JSON."); printFailure("Price token has expired, please request estimation again"); exit; } if ($tokenData['passenger_id'] != $passenger_id) { error_log("[add_ride] Security failed — passenger_id mismatch."); printFailure("Tampered price token (passenger mismatch)"); exit; } if (!coordsMatch($tokenData['start_location'], $start_location) || !coordsMatch($tokenData['end_location'], $end_location)) { error_log("[add_ride] Security failed — coordinates mismatch. Token: " . ($tokenData['start_location'] . " / " . $tokenData['end_location']) . " Request: " . ($start_location . " / " . $end_location)); printFailure("Tampered price token (route mismatch)"); exit; } if (!isset($tokenData['prices'][$carType])) { error_log("[add_ride] Security failed — car type $carType not found in token."); printFailure("Invalid car type for this token"); exit; } // ✅ FIX H-05: التحقق من distance و duration في الـ token أيضاً if (isset($tokenData['distance']) && $tokenData['distance'] != $distance) { error_log("[add_ride] Security failed — distance mismatch."); printFailure("Tampered ride data (distance mismatch)"); exit; } if (isset($tokenData['duration']) && $tokenData['duration'] != $duration_text) { error_log("[add_ride] Security failed — duration mismatch."); printFailure("Tampered ride data (duration mismatch)"); exit; } // Securely override pricing from the cryptographically signed token $price = $tokenData['prices'][$carType]['price']; $price_for_driver = $tokenData['prices'][$carType]['driver_price']; $price_for_passenger = $price; // ── 2. تنسيق التواريخ ───────────────────────────────────────── $date_formatted = date("Y-m-d"); $time_formatted = date("H:i:s"); $endtime_formatted = filterRequest("endtime") ? date("H:i:s", strtotime(filterRequest("endtime"))) : "00:00:00"; // ── 3. إحداثيات البداية والنهاية ────────────────────────────── $startLat = $startLng = $endLat = $endLng = ""; if (!empty($start_location)) { [$startLat, $startLng] = array_map('trim', explode(',', $start_location, 2)); } if (!empty($end_location)) { [$endLat, $endLng] = array_map('trim', explode(',', $end_location, 2)); } // ── 4. مصفوفة بيانات الإدخال ────────────────────────────────── $insertData = [ ':start_location' => $start_location, ':end_location' => $end_location, ':date' => $date_formatted, ':time' => $time_formatted, ':endtime' => $endtime_formatted, ':price' => $price, ':passenger_id' => $passenger_id, ':driver_id' => $driver_id, ':status' => $status, ':carType' => $carType, ':price_for_driver' => $price_for_driver, ':price_for_passenger' => $price_for_passenger, ':distance' => $distance, ]; $sqlInsert = "INSERT INTO `ride` (`start_location`,`end_location`,`date`,`time`,`endtime`, `price`,`passenger_id`,`driver_id`,`status`,`carType`, `price_for_driver`,`price_for_passenger`,`distance`) VALUES (:start_location,:end_location,:date,:time,:endtime, :price,:passenger_id,:driver_id,:status,:carType, :price_for_driver,:price_for_passenger,:distance)"; try { // ═══════════════════════════════════════════════════════════ // STEP A — ride DB أولاً (هو المرجع الأساسي) // ═══════════════════════════════════════════════════════════ $stmtRide = $con_ride->prepare($sqlInsert); $stmtRide->execute($insertData); $insertedId = $con_ride->lastInsertId(); if (!$insertedId) { error_log("[add_ride] ride DB insert returned no ID."); printFailure("Failed to create ride"); exit; } error_log("[add_ride] ride DB insert success. RideID=$insertedId"); // ═══════════════════════════════════════════════════════════ // STEP B — primary DB ثانياً (نسخة أرشيفية بنفس الـ ID) // ═══════════════════════════════════════════════════════════ $sqlInsertWithId = "INSERT INTO `ride` (`id`,`start_location`,`end_location`,`date`,`time`,`endtime`, `price`,`passenger_id`,`driver_id`,`status`,`carType`, `price_for_driver`,`price_for_passenger`,`distance`) VALUES (:id,:start_location,:end_location,:date,:time,:endtime, :price,:passenger_id,:driver_id,:status,:carType, :price_for_driver,:price_for_passenger,:distance)"; try { $primaryData = $insertData; $primaryData[':id'] = $insertedId; $stmtPrimary = $con->prepare($sqlInsertWithId); $stmtPrimary->execute($primaryData); error_log("[add_ride] primary DB sync success. RideID=$insertedId"); } catch (PDOException $ePrimary) { // لا نوقف العملية — ride DB هو المرجع error_log("[add_ride] primary DB sync WARNING: " . $ePrimary->getMessage()); } // ═══════════════════════════════════════════════════════════ // STEP C — بناء الـ payload وإرسال الرحلة للسائقين // ═══════════════════════════════════════════════════════════ $kazan = (float) $price - (float) $price_for_driver; $payload = [ (string) $startLat, (string) $startLng, number_format((float) $price, 2, '.', ''), (string) $endLat, (string) $endLng, (string) $distance_text, "", (string) $passenger_id, (string) $passenger_name, (string) $passenger_token, (string) $passenger_phone, (string) $distance, "1", (string) $is_wallet, (string) $distance, (string) $duration_text, (string) $insertedId, "", "", (string) $duration_text, $has_steps ?: 'false', (string) $step0, (string) $step1, (string) $step2, (string) $step3, (string) $step4, number_format((float) $price_for_driver, 2, '.', ''), (string) $passenger_wallet, (string) $passenger_email, (string) $start_name_loc, (string) $end_name_loc, (string) $carType, number_format($kazan, 2, '.', ''), (string) $passenger_rating, ]; // Direct dispatch للسائقين القريبين $driversData = findBestDrivers($con, $startLat, $startLng, $carType); if (!empty($driversData)) { dispatchRideToDrivers($driversData, $insertedId, $payload, $start_name_loc, $encryptionHelper); error_log("[add_ride] Dispatched RideID=$insertedId to " . count($driversData) . " drivers."); } else { error_log("[add_ride] No direct drivers found for RideID=$insertedId — market only."); } // Broadcast للـ marketplace دائماً broadcastRideToMarket($insertedId, $startLat, $startLng, $payload); // رد النجاح للتطبيق printSuccess($insertedId); } catch (PDOException $e) { error_log("[add_ride] CRITICAL ride DB error: " . $e->getMessage()); printFailure("Database error"); }