prevent404(); // Set up query vars for backward compatibility $this->setQueryVars([ 'yatra_page' => $base, ]); // Hydrate global $booking from session so templates don't crash $booking = null; if (function_exists('yatra_get_booking_session')) { $session = yatra_get_booking_session(); // If no session exists, initialize with defaults for display purposes // This allows logged-out users to see pricing without "Contact for pricing" error if (empty($session) || empty($session['trip_id'])) { // Try to get trip_id from URL parameters (for direct booking page access) $trip_id = isset($_GET['trip_id']) ? (int) $_GET['trip_id'] : null; if ($trip_id) { // Initialize minimal session for pricing display $session = [ 'trip_id' => $trip_id, 'travelers' => 1, // Default to 1 traveler 'travel_date' => '', 'timestamp' => time(), ]; } elseif (function_exists('yatra_has_remaining_session') && yatra_has_remaining_session()) { // "Pay remaining balance" from My Account clears booking session and only sets // yatra remaining session — hydrate a virtual booking session so checkout can render. $rem = yatra_get_remaining_session(); if (!empty($rem['trip_id'])) { if (function_exists('yatra_start_session')) { yatra_start_session(); } $session = [ 'trip_id' => (int) $rem['trip_id'], 'travelers' => max(1, (int) ($rem['travelers'] ?? 1)), 'travel_date' => is_string($rem['travel_date'] ?? null) ? $rem['travel_date'] : '', 'timestamp' => time(), 'remaining_amount' => isset($rem['remaining_amount']) ? (float) $rem['remaining_amount'] : null, 'amount_paid' => isset($rem['amount_paid']) ? (float) $rem['amount_paid'] : null, 'total_amount' => isset($rem['total_amount']) ? (float) $rem['total_amount'] : null, 'booking_reference' => $rem['booking_reference'] ?? '', 'booking_id' => (int) ($rem['booking_id'] ?? 0), 'is_remaining_payment' => true, ]; } } } if (!empty($session) && !empty($session['trip_id'])) { // Load trip for display context try { $tripRepo = new \Yatra\Repositories\TripRepository(); $trip = $tripRepo->findPublished((int) $session['trip_id']); } catch (\Throwable $e) { $trip = null; } // Ensure trip has a price property for templates (centralized pricing) if ($trip && !isset($trip->price)) { $trip->price = \Yatra\Services\TripPricingService::resolveRegularCurrentPrice($trip); } // Ensure trip has a usable featured_image URL (not attachment ID) if ($trip) { // If featured_image is not set, try to get it from WordPress if (empty($trip->featured_image) && !empty($trip->id)) { $attachment_id = get_post_thumbnail_id($trip->id); if ($attachment_id) { $trip->featured_image = $attachment_id; } } // Convert attachment ID to URL if needed if (!empty($trip->featured_image) && is_numeric($trip->featured_image)) { $imgUrl = wp_get_attachment_url((int) $trip->featured_image); if ($imgUrl) { $trip->featured_image = $imgUrl; } else { // If attachment URL fails, use placeholder $trip->featured_image = plugins_url('assets/images/trip-placeholder.svg', YATRA_PLUGIN_FILE); } } elseif (empty($trip->featured_image)) { // No featured image at all, use placeholder $trip->featured_image = plugins_url('assets/images/trip-placeholder.svg', YATRA_PLUGIN_FILE); } } // Load trip attributes for booking page display if ($trip && !empty($trip->id)) { try { $singleTripController = new \Yatra\Controllers\SingleTripController(); // Use reflection to access private method $reflection = new \ReflectionClass($singleTripController); $method = $reflection->getMethod('getTripAttributes'); $method->setAccessible(true); $trip->attributes = $method->invoke($singleTripController, (int) $trip->id); } catch (\Throwable $e) { $trip->attributes = []; } } // Checkout must always reflect current Settings + registry — never filter by session. // Session snapshots enabled_gateways at "add to booking" time; admin/site changes and older // sessions would otherwise hide newly enabled gateways (e.g. only Pay Later forever). $gatewayRegistry = \Yatra\PaymentGateways\PaymentGatewayRegistry::getInstance(); $enabled_gateways = []; foreach ($gatewayRegistry->getForCheckout() as $gateway) { if (!empty($gateway['id'])) { $enabled_gateways[$gateway['id']] = $gateway; } } if (function_exists('yatra_start_session')) { yatra_start_session(); if (isset($_SESSION['yatra_booking']) && is_array($_SESSION['yatra_booking'])) { $_SESSION['yatra_booking']['enabled_gateways'] = $enabled_gateways; $token = $_SESSION['yatra_booking_token'] ?? ''; if (is_string($token) && $token !== '') { $transient_payload = array_merge($_SESSION['yatra_booking'], [ 'enabled_gateways' => $enabled_gateways, ]); set_transient($token, $transient_payload, 1800); } } } // Calculate pricing using CalculationService (single source of truth) $calculationService = new \Yatra\Services\CalculationService(); $pricing_calculation = []; // Get coupon code from session if available $coupon_code = isset($session['coupon']['code']) ? $session['coupon']['code'] : ''; try { $pricing_calculation = $calculationService->calculateFromSession($session, $coupon_code); } catch (\Throwable $e) { // If calculation fails, set empty pricing $pricing_calculation = [ 'base_amount' => 0, 'gross_total' => 0, 'final_total' => 0, 'amount_due' => 0, 'tax_calculation' => [ 'tax_breakdown' => [], 'total_tax_amount' => 0, 'tax_inclusive' => false, ], ]; } // Get itinerary costs (premium feature) $trip_id = (int) $session['trip_id']; $total_travelers = (int) ($session['travelers'] ?? 1); $traveler_counts = $session['traveler_counts'] ?? []; $travel_date = $session['travel_date'] ?? ''; $itinerary_costs = apply_filters('yatra_booking_itinerary_costs', [], $trip_id, $total_travelers, $traveler_counts, $travel_date); $itinerary_costs_total = 0.0; foreach ($itinerary_costs as $cost) { $basePrice = (float) ($cost['price'] ?? 0); $pricePer = $cost['price_per'] ?? 'person'; switch ($pricePer) { case 'person': $calculatedPrice = $basePrice * $total_travelers; break; case 'group': $calculatedPrice = $basePrice; break; case 'day': $duration_days = (int) ($trip->duration_days ?? 1); $calculatedPrice = $basePrice * $duration_days; break; default: $calculatedPrice = $basePrice; break; } $itinerary_costs_total += $calculatedPrice; } // Add itinerary costs to pricing calculation $pricing_calculation['itinerary_costs'] = $itinerary_costs; $pricing_calculation['itinerary_costs_total'] = $itinerary_costs_total; // Note: CalculationService already includes itinerary costs in final_total and amount_due // No need to add them again here // Create Checkout model instance $checkout = new \Yatra\Models\Checkout($trip, $session, $pricing_calculation); // Build booking view model expected by templates $booking = (object) [ 'trip' => $trip, 'travel_date' => $session['travel_date'] ?? '', 'travelers' => $session['travelers'] ?? 0, 'pricing_type' => $session['pricing_type'] ?? 'regular', 'price_types' => $session['price_types'] ?? [], 'traveler_counts' => $session['traveler_counts'] ?? [], 'partial_payment' => $session['partial_payment'] ?? null, 'partial_payment_percentage' => $session['partial_payment_percentage'] ?? null, 'deposit_required' => $session['deposit_required'] ?? null, 'deposit_percentage' => $session['deposit_percentage'] ?? null, 'enabled_gateways' => $enabled_gateways, 'group_discount' => $session['group_discount'] ?? null, // Pre-calculated pricing data 'pricing_calculation' => $pricing_calculation, 'tax_calculation' => $pricing_calculation['tax_calculation'] ?? [], // amounts for templates 'total_amount' => $session['total_amount'] ?? null, 'amount_paid' => $session['amount_paid'] ?? null, 'remaining_amount' => $session['remaining_amount'] ?? null, 'booking_reference' => $session['booking_reference'] ?? null, 'is_remaining_payment' => !empty($session['is_remaining_payment']), 'existing_booking_id' => (int) ($session['booking_id'] ?? 0), // Checkout model for clean template access 'checkout' => $checkout, ]; } } if ($booking !== null) { $this->setGlobal('booking', $booking); } // Load the booking page template $template_path = YATRA_PLUGIN_PATH . 'templates/booking.php'; if (!file_exists($template_path)) { $this->logError("Booking template not found: {$template_path}"); return false; } include $template_path; $this->exit(); return true; } }