PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
← All changes | templates/booking-confirmation.php +975 -135 3.0.2.6 → 3.0.16 View file →
@@ -27,11 +27,55 @@
27 27 yatra_clear_booking_session();
28 28 }
29 29
30 30 // Format dates
31 -$travel_date_formatted = date_i18n(get_option('date_format'), strtotime($booking->travel_date));
32 -$booking_date_formatted = date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($booking->created_at));
31 +$travel_date_formatted = \Yatra\Helpers\FormatHelper::formatDate((string) ($booking->travel_date ?? ''));
32 +$booking_date_formatted = \Yatra\Helpers\FormatHelper::formatDateTime((string) ($booking->created_at ?? ''));
33 33
34 +// "Balance just paid" context detection.
35 +// A `?balance=paid` query arg is appended by the remaining-payment flows
36 +// (see PaymentGatewayController::create_remaining_balance_intent and
37 +// BookingSessionController::process_remaining_payment). We only render the
38 +// "balance paid" copy when BOTH the flag is present AND the booking actually
39 +// reports a paid status — that way a stale bookmarked URL or a tampered
40 +// query string can't show misleading content if a balance is still due.
41 +$is_balance_paid_request = isset($_GET['balance']) && sanitize_key((string) $_GET['balance']) === 'paid';
42 +$booking_payment_status = (string) ($booking->payment_status ?? '');
43 +$booking_amount_due = (float) ($booking->amount_due ?? 0);
44 +$booking_is_fully_paid = $booking_payment_status === 'paid' || $booking_amount_due <= 0.01;
45 +$show_balance_paid_banner = $is_balance_paid_request && $booking_is_fully_paid;
46 +
47 +// A gateway may redirect back here with `?payment=cancelled` (user aborted the
48 +// payment at the provider) or `?payment=failed`. Show an informative notice only
49 +// when the booking is not already fully paid, so a genuinely-paid booking that
50 +// happens to carry a stale query arg never shows a misleading "cancelled" message.
51 +$payment_result_flag = isset($_GET['payment']) ? sanitize_key((string) $_GET['payment']) : '';
52 +$show_payment_cancelled_notice = in_array($payment_result_flag, ['cancelled', 'failed'], true) && !$booking_is_fully_paid;
53 +
54 +// Resolve the most recent gateway used on this booking.
55 +// `$booking->payment_gateway` is the *initial* method chosen at booking time
56 +// (e.g. Pay Later), which is misleading when the customer later settled the
57 +// balance via a different gateway (e.g. Stripe). Show the latest payment's
58 +// gateway in the Payment Method line; fall back to the booking's stored
59 +// gateway when no payment row exists yet.
60 +$display_payment_gateway = (string) ($booking->payment_gateway ?? '');
61 +if (!empty($booking->id)) {
62 + try {
63 + $latest_payment = (new \Yatra\Repositories\PaymentRepository())->findLatestByBookingId((int) $booking->id);
64 + if ($latest_payment) {
65 + $latest_gateway = (string) ($latest_payment->payment_gateway ?? $latest_payment->gateway ?? '');
66 + if ($latest_gateway !== '') {
67 + $display_payment_gateway = $latest_gateway;
68 + }
69 + }
70 + } catch (\Throwable $e) {
71 + // Repository unavailable for some reason — keep the booking's stored gateway.
72 + }
73 +}
74 +
75 +// Resolved customer account URL (configurable via Settings → Permalink → Account base).
76 +$yatra_account_url = home_url('/' . \Yatra\Services\SettingsService::getAccountBase());
77 +
34 78 // Get status color
35 79 $status_colors = [
36 80 'pending' => ['bg' => '#fef3c7', 'text' => '#d97706', 'label' => 'Pending'],
37 81 'confirmed' => ['bg' => '#d1fae5', 'text' => '#059669', 'label' => 'Confirmed'],
@@ -43,8 +87,21 @@
43 87 ];
44 88
45 89 $status_style = $status_colors[$booking->status] ?? $status_colors['pending'];
46 90
91 +// Ensure Font Awesome CSS is available for icon-picker rendered icons (fa-solid/fa-regular).
92 +// This template can be displayed outside normal "trip page" contexts, so we explicitly
93 +// register/enqueue the shared stylesheet dependencies before printing the header.
94 +if (class_exists(\Yatra\Providers\FrontendAssetsProvider::class)) {
95 + \Yatra\Providers\FrontendAssetsProvider::registerCoreFrontendStylesheets();
96 + if (wp_style_is('yatra-fontawesome-6', 'registered')) {
97 + wp_enqueue_style('yatra-fontawesome-6');
98 + }
99 + if (wp_style_is('yatra-common', 'registered')) {
100 + wp_enqueue_style('yatra-common');
101 + }
102 +}
103 +
47 104 yatra_get_header();
48 105
49 106 // Hook for Pro modules to add custom scripts (Facebook Pixel, etc.)
50 107 do_action('yatra_booking_confirmation_header', $booking);
@@ -61,10 +118,12 @@
61 118 <polyline points="9,12 12,15 16,10"></polyline>
62 119 </svg>
63 120 </div>
64 121 <h1 class="yatra-confirmation-title">
65 - <?php
66 - if ($booking->status === 'confirmed') {
122 + <?php
123 + if ($show_balance_paid_banner) {
124 + esc_html_e('Balance Paid!', 'yatra');
125 + } elseif ($booking->status === 'confirmed') {
67 126 esc_html_e('Booking Confirmed!', 'yatra');
68 127 } elseif ($booking->status === 'pending') {
69 128 esc_html_e('Booking Received!', 'yatra');
70 129 } elseif ($booking->status === 'cancelled') {
@@ -74,10 +133,12 @@
74 133 }
75 134 ?>
76 135 </h1>
77 136 <p class="yatra-confirmation-subtitle">
78 - <?php
79 - if ($booking->status === 'confirmed') {
137 + <?php
138 + if ($show_balance_paid_banner) {
139 + esc_html_e('Your remaining balance has been received. This booking is now fully paid.', 'yatra');
140 + } elseif ($booking->status === 'confirmed') {
80 141 esc_html_e('Thank you for your booking. We look forward to hosting you!', 'yatra');
81 142 } elseif ($booking->status === 'pending') {
82 143 esc_html_e('Thank you for your booking. Your booking is pending confirmation.', 'yatra');
83 144 } elseif ($booking->status === 'cancelled') {
@@ -92,10 +153,58 @@
92 153 <span class="yatra-ref-code"><?php echo esc_html($booking->reference); ?></span>
93 154 </div>
94 155 </div>
95 156
157 + <?php if ($show_balance_paid_banner) : ?>
158 + <div class="yatra-balance-paid-banner" role="status" aria-live="polite" style="margin: 16px 0; padding: 14px 18px; background: #d1fae5; border: 1px solid #6ee7b7; border-left: 4px solid #059669; border-radius: 8px; color: #065f46; display: flex; gap: 12px; align-items: flex-start;">
159 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="flex: 0 0 22px; width: 22px; height: 22px; margin-top: 1px;">
160 + <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
161 + </svg>
162 + <div style="flex: 1; line-height: 1.45;">
163 + <strong style="display: block; margin-bottom: 2px;">
164 + <?php esc_html_e('Balance payment received.', 'yatra'); ?>
165 + </strong>
166 + <span>
167 + <?php
168 + printf(
169 + wp_kses(
170 + /* translators: 1: booking reference number (HTML <strong> wrapped), 2: link labelled "payment history" pointing to the customer account page (HTML <a> wrapped). */
171 + __('You\'ve completed payment for booking %1$s. A receipt has been emailed to you. View or download your %2$s anytime from your account.', 'yatra'),
172 + ['a' => ['href' => true], 'strong' => []]
173 + ),
174 + '<strong>' . esc_html((string) ($booking->reference ?? '')) . '</strong>',
175 + '<a href="' . esc_url(add_query_arg('tab', 'payments', $yatra_account_url)) . '">' . esc_html__('payment history', 'yatra') . '</a>'
176 + );
177 + ?>
178 + </span>
179 + </div>
180 + </div>
181 + <?php endif; ?>
182 +
183 + <?php if ($show_payment_cancelled_notice) : ?>
184 + <div class="yatra-payment-cancelled-banner" role="status" aria-live="polite" style="margin: 16px 0; padding: 14px 18px; background: #fef3c7; border: 1px solid #fcd34d; border-left: 4px solid #d97706; border-radius: 8px; color: #92400e; display: flex; gap: 12px; align-items: flex-start;">
185 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="flex: 0 0 22px; width: 22px; height: 22px; margin-top: 1px;">
186 + <circle cx="12" cy="12" r="10"></circle>
187 + <line x1="12" y1="8" x2="12" y2="12"></line>
188 + <line x1="12" y1="16" x2="12.01" y2="16"></line>
189 + </svg>
190 + <div style="flex: 1; line-height: 1.45;">
191 + <strong style="display: block; margin-bottom: 2px;">
192 + <?php esc_html_e('Payment not completed.', 'yatra'); ?>
193 + </strong>
194 + <span>
195 + <?php esc_html_e('Your payment was cancelled and no charge was made. Your booking has been saved — you can complete payment anytime from your account.', 'yatra'); ?>
196 + </span>
197 + </div>
198 + </div>
199 + <?php endif; ?>
200 +
96 201 <?php
97 - // Resolve featured image URL (handle attachment ID or direct URL)
202 + // Resolve featured image URL (handle attachment ID or direct URL).
203 + // Falls back to the bundled trip-placeholder.svg so the confirmation
204 + // card always renders with a visual on the left — never an empty
205 + // text-only block, regardless of whether the operator set a featured
206 + // image for the trip.
98 207 $featured_image_url = '';
99 208 if (!empty($booking->featured_image)) {
100 209 if (is_numeric($booking->featured_image)) {
101 210 $featured_image_url = wp_get_attachment_url((int) $booking->featured_image) ?: '';
@@ -102,8 +211,14 @@
102 211 } else {
103 212 $featured_image_url = $booking->featured_image;
104 213 }
105 214 }
215 + if ($featured_image_url === '') {
216 + $featured_image_url = YATRA_PLUGIN_URL . 'assets/images/trip-placeholder.svg';
217 + $featured_image_is_placeholder = true;
218 + } else {
219 + $featured_image_is_placeholder = false;
220 + }
106 221 ?>
107 222
108 223 <div class="yatra-confirmation-content">
109 224 <!-- Trip Summary Card -->
@@ -108,23 +223,66 @@
108 223 <div class="yatra-confirmation-content">
109 224 <!-- Trip Summary Card -->
110 225 <div class="yatra-confirmation-card yatra-trip-summary-card">
111 226 <div class="yatra-trip-summary">
112 - <?php if (!empty($featured_image_url)) : ?>
113 - <div class="yatra-trip-image">
114 - <img src="<?php echo esc_url($featured_image_url); ?>" alt="<?php echo esc_attr($booking->trip_title); ?>">
227 + <div class="yatra-trip-image<?php echo $featured_image_is_placeholder ? ' yatra-trip-image--placeholder' : ''; ?>">
228 + <img src="<?php echo esc_url($featured_image_url); ?>"
229 + alt="<?php echo esc_attr($booking->trip_title); ?>"
230 + loading="lazy"
231 + onerror="this.onerror=null;this.src='<?php echo esc_url(YATRA_PLUGIN_URL . 'assets/images/trip-placeholder.svg'); ?>';this.parentNode.classList.add('yatra-trip-image--placeholder');">
115 232 </div>
116 - <?php endif; ?>
117 233
118 234 <div class="yatra-trip-info">
119 235 <h2 class="yatra-trip-name"><?php echo esc_html($booking->trip_title); ?></h2>
120 236
121 237 <?php if (!empty($booking->trip_average_rating) && $booking->trip_review_count >= 0) : ?>
238 + <?php
239 + // The confirmation page isn't a "booking page" (it has its
240 + // own route), so booking.css — which colours the rating
241 + // stars amber — is never enqueued here and the filled stars
242 + // fell back to the grey base colour. The rest of this page
243 + // is inline-styled, so rather than loading the whole
244 + // stylesheet (and risk restyling it) the rating rules are
245 + // scoped inline here, mirroring assets/css/booking.css.
246 + ?>
247 + <?php
248 + // IMPORTANT: these rules are deliberately scoped to
249 + // `.yatra-trip-rating .yatra-rating-stars .yatra-star*`
250 + // (specificity 0,3,0 / 0,4,1) and set `fill` directly on
251 + // the SVG — NOT just `color` + `fill="currentColor"`.
252 + // trip.css / booking.css define `.yatra-rating-stars
253 + // .yatra-star { color:#d1d5db }` (0,2,0) and colour the
254 + // "filled" state with a `.filled` (dot) class, not the
255 + // `-filled` (hyphen) class this markup uses. If either
256 + // stylesheet is present on the confirmation page, its
257 + // grey base out-specifies a plain `.yatra-star-filled`
258 + // and its amber rule never matches — so every star showed
259 + // grey even at 4.8. Winning on specificity + an explicit
260 + // fill makes the amber stick regardless of what's loaded.
261 + ?>
262 + <style>
263 + .yatra-trip-rating{display:flex;align-items:center;gap:12px;margin-bottom:12px}
264 + .yatra-trip-rating .yatra-rating-stars{display:inline-flex;gap:4px}
265 + .yatra-trip-rating .yatra-rating-stars .yatra-star{width:20px;height:20px;color:#e2e8f0;font-size:0;line-height:0}
266 + .yatra-trip-rating .yatra-rating-stars .yatra-star svg{width:100%;height:100%;display:block;fill:currentColor}
267 + .yatra-trip-rating .yatra-rating-stars .yatra-star-filled{color:#f59e0b}
268 + .yatra-trip-rating .yatra-rating-stars .yatra-star-filled svg{fill:#f59e0b}
269 + .yatra-trip-rating .yatra-rating-stars .yatra-star-half{position:relative;color:#e2e8f0}
270 + .yatra-trip-rating .yatra-rating-stars .yatra-star-half svg{fill:#e2e8f0}
271 + .yatra-trip-rating .yatra-rating-stars .yatra-star-half::after{content:"";position:absolute;top:0;left:0;bottom:0;width:50%;overflow:hidden;background:#f59e0b;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpolygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26'/%3E%3C/svg%3E") no-repeat left center / 200% 100%;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpolygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26'/%3E%3C/svg%3E") no-repeat left center / 200% 100%}
272 + .yatra-trip-rating .yatra-rating-meta strong{color:#0f172a;margin-right:6px}
273 + .yatra-trip-rating .yatra-rating-meta span{color:#64748b;font-size:14px}
274 + </style>
122 275 <div class="yatra-trip-rating">
123 276 <div class="yatra-rating-stars">
124 277 <?php
125 - $filled_stars = floor($booking->trip_average_rating);
126 - $has_half = ($booking->trip_average_rating - $filled_stars) >= 0.5;
278 + // Shared with the reviews block and listing cards so every
279 + // surface draws the same rating. Rounds to the nearest half:
280 + // flooring drew 4.9 as four-and-a-half stars, which looked
281 + // wrong beside the printed "4.9".
282 + $star_parts = yatra_rating_star_parts($booking->trip_average_rating);
283 + $filled_stars = $star_parts['full'];
284 + $has_half = $star_parts['half'];
127 285 for ($i = 1; $i <= 5; $i++) :
128 286 $class = $i <= $filled_stars ? 'yatra-star-filled' : ($has_half && $i === $filled_stars + 1 ? 'yatra-star-half' : '');
129 287 ?>
130 288 <span class="yatra-star <?php echo esc_attr($class); ?>">
@@ -135,10 +293,15 @@
135 293 <div class="yatra-rating-meta">
136 294 <strong><?php echo esc_html(number_format($booking->trip_average_rating, 1)); ?></strong>
137 295 <span>
138 296 <?php
139 - /* translators: %d review count */
140 - printf(esc_html(_n('%d Review', '%d Reviews', (int) $booking->trip_review_count, 'yatra')), (int) $booking->trip_review_count);
297 + printf(
298 + esc_html(
299 + /* translators: %d: number of reviews. */
300 + _n('%d Review', '%d Reviews', (int) $booking->trip_review_count, 'yatra')
301 + ),
302 + (int) $booking->trip_review_count
303 + );
141 304 ?>
142 305 </span>
143 306 </div>
144 307 </div>
@@ -144,24 +307,72 @@
144 307 </div>
145 308 <?php endif; ?>
146 309
147 310 <div class="yatra-trip-meta">
148 - <?php if (!empty($booking->duration_days)) : ?>
311 + <?php if (!empty($booking->duration_days) || !empty($booking->duration_hours)) : ?>
149 312 <span class="yatra-meta-item">
150 313 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
151 314 <circle cx="12" cy="12" r="10"></circle>
152 315 <polyline points="12 6 12 12 16 14"></polyline>
153 316 </svg>
154 - <?php printf(esc_html__('%d Days / %d Nights', 'yatra'), (int) $booking->duration_days, (int) $booking->duration_nights); ?>
317 + <?php
318 + $days = (int) ($booking->duration_days ?? 0);
319 + $nights = (int) ($booking->duration_nights ?? 0);
320 + $hours = (int) ($booking->duration_hours ?? 0);
321 +
322 + if ($hours > 0) {
323 + // Hour-based day tour: "8 Hours", not "1 Day".
324 + printf(
325 + esc_html(
326 + /* translators: %d: number of hours. */
327 + _n('%d Hour', '%d Hours', $hours, 'yatra')
328 + ),
329 + $hours
330 + );
331 + } elseif ($days > 0 && $nights > 0) {
332 + printf(
333 + /* translators: 1: number of days, 2: number of nights. */
334 + esc_html__('%1$d Days / %2$d Nights', 'yatra'),
335 + $days,
336 + $nights
337 + );
338 + } elseif ($days > 0) {
339 + printf(
340 + esc_html(
341 + /* translators: %d: number of days. */
342 + _n('%d Day', '%d Days', $days, 'yatra')
343 + ),
344 + $days
345 + );
346 + } elseif ($nights > 0) {
347 + printf(
348 + esc_html(
349 + /* translators: %d: number of nights. */
350 + _n('%d Night', '%d Nights', $nights, 'yatra')
351 + ),
352 + $nights
353 + );
354 + }
355 + ?>
155 356 </span>
156 357 <?php endif; ?>
157 358
158 359 <?php if (!empty($booking->difficulty_level)) : ?>
159 360 <span class="yatra-meta-item">
160 - <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
161 - <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
162 - </svg>
163 - <?php echo esc_html(ucfirst($booking->difficulty_level)); ?>
361 + <?php
362 + $difficulty = \Yatra\Models\Trip::resolveDifficultyDisplay(
363 + (string) $booking->difficulty_level
364 + );
365 +
366 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
367 + echo yatra_stored_picker_icon_markup(
368 + $difficulty['icon_picker'] ?? null,
369 + '',
370 + 'yatra-icon-sm'
371 + );
372 +
373 + echo esc_html($difficulty['level'] ?? (string) $booking->difficulty_level);
374 + ?>
164 375 </span>
165 376 <?php endif; ?>
166 377 </div>
167 378
@@ -210,18 +421,8 @@
210 421 </div>
211 422 </div>
212 423 <?php endif; ?>
213 424
214 - <?php if (!empty($booking->trip_attributes_list)) : ?>
215 - <div class="yatra-trip-tags">
216 - <span class="yatra-tag-label"><?php esc_html_e('Features:', 'yatra'); ?></span>
217 - <div class="yatra-tag-group">
218 - <?php foreach ($booking->trip_attributes_list as $attribute) : ?>
219 - <span class="yatra-tag-item yatra-attribute-tag"><?php echo esc_html($attribute); ?></span>
220 - <?php endforeach; ?>
221 - </div>
222 - </div>
223 - <?php endif; ?>
224 425 </div>
225 426 </div>
226 427 </div>
227 428
@@ -289,9 +490,9 @@
289 490 <?php echo esc_html($traveler_name ?: __('Traveler', 'yatra') . ' ' . ($index + 1)); ?>
290 491 </span>
291 492 <?php if (!empty($traveler['date_of_birth'])) : ?>
292 493 <span class="yatra-traveler-dob">
293 - <?php echo esc_html(date_i18n(get_option('date_format'), strtotime($traveler['date_of_birth']))); ?>
494 + <?php echo esc_html(\Yatra\Helpers\FormatHelper::formatDate((string) $traveler['date_of_birth'])); ?>
294 495 </span>
295 496 <?php endif; ?>
296 497 </div>
297 498 </div>
@@ -318,44 +519,123 @@
318 519 <?php esc_html_e('Payment Summary', 'yatra'); ?>
319 520 </h3>
320 521
321 522 <div class="yatra-payment-rows">
322 - <?php
323 - // Get subtotal (gross amount before tax)
324 - $subtotal = !empty($booking->subtotal) ? (float) $booking->subtotal : (float) $booking->total_amount;
325 - $tax_amount = !empty($booking->tax_amount) ? (float) $booking->tax_amount : 0;
326 - $tax_inclusive = !empty($booking->tax_inclusive);
327 -
328 - // If tax is inclusive, subtotal should be total_amount, otherwise calculate it
329 - if (!$tax_inclusive && $tax_amount > 0) {
330 - $subtotal = $booking->total_amount - $tax_amount;
523 + <?php
524 + // ── Raw figures from DB ───────────────────────────────────
525 + $db_subtotal = (float) ($booking->subtotal ?? $booking->total_amount);
526 + $discount_amount = (float) ($booking->discount_amount ?? 0);
527 + $itinerary_costs_total = (float) ($booking->itinerary_costs_total ?? 0);
528 + $tax_amount = (float) ($booking->tax_amount ?? 0);
529 + $tax_inclusive = !empty($booking->tax_inclusive);
530 + $total_amount = (float) $booking->total_amount;
531 + $amount_paid = (float) ($booking->amount_paid ?? 0);
532 + $amount_due = (float) ($booking->amount_due ?? 0);
533 +
534 + // ── Tax breakdown (built first so it can gate later rows) ─
535 + $tax_breakdown = [];
536 + if (!empty($booking->tax_details)) {
537 + $taxes = json_decode($booking->tax_details, true) ?: [];
538 + foreach ($taxes as $tax) {
539 + $tax_breakdown[] = [
540 + 'name' => $tax['name'] ?? __('Tax', 'yatra'),
541 + 'rate' => (float) ($tax['rate'] ?? 0),
542 + 'amount' => (float) ($tax['amount'] ?? 0),
543 + ];
544 + }
545 + } elseif ($tax_amount > 0) {
546 + $tax_breakdown[] = [
547 + 'name' => __('Tax', 'yatra'),
548 + 'rate' => (float) ($booking->tax_rate ?? 0),
549 + 'amount' => $tax_amount,
550 + ];
331 551 }
552 +
553 + // ── Itinerary costs ───────────────────────────────────────
554 + $itinerary_costs = [];
555 + if (!empty($booking->itinerary_costs)) {
556 + $itinerary_costs = json_decode($booking->itinerary_costs, true) ?: [];
557 + }
558 +
559 + // ── Additional services ───────────────────────────────────
560 + $raw_services = apply_filters('yatra_booking_get_services', [], (int) ($booking->id ?? 0));
561 + $selected_services = [];
562 + if (!empty($raw_services) && is_array($raw_services)) {
563 + foreach ($raw_services as $s) {
564 + $arr = is_array($s) ? $s : (array) $s;
565 + if (!isset($arr['selected']) || !empty($arr['selected'])) {
566 + $selected_services[] = $arr;
567 + }
568 + }
569 + }
570 + $services_total = 0.0;
571 + foreach ($selected_services as $s) {
572 + $services_total += (float) ($s['total_price'] ?? $s['calculated_price'] ?? $s['total_cost'] ?? $s['amount'] ?? $s['unit_price'] ?? $s['price'] ?? 0);
573 + }
574 +
575 + // ── Derived amounts ───────────────────────────────────────
576 + // base_trip_cost: the trip price without services (services are embedded in db_subtotal by Pro)
577 + $base_trip_cost = $db_subtotal - $services_total;
578 + // taxable_amount: what tax is calculated on
579 + $taxable_amount = max(0.0, $db_subtotal - $discount_amount) + $itinerary_costs_total;
580 +
581 + $has_services = !empty($selected_services);
582 + $has_discount = $discount_amount > 0;
583 + $has_itinerary = !empty($itinerary_costs) && $itinerary_costs_total > 0;
584 + $has_tax = !empty($tax_breakdown);
585 + $show_taxable_row = $has_tax && ($has_discount || $has_itinerary);
332 586 ?>
333 -
334 - <!-- Gross Total (base trip cost before services/discounts/taxes) -->
587 +
588 + <!-- Trip Base Price (always the first line) -->
335 589 <div class="yatra-payment-row">
590 + <span>
591 + <?php esc_html_e('Trip Base Price', 'yatra'); ?>
592 + <?php if ($tax_inclusive): ?>
593 + <span style="display:inline-flex;align-items:center;padding:1px 6px;border-radius:4px;font-size:0.72em;font-weight:600;background:#eff6ff;color:#1d4ed8;margin-left:5px;letter-spacing:0;">
594 + <?php esc_html_e('Tax Incl.', 'yatra'); ?>
595 + </span>
596 + <?php endif; ?>
597 + </span>
598 + <span><?php echo esc_html(yatra_format_price($base_trip_cost, $booking->currency)); ?></span>
599 + </div>
600 +
601 + <?php if ($has_services): ?>
602 + <!-- Additional Services section header -->
603 + <div class="yatra-payment-row" style="margin-top: 4px;">
604 + <span style="font-size: 0.75em; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: #6b7280;">
605 + + <?php esc_html_e('Additional Services', 'yatra'); ?>
606 + </span>
607 + <span></span>
608 + </div>
609 + <!-- Service line items -->
610 + <?php foreach ($selected_services as $svc): ?>
611 + <?php
612 + $svc_name = $svc['service_name'] ?? $svc['name'] ?? $svc['title'] ?? $svc['label'] ?? __('Service', 'yatra');
613 + $svc_amount = (float) ($svc['total_price'] ?? $svc['calculated_price'] ?? $svc['total_cost'] ?? $svc['amount'] ?? $svc['unit_price'] ?? $svc['price'] ?? 0);
614 + ?>
615 + <div class="yatra-payment-row" style="margin-left: 16px;">
616 + <span><?php echo esc_html($svc_name); ?></span>
617 + <span><?php echo esc_html(yatra_format_price($svc_amount, $booking->currency, false)); ?></span>
618 + </div>
619 + <?php endforeach; ?>
620 + <!-- Gross Total = Trip Base Price + Services -->
621 + <div class="yatra-payment-row" style="border-top: 1px solid #e5e7eb; margin-top: 6px; padding-top: 6px;">
336 622 <span><strong><?php esc_html_e('Gross Total', 'yatra'); ?></strong></span>
337 - <span><strong><?php echo esc_html(yatra_format_price($subtotal, $booking->currency)); ?></strong></span>
623 + <span><strong><?php echo esc_html(yatra_format_price($db_subtotal, $booking->currency)); ?></strong></span>
338 624 </div>
339 -
340 - <?php if (!empty($booking->discount_amount) && $booking->discount_amount > 0) : ?>
625 + <?php endif; ?>
626 +
627 + <?php if ($has_discount): ?>
341 628 <!-- Discount -->
342 - <div class="yatra-payment-row yatra-discount-row">
629 + <div class="yatra-payment-row">
343 630 <span><?php esc_html_e('Discount', 'yatra'); ?></span>
344 - <span style="color: #059669; font-weight: 500;">-<?php echo esc_html(yatra_format_price($booking->discount_amount, $booking->currency)); ?></span>
631 + <span style="color: #059669; font-weight: 500;">-<?php echo esc_html(yatra_format_price($discount_amount, $booking->currency)); ?></span>
345 632 </div>
346 633 <?php endif; ?>
347 -
348 - <!-- Itinerary Costs -->
349 - <?php
350 - $itinerary_costs = [];
351 - if (!empty($booking->itinerary_costs)) {
352 - $itinerary_costs = json_decode($booking->itinerary_costs, true) ?: [];
353 - }
354 -
355 - if (!empty($itinerary_costs)):
356 - ?>
357 - <div class="yatra-payment-row">
634 +
635 + <?php if ($has_itinerary): ?>
636 + <!-- Itinerary costs header -->
637 + <div class="yatra-payment-row" style="margin-top: 4px;">
358 638 <span><strong><?php esc_html_e('Itinerary Costs', 'yatra'); ?></strong></span>
359 639 <span></span>
360 640 </div>
361 641 <?php foreach ($itinerary_costs as $cost): ?>
@@ -361,19 +641,19 @@
361 641 <?php foreach ($itinerary_costs as $cost): ?>
362 642 <div class="yatra-payment-row" style="margin-left: 20px;">
363 643 <span>
364 644 <?php echo esc_html($cost['name']); ?>
365 - <?php if (!empty($cost['price_per'])) : ?>
366 - <small style="opacity: 0.7;">
367 - <?php
368 - if ($cost['price_per'] === 'person') {
369 - esc_html_e('(per person)', 'yatra');
370 - } elseif ($cost['price_per'] === 'group') {
371 - esc_html_e('(per booking)', 'yatra');
372 - } else {
373 - esc_html_e('(flat rate)', 'yatra');
374 - }
375 - ?>
645 + <?php if (!empty($cost['price_per'])): ?>
646 + <small style="opacity: 0.65;">
647 + <?php
648 + if ($cost['price_per'] === 'person') {
649 + esc_html_e('(per person)', 'yatra');
650 + } elseif ($cost['price_per'] === 'group') {
651 + esc_html_e('(per booking)', 'yatra');
652 + } else {
653 + esc_html_e('(flat rate)', 'yatra');
654 + }
655 + ?>
376 656 </small>
377 657 <?php endif; ?>
378 658 </span>
379 659 <span><?php echo esc_html(yatra_format_price($cost['total_cost'] ?? $cost['price'], $booking->currency)); ?></span>
@@ -379,83 +659,101 @@
379 659 <span><?php echo esc_html(yatra_format_price($cost['total_cost'] ?? $cost['price'], $booking->currency)); ?></span>
380 660 </div>
381 661 <?php endforeach; ?>
382 662 <?php endif; ?>
383 -
384 - <!-- Taxable Amount (SubTotal - amount that tax is calculated on) -->
385 - <?php
386 - $taxable_amount = 0;
387 - if (!empty($booking->itinerary_costs_total)) {
388 - // Calculate taxable amount: discounted subtotal + itinerary costs
389 - $discounted_subtotal = $subtotal - ($booking->discount_amount ?? 0);
390 - $taxable_amount = $discounted_subtotal + (float) $booking->itinerary_costs_total;
391 - }
392 -
393 - if ($taxable_amount > 0 && !empty($tax_breakdown)):
394 - ?>
663 +
664 + <?php if (!$tax_inclusive && $show_taxable_row): ?>
665 + <!-- Taxable Amount (only for exclusive tax when there are deductions above it) -->
395 666 <div class="yatra-payment-row" style="border-top: 1px dashed #e5e7eb; margin-top: 8px; padding-top: 8px;">
396 - <span><strong><?php esc_html_e('Subtotal (Taxable Amount)', 'yatra'); ?></strong></span>
667 + <span><strong><?php esc_html_e('Taxable Amount', 'yatra'); ?></strong></span>
397 668 <span><strong><?php echo esc_html(yatra_format_price($taxable_amount, $booking->currency)); ?></strong></span>
398 669 </div>
399 670 <?php endif; ?>
400 -
401 - <!-- Tax Breakdown -->
402 - <?php
403 - $tax_breakdown = [];
404 - if (!empty($booking->tax_details)) {
405 - $taxes = json_decode($booking->tax_details, true) ?: [];
406 - foreach ($taxes as $tax) {
407 - $tax_breakdown[] = [
408 - 'name' => $tax['name'] ?? 'Tax',
409 - 'rate' => $tax['rate'] ?? 0,
410 - 'amount' => $tax['amount'] ?? 0
411 - ];
412 - }
413 - } elseif (!empty($booking->tax_amount) && $booking->tax_amount > 0) {
414 - // Fallback for single tax
415 - $tax_breakdown[] = [
416 - 'name' => __('Tax', 'yatra'),
417 - 'rate' => (float) ($booking->tax_rate ?? 0),
418 - 'amount' => (float) $booking->tax_amount
419 - ];
420 - }
421 -
422 - if (!empty($tax_breakdown)):
423 - ?>
671 +
672 + <?php if ($has_tax && !$tax_inclusive): ?>
673 + <!-- Exclusive tax: shown as an addition before Net Amount -->
674 + <div class="yatra-payment-row" style="margin-top: 4px;">
675 + <span style="font-size: 0.75em; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: #6b7280;">
676 + + <?php esc_html_e('Tax', 'yatra'); ?>
677 + </span>
678 + <span></span>
679 + </div>
424 680 <?php foreach ($tax_breakdown as $tax): ?>
425 - <div class="yatra-payment-row">
426 - <span><?php echo esc_html($tax['name']); ?> (<?php echo esc_html($tax['rate']); ?>%)</span>
427 - <span><?php echo esc_html(yatra_format_price($tax['amount'], $booking->currency)); ?></span>
681 + <div class="yatra-payment-row" style="margin-left: 16px;">
682 + <span>
683 + <?php echo esc_html($tax['name']); ?>
684 + <?php if ($tax['rate'] > 0): ?>
685 + (<?php echo esc_html($tax['rate']); ?>%)
686 + <?php endif; ?>
687 + </span>
688 + <span>+<?php echo esc_html(yatra_format_price($tax['amount'], $booking->currency)); ?></span>
428 689 </div>
429 690 <?php endforeach; ?>
430 691 <?php endif; ?>
431 -
692 +
432 693 <!-- Net Amount -->
433 - <div class="yatra-payment-row">
434 - <span><strong><?php esc_html_e('Net Amount', 'yatra'); ?></strong></span>
435 - <span><strong><?php echo esc_html(yatra_format_price($booking->total_amount, $booking->currency)); ?></strong></span>
694 + <div class="yatra-payment-row" style="border-top: 2px solid #111827; margin-top: 10px; padding-top: 10px;">
695 + <span style="font-size: 1.05em;"><strong><?php esc_html_e('Net Amount', 'yatra'); ?></strong></span>
696 + <span style="font-size: 1.05em;"><strong><?php echo esc_html(yatra_format_price($total_amount, $booking->currency)); ?></strong></span>
436 697 </div>
437 -
438 - <?php if (($booking->amount_paid ?? 0) > 0) : ?>
439 - <!-- Amount Paid -->
440 - <div class="yatra-payment-row">
698 +
699 + <?php if ($has_tax && $tax_inclusive): ?>
700 + <!-- Inclusive tax: shown as an informational footnote after Net Amount -->
701 + <?php foreach ($tax_breakdown as $tax): ?>
702 + <div class="yatra-payment-row" style="margin-top: 4px; opacity: 0.7;">
703 + <span style="font-size: 0.85em; font-style: italic;">
704 + <?php
705 + printf(
706 + /* translators: %1$s = tax name, %2$s = rate, %3$s = amount */
707 + esc_html__('Incl. %1$s%2$s: %3$s', 'yatra'),
708 + esc_html($tax['name']),
709 + $tax['rate'] > 0 ? ' (' . esc_html($tax['rate']) . '%)' : '',
710 + esc_html(yatra_format_price($tax['amount'], $booking->currency))
711 + );
712 + ?>
713 + </span>
714 + <span></span>
715 + </div>
716 + <?php endforeach; ?>
717 + <?php endif; ?>
718 +
719 + <?php if ($amount_paid > 0): ?>
720 + <div class="yatra-payment-row" style="margin-top: 6px;">
441 721 <span><?php esc_html_e('Amount Paid', 'yatra'); ?></span>
442 - <span><?php echo esc_html(yatra_format_price($booking->amount_paid, $booking->currency)); ?></span>
722 + <span style="color: #059669;"><?php echo esc_html(yatra_format_price($amount_paid, $booking->currency)); ?></span>
443 723 </div>
444 724 <?php endif; ?>
445 -
446 - <?php if ($booking->amount_due > 0) : ?>
447 - <!-- Due Now -->
725 +
726 + <?php if ($amount_due > 0): ?>
727 + <?php
728 + // Label the "due now" amount by payment type so a
729 + // deposit/partial reads clearly instead of just "Due Now".
730 + $payment_method_type = (string) ($booking->payment_method ?? 'full');
731 + $remaining_balance = max(0.0, $total_amount - $amount_paid - $amount_due);
732 + if ($payment_method_type === 'deposit') {
733 + $due_now_label = __('Deposit Due Now', 'yatra');
734 + } elseif ($payment_method_type === 'partial') {
735 + $due_now_label = __('Partial Payment Due Now', 'yatra');
736 + } else {
737 + $due_now_label = __('Due Now', 'yatra');
738 + }
739 + ?>
448 740 <div class="yatra-payment-row yatra-due-row">
449 - <span><?php esc_html_e('Due Now', 'yatra'); ?></span>
450 - <span><?php echo esc_html(yatra_format_price($booking->amount_due, $booking->currency)); ?></span>
741 + <span><strong><?php echo esc_html($due_now_label); ?></strong></span>
742 + <span><strong><?php echo esc_html(yatra_format_price($amount_due, $booking->currency)); ?></strong></span>
451 743 </div>
744 + <?php if ($payment_method_type !== 'full' && $remaining_balance > 0.01): ?>
745 + <div class="yatra-payment-row" style="margin-top: 6px;">
746 + <span><?php esc_html_e('Remaining Balance', 'yatra'); ?></span>
747 + <span><?php echo esc_html(yatra_format_price($remaining_balance, $booking->currency)); ?></span>
748 + </div>
452 749 <?php endif; ?>
750 + <?php endif; ?>
453 751 </div>
454 752
455 753 <div class="yatra-payment-method">
456 754 <span class="yatra-pm-label"><?php esc_html_e('Payment Method:', 'yatra'); ?></span>
457 - <span class="yatra-pm-value"><?php echo esc_html(ucwords(str_replace('_', ' ', $booking->payment_gateway))); ?></span>
755 + <span class="yatra-pm-value"><?php echo esc_html(yatra_payment_gateway_label((string) $display_payment_gateway)); ?></span>
458 756 </div>
459 757 </div>
460 758
461 759 <!-- Contact Info Card -->
@@ -569,15 +867,31 @@
569 867 <?php esc_html_e('Return to Home', 'yatra'); ?>
570 868 </a>
571 869
572 870 <?php
573 - // Get latest payment for this booking to enable invoice download
871 + // Get latest payment for this booking to enable invoice download.
872 + // The invoice endpoint authorises via: admin → owner → signed invoice_token → guest booking_token.
873 + // We always issue a signed invoice_token here so the link works for guests
874 + // (no session required) AND for logged-in users who don't own the booking
875 + // (e.g. site admins viewing the page) — without re-checking login on the client.
574 876 $paymentRepository = new \Yatra\Repositories\PaymentRepository();
575 877 $latestPayment = $paymentRepository->findLatestByBookingId((int) $booking->id);
576 - if ($latestPayment && in_array($latestPayment->status, ['completed', 'paid'], true)) :
878 + if ($latestPayment && in_array($latestPayment->status, ['completed', 'paid'], true)) :
879 + $invoiceToken = \Yatra\Controllers\PaymentGatewayController::issueInvoiceToken(
880 + (int) $latestPayment->id,
881 + (int) ($latestPayment->booking_id ?? $booking->id)
882 + );
883 + $invoiceUrl = add_query_arg(
884 + [
885 + 'download' => '1',
886 + 'invoice_token' => $invoiceToken,
887 + ],
888 + rest_url('yatra/v1/payment/' . (int) $latestPayment->id . '/invoice')
889 + );
577 890 ?>
578 - <a href="<?php echo esc_url(rest_url('yatra/v1/payment/' . $latestPayment->id . '/invoice')); ?>"
579 - target="_blank"
891 + <a href="<?php echo esc_url($invoiceUrl); ?>"
892 + target="_blank"
893 + rel="noopener"
580 894 class="yatra-btn yatra-btn-primary">
581 895 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
582 896 <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
583 897 <polyline points="7 10 12 15 17 10"></polyline>
@@ -584,11 +898,36 @@
584 898 <line x1="12" y1="15" x2="12" y2="3"></line>
585 899 </svg>
586 900 <?php esc_html_e('Download Invoice', 'yatra'); ?>
587 901 </a>
902 + <?php
903 + // No completed payment yet (e.g. Bank Transfer / offline): offer a
904 + // pro-forma invoice that carries the payment instructions so the
905 + // customer knows how to pay. Uses a booking-scoped invoice token.
906 + elseif ((float) ($booking->amount_due ?? $booking->total_amount ?? 0) > 0) :
907 + $proformaToken = \Yatra\Controllers\PaymentGatewayController::issueInvoiceToken(0, (int) $booking->id);
908 + $proformaUrl = add_query_arg(
909 + [
910 + 'download' => '1',
911 + 'invoice_token' => $proformaToken,
912 + ],
913 + rest_url('yatra/v1/booking/' . (int) $booking->id . '/invoice')
914 + );
915 + ?>
916 + <a href="<?php echo esc_url($proformaUrl); ?>"
917 + target="_blank"
918 + rel="noopener"
919 + class="yatra-btn yatra-btn-primary">
920 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
921 + <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
922 + <polyline points="7 10 12 15 17 10"></polyline>
923 + <line x1="12" y1="15" x2="12" y2="3"></line>
924 + </svg>
925 + <?php esc_html_e('Download Invoice', 'yatra'); ?>
926 + </a>
588 927 <?php endif; ?>
589 928
590 - <button onclick="window.print();" class="yatra-btn yatra-btn-outline">
929 + <button type="button" data-yatra-print="confirmation" class="yatra-btn yatra-btn-outline">
591 930 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
592 931 <polyline points="6 9 6 2 18 2 18 9"></polyline>
593 932 <path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path>
594 933 <rect x="6" y="14" width="12" height="8"></rect>
@@ -735,8 +1074,68 @@
735 1074 color: #111827;
736 1075 margin: 0 0 12px;
737 1076 }
738 1077
1078 +/* Star rating. This confirmation page only enqueues yatra-common + fontawesome,
1079 + not booking.css/trip.css where these live, so without them the inline SVG
1080 + stars render with no size or colour (invisible). Mirrors booking.css. */
1081 +.yatra-trip-rating {
1082 + display: flex;
1083 + align-items: center;
1084 + gap: 12px;
1085 + margin-bottom: 12px;
1086 +}
1087 +
1088 +.yatra-rating-stars {
1089 + display: inline-flex;
1090 + gap: 4px;
1091 +}
1092 +
1093 +.yatra-star {
1094 + width: 20px;
1095 + height: 20px;
1096 + color: #e2e8f0;
1097 +}
1098 +
1099 +.yatra-star svg {
1100 + width: 100%;
1101 + height: 100%;
1102 +}
1103 +
1104 +.yatra-star-filled {
1105 + color: #f59e0b;
1106 +}
1107 +
1108 +/* Half star: base star stays grey; overlay the left half in amber via an
1109 + SVG mask (size-independent, so it tracks the star's dimensions). */
1110 +.yatra-star-half {
1111 + position: relative;
1112 + color: #e2e8f0;
1113 +}
1114 +
1115 +.yatra-star-half::after {
1116 + content: "";
1117 + position: absolute;
1118 + top: 0;
1119 + left: 0;
1120 + bottom: 0;
1121 + width: 50%;
1122 + overflow: hidden;
1123 + background: #f59e0b;
1124 + -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpolygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26'/%3E%3C/svg%3E") no-repeat left center / 200% 100%;
1125 + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpolygon points='12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26'/%3E%3C/svg%3E") no-repeat left center / 200% 100%;
1126 +}
1127 +
1128 +.yatra-rating-meta strong {
1129 + color: #0f172a;
1130 + margin-right: 6px;
1131 +}
1132 +
1133 +.yatra-rating-meta span {
1134 + color: #64748b;
1135 + font-size: 14px;
1136 +}
1137 +
739 1138 .yatra-trip-meta {
740 1139 display: flex;
741 1140 gap: 20px;
742 1141 flex-wrap: wrap;
@@ -784,8 +1183,13 @@
784 1183 .yatra-trip-summary {
785 1184 flex-direction: column;
786 1185 text-align: center;
787 1186 }
1187 +
1188 + .yatra-info-grid {
1189 + grid-template-columns: 1fr;
1190 + gap: 12px;
1191 + }
788 1192 }
789 1193
790 1194 .yatra-info-grid {
791 1195 display: grid;
@@ -796,13 +1200,18 @@
796 1200 .yatra-info-item {
797 1201 display: flex;
798 1202 flex-direction: column;
799 1203 gap: 4px;
1204 + padding: 12px 14px;
1205 + background: #f9fafb;
1206 + border: 1px solid #eef2f7;
1207 + border-radius: 12px;
800 1208 }
801 1209
802 1210 .yatra-info-label {
803 1211 font-size: 13px;
804 1212 color: #6b7280;
1213 + line-height: 1.2;
805 1214 }
806 1215
807 1216 .yatra-info-value {
808 1217 font-size: 15px;
@@ -807,8 +1216,10 @@
807 1216 .yatra-info-value {
808 1217 font-size: 15px;
809 1218 font-weight: 600;
810 1219 color: #111827;
1220 + line-height: 1.25;
1221 + text-align: left;
811 1222 }
812 1223
813 1224 .yatra-status-badge {
814 1225 display: inline-block;
@@ -883,8 +1294,44 @@
883 1294 .yatra-discount-row span:last-child {
884 1295 color: #22c55e;
885 1296 }
886 1297
1298 +/* Bank-transfer details (markup injected by Yatra Pro's Bank Transfer gateway
1299 + into this page). The card reuses this page's .yatra-confirmation-card /
1300 + .yatra-card-title classes, but its rows had no styling at all, so the values
1301 + sat immediately after their label while every other row on the page is
1302 + flex/space-between — the bank values were the only ones not flush right.
1303 + Styling lives here because this template owns the confirmation page's CSS
1304 + and Pro enqueues no stylesheet on this page. */
1305 +.yatra-bank-info-grid {
1306 + border: 1px solid #e5e7eb;
1307 + border-radius: 8px;
1308 + overflow: hidden;
1309 +}
1310 +
1311 +.yatra-bank-info-item {
1312 + display: flex;
1313 + justify-content: space-between;
1314 + align-items: baseline;
1315 + gap: 12px;
1316 + padding: 12px 16px;
1317 + border-bottom: 1px solid #e5e7eb;
1318 +}
1319 +
1320 +.yatra-bank-info-item:last-child {
1321 + border-bottom: none;
1322 +}
1323 +
1324 +.yatra-bank-label {
1325 + color: #6b7280;
1326 + flex-shrink: 0;
1327 +}
1328 +
1329 +.yatra-bank-value {
1330 + text-align: right;
1331 + word-break: break-word;
1332 +}
1333 +
887 1334 .yatra-payment-method {
888 1335 margin-top: 16px;
889 1336 padding: 12px;
890 1337 background: white;
@@ -992,18 +1439,411 @@
992 1439 border-color: #3b82f6;
993 1440 color: #3b82f6;
994 1441 }
995 1442
996 -/* Print Styles */
1443 +/* =====================================================================
1444 + Print Styles — produce a clean, single-page-friendly receipt.
1445 + Triggered by the print button (adds .yatra-printing-confirmation
1446 + to <body> + clones the confirmation card into .yatra-print-root).
1447 + ===================================================================== */
997 1448 @media print {
998 - .yatra-confirmation-actions {
999 - display: none;
1449 + @page {
1450 + size: A4;
1451 + margin: 9mm 10mm;
1000 1452 }
1001 -
1002 - .yatra-confirmation-wrapper {
1453 +
1454 + /* Force browsers to print our colors / borders */
1455 + body.yatra-printing-confirmation,
1456 + body.yatra-printing-confirmation * {
1457 + -webkit-print-color-adjust: exact !important;
1458 + print-color-adjust: exact !important;
1459 + color-adjust: exact !important;
1460 + }
1461 +
1462 + /* Reset body, kill animations & transitions */
1463 + body.yatra-printing-confirmation {
1464 + background: #fff !important;
1465 + color: #111827 !important;
1466 + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
1467 + Helvetica, Arial, sans-serif !important;
1468 + font-size: 10pt !important;
1469 + line-height: 1.45 !important;
1470 + }
1471 + body.yatra-printing-confirmation *,
1472 + body.yatra-printing-confirmation *::before,
1473 + body.yatra-printing-confirmation *::after {
1474 + animation: none !important;
1475 + transition: none !important;
1476 + box-shadow: none !important;
1477 + text-shadow: none !important;
1478 + }
1479 +
1480 + /* Hide everything except our cloned print root */
1481 + body.yatra-printing-confirmation > *:not(.yatra-print-root) {
1482 + display: none !important;
1483 + }
1484 + body.yatra-printing-confirmation .yatra-print-root,
1485 + body.yatra-printing-confirmation .yatra-print-root * {
1486 + visibility: visible;
1487 + }
1488 + body.yatra-printing-confirmation .yatra-print-root {
1489 + position: static !important;
1490 + left: auto;
1491 + top: auto;
1492 + width: 100%;
1493 + margin: 0;
1003 1494 padding: 0;
1004 1495 }
1496 +
1497 + /* Wrappers — remove screen padding/widths */
1498 + body.yatra-printing-confirmation .yatra-confirmation-wrapper,
1499 + body.yatra-printing-confirmation .yatra-confirmation-container {
1500 + max-width: none !important;
1501 + margin: 0 !important;
1502 + padding: 0 !important;
1503 + }
1504 +
1505 + /* Hide things that don't belong on paper */
1506 + body.yatra-printing-confirmation .yatra-confirmation-actions,
1507 + body.yatra-printing-confirmation .yatra-no-print,
1508 + body.yatra-printing-confirmation .yatra-whats-next,
1509 + body.yatra-printing-confirmation .yatra-trip-rating {
1510 + display: none !important;
1511 + }
1512 +
1513 + /* Confirmation header — compact, two-column with status icon left & ref right */
1514 + body.yatra-printing-confirmation .yatra-confirmation-header {
1515 + display: flex !important;
1516 + align-items: center !important;
1517 + justify-content: space-between !important;
1518 + gap: 16pt !important;
1519 + text-align: left !important;
1520 + margin: 0 0 8pt !important;
1521 + padding: 0 0 6pt !important;
1522 + border-bottom: 1pt solid #111827 !important;
1523 + }
1524 + body.yatra-printing-confirmation .yatra-confirmation-icon {
1525 + width: 28pt !important;
1526 + height: 28pt !important;
1527 + margin: 0 !important;
1528 + background: #16a34a !important;
1529 + flex-shrink: 0 !important;
1530 + }
1531 + body.yatra-printing-confirmation .yatra-confirmation-icon svg {
1532 + width: 16pt !important;
1533 + height: 16pt !important;
1534 + }
1535 + body.yatra-printing-confirmation .yatra-confirmation-title {
1536 + font-size: 14pt !important;
1537 + font-weight: 700 !important;
1538 + margin: 0 !important;
1539 + flex: 1 !important;
1540 + color: #111827 !important;
1541 + }
1542 + body.yatra-printing-confirmation .yatra-confirmation-subtitle {
1543 + display: none !important;
1544 + }
1545 + body.yatra-printing-confirmation .yatra-confirmation-reference {
1546 + display: inline-flex !important;
1547 + flex-direction: column !important;
1548 + align-items: flex-end !important;
1549 + gap: 2pt !important;
1550 + background: transparent !important;
1551 + padding: 0 !important;
1552 + border: none !important;
1553 + border-radius: 0 !important;
1554 + }
1555 + body.yatra-printing-confirmation .yatra-ref-label {
1556 + font-size: 8pt !important;
1557 + text-transform: uppercase !important;
1558 + letter-spacing: 0.4pt !important;
1559 + color: #6b7280 !important;
1560 + }
1561 + body.yatra-printing-confirmation .yatra-ref-code {
1562 + font-size: 11pt !important;
1563 + font-weight: 700 !important;
1564 + color: #111827 !important;
1565 + letter-spacing: 0.6pt !important;
1566 + }
1567 +
1568 + /* Cards — flat, thin border, never split across pages */
1569 + body.yatra-printing-confirmation .yatra-confirmation-card {
1570 + background: #fff !important;
1571 + border: 0.75pt solid #d1d5db !important;
1572 + border-radius: 3pt !important;
1573 + padding: 8pt 10pt !important;
1574 + margin: 0 0 6pt !important;
1575 + page-break-inside: avoid !important;
1576 + break-inside: avoid !important;
1577 + }
1578 + body.yatra-printing-confirmation .yatra-trip-summary-card {
1579 + background: #f9fafb !important;
1580 + border-color: #d1d5db !important;
1581 + }
1582 + body.yatra-printing-confirmation .yatra-card-title {
1583 + font-size: 10.5pt !important;
1584 + font-weight: 700 !important;
1585 + gap: 6pt !important;
1586 + margin: 0 0 8pt !important;
1587 + padding: 0 0 5pt !important;
1588 + border-bottom: 0.5pt solid #e5e7eb !important;
1589 + color: #111827 !important;
1590 + }
1591 + body.yatra-printing-confirmation .yatra-card-title svg {
1592 + width: 12pt !important;
1593 + height: 12pt !important;
1594 + color: #111827 !important;
1595 + }
1596 +
1597 + /* Trip summary — small image, tight spacing */
1598 + body.yatra-printing-confirmation .yatra-trip-summary {
1599 + gap: 10pt !important;
1600 + align-items: flex-start !important;
1601 + }
1602 + /* Images often render as empty placeholders in print preview; hide for a cleaner receipt. */
1603 + body.yatra-printing-confirmation .yatra-trip-image {
1604 + display: none !important;
1605 + }
1606 + body.yatra-printing-confirmation .yatra-trip-name {
1607 + font-size: 12pt !important;
1608 + font-weight: 700 !important;
1609 + margin: 0 0 4pt !important;
1610 + color: #111827 !important;
1611 + }
1612 + body.yatra-printing-confirmation .yatra-trip-meta {
1613 + font-size: 9pt !important;
1614 + gap: 10pt !important;
1615 + margin-bottom: 4pt !important;
1616 + color: #374151 !important;
1617 + }
1618 + body.yatra-printing-confirmation .yatra-trip-meta svg,
1619 + body.yatra-printing-confirmation .yatra-trip-location svg {
1620 + width: 9pt !important;
1621 + height: 9pt !important;
1622 + }
1623 + body.yatra-printing-confirmation .yatra-trip-location {
1624 + font-size: 9pt !important;
1625 + margin: 0 0 4pt !important;
1626 + color: #374151 !important;
1627 + }
1628 +
1629 + /* Tags — outline pills, no fills */
1630 + body.yatra-printing-confirmation .yatra-trip-tags {
1631 + font-size: 9pt !important;
1632 + margin-top: 4pt !important;
1633 + }
1634 + body.yatra-printing-confirmation .yatra-tag-label {
1635 + font-weight: 700 !important;
1636 + color: #374151 !important;
1637 + margin-right: 4pt !important;
1638 + }
1639 + body.yatra-printing-confirmation .yatra-tag-item {
1640 + display: inline-block !important;
1641 + background: #fff !important;
1642 + border: 0.5pt solid #d1d5db !important;
1643 + padding: 1pt 5pt !important;
1644 + border-radius: 2pt !important;
1645 + font-size: 8.5pt !important;
1646 + color: #374151 !important;
1647 + margin: 1pt 2pt 1pt 0 !important;
1648 + }
1649 +
1650 + /* Two-column grid stays two columns on paper */
1651 + body.yatra-printing-confirmation .yatra-confirmation-grid {
1652 + display: grid !important;
1653 + grid-template-columns: 1.15fr 0.85fr !important;
1654 + gap: 6pt !important;
1655 + align-items: start !important;
1656 + }
1657 + body.yatra-printing-confirmation .yatra-confirmation-column {
1658 + min-width: 0 !important;
1659 + }
1660 +
1661 + /* Booking info grid */
1662 + body.yatra-printing-confirmation .yatra-info-grid {
1663 + display: grid !important;
1664 + grid-template-columns: 1fr 1fr !important;
1665 + gap: 5pt 9pt !important;
1666 + }
1667 + body.yatra-printing-confirmation .yatra-info-item {
1668 + display: flex !important;
1669 + flex-direction: column !important;
1670 + gap: 1pt !important;
1671 + }
1672 + body.yatra-printing-confirmation .yatra-info-label {
1673 + font-size: 7.5pt !important;
1674 + text-transform: uppercase !important;
1675 + letter-spacing: 0.3pt !important;
1676 + color: #6b7280 !important;
1677 + }
1678 + body.yatra-printing-confirmation .yatra-info-value {
1679 + font-size: 10pt !important;
1680 + font-weight: 600 !important;
1681 + color: #111827 !important;
1682 + }
1683 + body.yatra-printing-confirmation .yatra-status-badge {
1684 + display: inline-block !important;
1685 + padding: 1pt 6pt !important;
1686 + border: 0.75pt solid currentColor !important;
1687 + border-radius: 2pt !important;
1688 + font-size: 8.5pt !important;
1689 + font-weight: 700 !important;
1690 + }
1691 +
1692 + /* Travelers */
1693 + body.yatra-printing-confirmation .yatra-travelers-list {
1694 + margin: 0 !important;
1695 + }
1696 + body.yatra-printing-confirmation .yatra-traveler-item {
1697 + display: flex !important;
1698 + align-items: center !important;
1699 + gap: 8pt !important;
1700 + padding: 4pt 0 !important;
1701 + border-bottom: 0.5pt solid #e5e7eb !important;
1702 + }
1703 + body.yatra-printing-confirmation .yatra-traveler-item:last-child {
1704 + border-bottom: none !important;
1705 + }
1706 + body.yatra-printing-confirmation .yatra-traveler-number {
1707 + display: inline-flex !important;
1708 + align-items: center !important;
1709 + justify-content: center !important;
1710 + width: 16pt !important;
1711 + height: 16pt !important;
1712 + background: #111827 !important;
1713 + color: #fff !important;
1714 + border-radius: 50% !important;
1715 + font-size: 8.5pt !important;
1716 + font-weight: 700 !important;
1717 + flex-shrink: 0 !important;
1718 + }
1719 + body.yatra-printing-confirmation .yatra-traveler-name {
1720 + font-size: 10pt !important;
1721 + font-weight: 600 !important;
1722 + color: #111827 !important;
1723 + }
1724 + body.yatra-printing-confirmation .yatra-traveler-dob {
1725 + font-size: 8.5pt !important;
1726 + color: #6b7280 !important;
1727 + }
1728 +
1729 + /* Payment summary rows */
1730 + body.yatra-printing-confirmation .yatra-payment-rows {
1731 + margin: 0 !important;
1732 + }
1733 + body.yatra-printing-confirmation .yatra-payment-row,
1734 + body.yatra-printing-confirmation .yatra-bank-info-item {
1735 + display: flex !important;
1736 + justify-content: space-between !important;
1737 + align-items: baseline !important;
1738 + gap: 12pt !important;
1739 + font-size: 9.25pt !important;
1740 + padding: 1.5pt 0 !important;
1741 + color: #111827 !important;
1742 + }
1743 + body.yatra-printing-confirmation .yatra-due-row {
1744 + background: #fef3c7 !important;
1745 + padding: 4pt 6pt !important;
1746 + margin-top: 5pt !important;
1747 + border-radius: 2pt !important;
1748 + border: 0.5pt solid #fbbf24 !important;
1749 + }
1750 + body.yatra-printing-confirmation .yatra-payment-method {
1751 + margin-top: 6pt !important;
1752 + padding-top: 5pt !important;
1753 + font-size: 9pt !important;
1754 + border-top: 0.5pt solid #e5e7eb !important;
1755 + color: #374151 !important;
1756 + }
1757 +
1758 + /* Contact info */
1759 + body.yatra-printing-confirmation .yatra-contact-info {
1760 + margin: 0 !important;
1761 + }
1762 + body.yatra-printing-confirmation .yatra-contact-item {
1763 + font-size: 9.5pt !important;
1764 + margin: 0 0 3pt !important;
1765 + color: #111827 !important;
1766 + }
1767 + body.yatra-printing-confirmation .yatra-contact-item strong {
1768 + color: #6b7280 !important;
1769 + font-weight: 600 !important;
1770 + margin-right: 4pt !important;
1771 + }
1772 +
1773 + /* Links — print as plain text, no underline (URL is still in browser footer) */
1774 + body.yatra-printing-confirmation a,
1775 + body.yatra-printing-confirmation a:visited {
1776 + color: inherit !important;
1777 + text-decoration: none !important;
1778 + }
1779 +
1780 + /* Avoid orphan headings */
1781 + body.yatra-printing-confirmation h1,
1782 + body.yatra-printing-confirmation h2,
1783 + body.yatra-printing-confirmation h3,
1784 + body.yatra-printing-confirmation h4 {
1785 + page-break-after: avoid !important;
1786 + break-after: avoid !important;
1787 + }
1005 1788 }
1006 1789 </style>
1790 +
1791 +<script>
1792 +(function () {
1793 + document.addEventListener('click', function (e) {
1794 + var btn = e.target.closest('[data-yatra-print="confirmation"]');
1795 + if (!btn) {
1796 + return;
1797 + }
1798 + e.preventDefault();
1799 +
1800 + var card = document.querySelector('.yatra-confirmation-container');
1801 + if (!card) {
1802 + window.print();
1803 + return;
1804 + }
1805 +
1806 + // Wrap (or reuse) a print-only root so the @media print rules can target a single
1807 + // direct child of <body>. Reused on the account page if the same data attribute
1808 + // and CSS rules are added there.
1809 + var root = document.querySelector('.yatra-print-root');
1810 + var addedWrapper = false;
1811 + if (!root) {
1812 + root = document.createElement('div');
1813 + root.className = 'yatra-print-root';
1814 + document.body.appendChild(root);
1815 + addedWrapper = true;
1816 + }
1817 + var clone = card.cloneNode(true);
1818 + // Drop the action buttons from the clone so they don't appear on paper.
1819 + clone.querySelectorAll('.yatra-confirmation-actions').forEach(function (n) { n.remove(); });
1820 + root.innerHTML = '';
1821 + root.appendChild(clone);
1822 +
1823 + document.body.classList.add('yatra-printing-confirmation');
1824 +
1825 + var cleanup = function () {
1826 + document.body.classList.remove('yatra-printing-confirmation');
1827 + if (addedWrapper && root && root.parentNode) {
1828 + root.parentNode.removeChild(root);
1829 + } else if (root) {
1830 + root.innerHTML = '';
1831 + }
1832 + window.removeEventListener('afterprint', cleanup);
1833 + };
1834 +
1835 + window.addEventListener('afterprint', cleanup);
1836 + // Safety net for browsers that don't fire afterprint reliably.
1837 + window.setTimeout(function () {
1838 + if (document.body.classList.contains('yatra-printing-confirmation')) {
1839 + cleanup();
1840 + }
1841 + }, 5000);
1842 +
1843 + window.print();
1844 + });
1845 +})();
1846 +</script>
1007 1847
1008 1848 <?php yatra_get_footer(); ?>
1009 1849