"failure", "message" => "Country parameter is required"]); exit; } $prices = []; $pricesRaw = []; $categories = [ 'totalPassengerSpeed' => 'Speed', 'totalPassengerBalash' => 'Awfar Car', 'totalPassengerComfort' => 'Comfort', 'totalPassengerElectric' => 'Electric', 'totalPassengerLady' => 'Lady', 'totalPassengerScooter' => 'Delivery', 'totalPassengerVan' => 'Van', 'totalPassengerRayehGai' => 'Speed', 'totalPassengerRayehGaiComfort' => 'Comfort', 'totalPassengerRayehGaiBalash' => 'Awfar Car', ]; // Common variables date_default_timezone_set('Asia/Damascus'); $currentTime = new DateTime(); $hour = (int)$currentTime->format('H'); switch ($country) { case 'Syria': $minFare = 150.0; break; case 'Egypt': $minFare = 20.0; break; case 'Jordan': $minFare = 1.0; break; default: $minFare = 0.0; break; } // Fetch kazan from DB for the specified country $sql = "SELECT * FROM `kazan` WHERE country = :country LIMIT 1"; $stmt = $con->prepare($sql); $stmt->execute([':country' => $country]); $kazanRow = $stmt->fetch(PDO::FETCH_ASSOC); if (!$kazanRow) { echo json_encode(["status" => "failure", "message" => "No pricing available for this country"]); exit; } // ---------------------------------------------------------------------- // Helper Functions for Countries // ---------------------------------------------------------------------- function getPerKmRate($carType, $kazanRow) { $rateColumns = [ 'Comfort' => 'comfortPrice', 'Speed' => 'speedPrice', 'Lady' => 'ladyPrice', 'Electric' => 'electricPrice', 'Van' => 'vanPrice', 'Delivery' => 'deliveryPrice', 'Mishwar Vip' => 'mishwarVipPrice', 'Fixed Price' => 'fixedPrice', 'Awfar Car' => 'awfarPrice', ]; $column = $rateColumns[$carType] ?? 'speedPrice'; $rate = floatval($kazanRow[$column] ?? 0); if ($rate <= 0) { $oldColumnMap = [ 'Lady' => 'familyPrice', 'Mishwar Vip' => 'freePrice', 'Electric' => 'naturePrice', 'Van' => 'heavyPrice', ]; $oldColumn = $oldColumnMap[$carType] ?? null; if ($oldColumn && isset($kazanRow[$oldColumn])) { $rate = floatval($kazanRow[$oldColumn]); } } if ($rate <= 0) { $rate = floatval($kazanRow['speedPrice'] ?? 36); } return $rate; } function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanRow, $startNameAddress, $endNameAddress, $destLat, $destLng, $passengerLat, $passengerLng, $carType = 'Speed') { global $redis, $redisLocation; $surgeMultiplier = 1.0; if (isset($redis) && $redis !== null) { try { $grid_size = 0.0135; $grid_lat = round((float)$passengerLat / $grid_size) * $grid_size; $grid_lng = round((float)$passengerLng / $grid_size) * $grid_size; $grid_id = $grid_lat . "_" . $grid_lng; // Demand is handled by Main Redis (prefix automatically applied) $demandCount = (int)$redis->get("demand:grid:" . $grid_id); $availableDrivers = 0; // Driver locations are handled by Location Redis (no prefix) try { if (isset($redisLocation) && $redisLocation !== null) { $drivers = $redisLocation->georadius('geo:drivers:available', $grid_lng, $grid_lat, 0.75, 'km'); $availableDrivers = count($drivers); } } catch (Exception $e) {} if ($demandCount > 0) { $surgeRatio = ($availableDrivers > 0) ? ($demandCount / $availableDrivers) : $demandCount; if ($surgeRatio > 1.2) { $surgeMultiplier = 1.0 + ($surgeRatio - 1.2) * 0.5; $surgeMultiplier = min(3.0, $surgeMultiplier); // Cap at 3.0 } } } catch (Exception $e) {} } $naturePrice = (float) ($kazanRow['naturePrice'] ?? 0); $heavyPrice = (float) ($kazanRow['heavyPrice'] ?? 0); $latePrice = (float) ($kazanRow['latePrice'] ?? 0); $kazanPercent = (float) ($kazanRow['kazan'] ?? 10); // === General Settings === $minBillableKm = 0.2; $airportAddon = 0.0; $damascusAirportBoundAddon = 0.0; switch ($country) { case 'Egypt': $airportAddon = 35.0; break; case 'Jordan': $airportAddon = 5.0; break; default: // Syria $airportAddon = 200.0; $damascusAirportBoundAddon = 1400.0; break; } $longSpeedThresholdKm = 40.0; $longSpeedPerKm = 26.0; $mediumDistThresholdKm = 25.0; $longDistThresholdKm = 35.0; $longTripPerMin = 6.0; $minuteCapMedium = 60; $minuteCapLong = 80; $freeMinutesLong = 10; $extraReduction100 = 0.07; $maxReductionCap = 0.35; $totalMinutes = floor($duration / 60); $airportCtx = (stripos($startNameAddress, 'airport') !== false || stripos($startNameAddress, 'مطار') !== false || stripos($endNameAddress, 'airport') !== false || stripos($endNameAddress, 'مطار') !== false); $clubCtx = (stripos($startNameAddress, 'club') !== false || stripos($startNameAddress, 'ديسكو') !== false || stripos($endNameAddress, 'club') !== false || stripos($endNameAddress, 'ديسكو') !== false); $northLat = 33.415313; $southLat = 33.400265; $eastLng = 36.531505; $westLng = 36.499687; $damascusAirportBoundCtx = ($destLat <= $northLat && $destLat >= $southLat && $destLng <= $eastLng && $destLng >= $westLng); $isInDamascusAirportBoundCtx = ($passengerLat <= $northLat && $passengerLat >= $southLat && $passengerLng <= $eastLng && $passengerLng >= $westLng); $billableDistance = ($distance < $minBillableKm) ? $minBillableKm : $distance; $isLongSpeed = $billableDistance > $longSpeedThresholdKm; $perKmSpeedBaseFromServer = getPerKmRate($carType, $kazanRow); $perKmSpeed = $isLongSpeed ? $longSpeedPerKm : $perKmSpeedBaseFromServer; $reductionPct40 = 0.0; if ($perKmSpeedBaseFromServer > 0) { $r = 1.0 - ($longSpeedPerKm / $perKmSpeedBaseFromServer); $reductionPct40 = max(0.0, min($maxReductionCap, $r)); } $reductionPct100 = max(0.0, min($maxReductionCap, $reductionPct40 + $extraReduction100)); $distanceReduction = 0.0; if ($billableDistance > 100.0) { $distanceReduction = $reductionPct100; } else if ($billableDistance > 40.0) { $distanceReduction = $reductionPct40; } date_default_timezone_set('Asia/Damascus'); $hour = (int)date('H'); $effectivePerMin = $naturePrice; if ($hour >= 21 || $hour < 1) { $effectivePerMin = $latePrice; } else if ($hour >= 1 && $hour < 5) { $effectivePerMin = $clubCtx ? ($latePrice * 2) : $latePrice; } else if ($hour >= 14 && $hour <= 17) { $effectivePerMin = $heavyPrice; } $billableMinutes = $totalMinutes; if ($billableDistance > $longDistThresholdKm) { $effectivePerMin = $longTripPerMin; $capped = ($billableMinutes > $minuteCapLong) ? $minuteCapLong : $billableMinutes; $billableMinutes = max(0, $capped - $freeMinutesLong); } else if ($billableDistance > $mediumDistThresholdKm) { $effectivePerMin = $longTripPerMin; $billableMinutes = ($billableMinutes > $minuteCapMedium) ? $minuteCapMedium : $billableMinutes; } $fare = $billableDistance * $perKmSpeed; $fare += $billableMinutes * $effectivePerMin; // Apply Redis Geohash Surge Multiplier $fare *= $surgeMultiplier; if ($airportCtx) $fare += $airportAddon; if ($damascusAirportBoundCtx || $isInDamascusAirportBoundCtx) { $fare += $damascusAirportBoundAddon; } $price = max($fare, $minFare); // Apply kazan (e.g. 11%) $withCommission = ceil($price * (1 + $kazanPercent / 100)); $kazan = $withCommission - $price; $price_for_driver = $price; return [ 'price' => $price, 'price_for_driver' => $price_for_driver, 'withCommission' => $withCommission, 'kazan' => $kazan, 'isNightFare' => false ]; } // 2. Validate Promo Code $discount = 0; if (!empty($promo_code)) { $sqlPromo = "SELECT amount FROM `promos` WHERE promo_code = :promo_code AND (passengerID = :passenger_id OR passengerID LIKE '%all%') AND validity_start_date <= CURDATE() AND validity_end_date >= CURDATE()"; $stmtPromo = $con->prepare($sqlPromo); $stmtPromo->execute([ ':promo_code' => $promo_code, ':passenger_id' => $passenger_id ]); if ($stmtPromo->rowCount() > 0) { $promoData = $stmtPromo->fetch(PDO::FETCH_ASSOC); $discount = (float) $promoData['amount']; } } // 3. Fetch Passenger Wallet (Negative Balance / Debt) $negativeBalance = 0; if (!empty($passenger_id)) { try { $redisInstance = null; if (isset($redis) && $redis !== null) { $redisInstance = $redis; } else if (extension_loaded('redis')) { $localRedis = new Redis(); $redisHost = getenv('REDIS_MAIN_HOST') ?: getenv('REDIS_HOST') ?: '127.0.0.1'; $redisPort = (int)(getenv('REDIS_MAIN_PORT') ?: getenv('REDIS_PORT') ?: 6379); $redisPass = getenv('REDIS_MAIN_PASSWORD') ?: getenv('REDIS_MAIN_AUTH') ?: getenv('REDIS_PASSWORD') ?: getenv('REDIS_AUTH'); if ($localRedis->connect($redisHost, $redisPort, 1.5)) { if ($redisPass) $localRedis->auth($redisPass); $localRedis->setOption(Redis::OPT_PREFIX, 'siro:'); $redisInstance = $localRedis; } } if ($redisInstance !== null) { $redisKey = "passenger_debt_" . $passenger_id; $redisDebt = $redisInstance->get($redisKey); if ($redisDebt !== false) { $negativeBalance = (float) $redisDebt; } } } catch (Exception $e) { $negativeBalance = 0; } } // Calculate prices for all categories foreach ($categories as $key => $carType) { $result = calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanRow, $startNameAddress, $endNameAddress, $destLat, $destLng, $passengerLat, $passengerLng, $carType); $withCommission = $result['withCommission']; $price_for_driver = $result['price_for_driver']; // Apply discount if ($discount > 0 && $discount <= 100) { $finalPrice = max(0, $withCommission - ($withCommission * ($discount / 100))); } else { $finalPrice = max(0, $withCommission - $discount); } // Add negative balance $finalPrice += $negativeBalance; $prices[$key] = $finalPrice; // For the token, we map the clean database carType to the final price and driver price $pricesRaw[$carType] = [ 'price' => $finalPrice, 'driver_price' => $price_for_driver ]; } // 4. Generate Cryptographically Signed Token $priceToken = ""; if (isset($encryptionHelper)) { $tokenPayload = [ 'passenger_id' => $passenger_id, 'start_location' => $passengerLat . ',' . $passengerLng, 'end_location' => $destLat . ',' . $destLng, // ✅ FIX R6: تضمين distance و duration في الـ token لمنع التلاعب 'distance' => $distance, 'duration' => $duration, 'expires' => time() + 180, // Valid for 3 minutes 'prices' => $pricesRaw ]; $priceToken = $encryptionHelper->encryptData(json_encode($tokenPayload)); } echo json_encode([ 'status' => 'success', 'data' => $prices, 'price_token' => $priceToken, 'applied_discount' => $discount, 'added_negative_balance' => $negativeBalance ]); ?>