| @@ -351,11 +351,19 @@ | ||
| 351 | 351 | $total_paid = $paymentRepository->getTotalPaidForBooking($booking_id); |
| 352 | 352 | $total_amount = (float) $booking->total_amount; |
| 353 | 353 | |
| 354 | 354 | if ($total_paid >= $total_amount) { |
| 355 | + // Fully paid. Respect the Auto-Confirm mode (same as every | |
| 356 | + // other payment-completion path) — only confirm when the | |
| 357 | + // mode is 'online' or 'all'; otherwise record the payment | |
| 358 | + // and leave the booking pending for manual confirmation. | |
| 355 | 359 | $prevStatus = (string) ($booking->status ?? 'pending'); |
| 356 | - $bookingRepository->update($booking_id, ['status' => 'confirmed', 'payment_status' => 'paid']); | |
| 357 | - \yatra_trigger_booking_confirmed((int) $booking_id, $prevStatus); | |
| 360 | + if (\yatra_should_confirm_booking_on_payment(true, (int) $booking_id)) { | |
| 361 | + $bookingRepository->update($booking_id, ['status' => 'confirmed', 'payment_status' => 'paid']); | |
| 362 | + \yatra_trigger_booking_confirmed((int) $booking_id, $prevStatus, true); | |
| 363 | + } else { | |
| 364 | + $bookingRepository->update($booking_id, ['payment_status' => 'paid']); | |
| 365 | + } | |
| 358 | 366 | } else { |
| 359 | 367 | $bookingRepository->update($booking_id, ['payment_status' => 'partial']); |
| 360 | 368 | } |
| 361 | 369 | } |
| @@ -888,8 +896,11 @@ | ||
| 888 | 896 | 'slug' => $trip->slug, |
| 889 | 897 | 'featured_image' => $trip->featured_image, |
| 890 | 898 | 'duration_days' => (int) $trip->duration_days, |
| 891 | 899 | 'duration_nights' => (int) $trip->duration_nights, |
| 900 | + // Hour-based day tours (0 on every day-based trip). Additive field: | |
| 901 | + // existing consumers keep reading duration_days/duration_nights. | |
| 902 | + 'duration_hours' => (int) ($trip->duration_hours ?? 0), | |
| 892 | 903 | 'difficulty_level' => $trip->difficulty_level, |
| 893 | 904 | 'min_travelers' => (int) ($trip->min_travelers ?: 1), |
| 894 | 905 | 'max_travelers' => (int) ($trip->max_travelers ?: 20), |
| 895 | 906 | 'original_price' => (float) $trip->original_price, |
| @@ -1279,9 +1290,9 @@ | ||
| 1279 | 1290 | // GET BOOKING SETTINGS |
| 1280 | 1291 | // ======================================== |
| 1281 | 1292 | $settings = [ |
| 1282 | 1293 | 'booking_confirmation' => \Yatra\Services\SettingsService::get('booking_confirmation', true), |
| 1283 | - 'auto_confirm_bookings' => \Yatra\Services\SettingsService::get('auto_confirm_bookings', false), | |
| 1294 | + 'auto_confirm_mode' => \yatra_get_auto_confirm_mode(), | |
| 1284 | 1295 | 'require_login' => \Yatra\Services\SettingsService::get('require_login', false), |
| 1285 | 1296 | 'allow_guest_checkout' => \Yatra\Services\SettingsService::get('allow_guest_checkout', true), |
| 1286 | 1297 | 'booking_expiry_hours' => (int) \Yatra\Services\SettingsService::get('booking_expiry_hours', 24), |
| 1287 | 1298 | 'auto_confirm_pay_later' => \Yatra\Services\SettingsService::get('auto_confirm_pay_later', true), |
| @@ -1364,9 +1375,13 @@ | ||
| 1364 | 1375 | // Which booking-form sections are enabled (Pro Dynamic Form module). |
| 1365 | 1376 | // The default config has every section enabled, so on existing/un-customised |
| 1366 | 1377 | // sites $contact_enabled and $traveler_enabled are both true and the logic |
| 1367 | 1378 | // below behaves exactly as before — only disabled sections change anything. |
| 1368 | - $form_config = function_exists('yatra_get_booking_form_config') ? yatra_get_booking_form_config() : []; | |
| 1379 | + // Scoped to the trip being booked — the same config the checkout | |
| 1380 | + // rendered, so a field hidden for this trip is never treated as required. | |
| 1381 | + $form_config = function_exists('yatra_get_booking_form_config') | |
| 1382 | + ? yatra_get_booking_form_config($trip_id > 0 ? (int) $trip_id : null) | |
| 1383 | + : []; | |
| 1369 | 1384 | $contact_enabled = !isset($form_config['contact_form']['enabled']) || (bool) $form_config['contact_form']['enabled']; |
| 1370 | 1385 | $traveler_enabled = !isset($form_config['traveler_form']['enabled']) || (bool) $form_config['traveler_form']['enabled']; |
| 1371 | 1386 | |
| 1372 | 1387 | // Get contact email - handle both flat and nested formats |
| @@ -2429,21 +2444,29 @@ | ||
| 2429 | 2444 | |
| 2430 | 2445 | // ======================================== |
| 2431 | 2446 | // DETERMINE BOOKING STATUS |
| 2432 | 2447 | // ======================================== |
| 2433 | - // Priority: | |
| 2434 | - // 1. auto_confirm_bookings setting (confirms ALL bookings automatically) | |
| 2435 | - // 2. For pay_later: auto_confirm_pay_later setting | |
| 2436 | - // 3. For bank_transfer: always pending until verified | |
| 2437 | - | |
| 2448 | + // Priority (Auto-Confirm mode: none | online | all): | |
| 2449 | + // - 'all' → confirm every booking here at checkout. | |
| 2450 | + // - 'online' → confirm nothing at checkout; only a successful online | |
| 2451 | + // gateway payment confirms later (offline stays pending). | |
| 2452 | + // - 'none' → per-method: pay_later uses auto_confirm_pay_later, | |
| 2453 | + // bank_transfer stays pending, everything else pending. | |
| 2454 | + | |
| 2438 | 2455 | $booking_status = 'pending'; |
| 2439 | 2456 | $status_message = __('Booking received!', 'yatra'); |
| 2440 | - | |
| 2441 | - // Check if auto-confirm all bookings is enabled | |
| 2442 | - if ($settings['auto_confirm_bookings']) { | |
| 2443 | - // Auto-confirm is enabled - confirm immediately regardless of payment | |
| 2457 | + | |
| 2458 | + $auto_confirm_mode = $settings['auto_confirm_mode'] ?? 'none'; | |
| 2459 | + if ($auto_confirm_mode === 'all') { | |
| 2460 | + // Confirm every booking immediately, regardless of payment. | |
| 2444 | 2461 | $booking_status = 'confirmed'; |
| 2445 | 2462 | $status_message = __('Booking confirmed!', 'yatra'); |
| 2463 | + } elseif ($auto_confirm_mode === 'online') { | |
| 2464 | + // Only successful online payments auto-confirm (at payment | |
| 2465 | + // completion). Leave the booking pending at checkout; offline | |
| 2466 | + // methods (bank transfer, pay-later) stay pending for the operator. | |
| 2467 | + $booking_status = 'pending'; | |
| 2468 | + $status_message = __('Booking received!', 'yatra'); | |
| 2446 | 2469 | } elseif ($payment_gateway === 'pay_later') { |
| 2447 | 2470 | // Pay Later: Check the specific pay_later auto-confirm setting |
| 2448 | 2471 | if ($settings['auto_confirm_pay_later']) { |
| 2449 | 2472 | $booking_status = 'confirmed'; |
| @@ -2863,11 +2886,15 @@ | ||
| 2863 | 2886 | // Default return_url to the configured booking confirmation URL so redirect gateways |
| 2864 | 2887 | // (e.g. PayPal Advanced, Mollie, Paystack) do not fall back to wrong paths; gateways |
| 2865 | 2888 | // may still append their own query args on top of this URL. |
| 2866 | 2889 | $ref = isset($params['reference']) ? trim((string) $params['reference']) : ''; |
| 2890 | + // Cancel returns must land on the booking-confirmation page (always resolvable); | |
| 2891 | + // `home_url('/book/?...')` 404s under a custom booking base/page. Use the reference, | |
| 2892 | + // falling back to the booking id so the confirmation route always has a token. | |
| 2893 | + $cancelRef = $ref !== '' ? $ref : (string) ($params['booking_id'] ?? ''); | |
| 2867 | 2894 | $paymentData = array_merge($params, [ |
| 2868 | 2895 | 'description' => $params['trip_title'] ?? '', |
| 2869 | - 'cancel_url' => home_url('/book/?payment=cancelled&ref=' . ($params['reference'] ?? '')), | |
| 2896 | + 'cancel_url' => add_query_arg('payment', 'cancelled', $this->getConfirmationUrl($cancelRef)), | |
| 2870 | 2897 | 'metadata' => [ |
| 2871 | 2898 | 'booking_id' => $params['booking_id'], |
| 2872 | 2899 | 'reference' => $params['reference'] ?? '' |
| 2873 | 2900 | ] |
| @@ -3068,17 +3095,18 @@ | ||
| 3068 | 3095 | // (Square, Authorize.Net) reach this generic path but previously left |
| 3069 | 3096 | // the booking at pending/pending — only the payment row was written. |
| 3070 | 3097 | // This now matches handle_successful_payment(): accumulate amount_paid, |
| 3071 | 3098 | // recompute amount_due, set payment_status (paid vs partial), and |
| 3072 | - // confirm the booking (a deposit confirms too, consistent with Stripe). | |
| 3099 | + // confirm the booking only when "Auto-Confirm Bookings" is on | |
| 3100 | + // (consistent with every gateway). | |
| 3073 | 3101 | $newAmountPaid = (float) ($booking->amount_paid ?? 0) + $amount; |
| 3074 | 3102 | $newAmountDue = max(0.0, (float) ($booking->total_amount ?? 0) - $newAmountPaid); |
| 3075 | 3103 | $paymentStatus = $newAmountDue > 0.0 ? 'partial' : 'paid'; |
| 3076 | 3104 | $previousStatus = (string) ($booking->status ?? 'pending'); |
| 3077 | 3105 | |
| 3078 | - // Only auto-confirm when the operator allows it (or fully paid). A | |
| 3079 | - // deposit / partial payment must not confirm when "Auto-Confirm | |
| 3080 | - // Bookings" is off. | |
| 3106 | + // Only auto-confirm when "Auto-Confirm Bookings" is on; otherwise the | |
| 3107 | + // booking stays pending for the operator to confirm manually, | |
| 3108 | + // regardless of a successful (full or partial) payment. | |
| 3081 | 3109 | $shouldConfirm = \yatra_should_confirm_booking_on_payment($newAmountDue <= 0.0, $bookingId); |
| 3082 | 3110 | |
| 3083 | 3111 | $bookingUpdate = [ |
| 3084 | 3112 | 'amount_paid' => $newAmountPaid, |
| @@ -3090,9 +3118,9 @@ | ||
| 3090 | 3118 | } |
| 3091 | 3119 | $this->bookingRepository->update($bookingId, $bookingUpdate); |
| 3092 | 3120 | |
| 3093 | 3121 | if ($shouldConfirm && function_exists('yatra_trigger_booking_confirmed')) { |
| 3094 | - \yatra_trigger_booking_confirmed($bookingId, $previousStatus); | |
| 3122 | + \yatra_trigger_booking_confirmed($bookingId, $previousStatus, true); | |
| 3095 | 3123 | } |
| 3096 | 3124 | |
| 3097 | 3125 | // Fire payment completed action |
| 3098 | 3126 | do_action('yatra_payment_completed', [ |
| @@ -3160,9 +3188,9 @@ | ||
| 3160 | 3188 | 'description' => $params['trip_title'], |
| 3161 | 3189 | ]], |
| 3162 | 3190 | 'application_context' => [ |
| 3163 | 3191 | 'return_url' => add_query_arg('payment', 'success', $this->getConfirmationUrl($params['reference'])), |
| 3164 | - 'cancel_url' => home_url('/book/?payment=cancelled&ref=' . $params['reference']), | |
| 3192 | + 'cancel_url' => add_query_arg('payment', 'cancelled', $this->getConfirmationUrl($params['reference'])), | |
| 3165 | 3193 | ], |
| 3166 | 3194 | ]), |
| 3167 | 3195 | ]); |
| 3168 | 3196 | |
| @@ -3281,9 +3309,12 @@ | ||
| 3281 | 3309 | 'su' => add_query_arg( |
| 3282 | 3310 | ['payment' => 'success', 'gateway' => 'esewa'], |
| 3283 | 3311 | $this->getConfirmationUrl($params['reference']) |
| 3284 | 3312 | ), |
| 3285 | - 'fu' => home_url('/book/?payment=failed&ref=' . $params['reference']), | |
| 3313 | + 'fu' => add_query_arg( | |
| 3314 | + ['payment' => 'failed', 'gateway' => 'esewa'], | |
| 3315 | + $this->getConfirmationUrl($params['reference']) | |
| 3316 | + ), | |
| 3286 | 3317 | ], $base_url); |
| 3287 | 3318 | |
| 3288 | 3319 | return ['success' => true, 'payment_url' => $payment_url]; |
| 3289 | 3320 | } |
| @@ -3435,9 +3466,9 @@ | ||
| 3435 | 3466 | <p style="margin:0 0 8px;"><strong><?php esc_html_e('Booking reference', 'yatra'); ?>:</strong> <?php echo esc_html($reference); ?></p> |
| 3436 | 3467 | <p style="margin:0 0 8px;"><strong><?php esc_html_e('Trip', 'yatra'); ?>:</strong> <?php echo esc_html($trip->title); ?></p> |
| 3437 | 3468 | <p style="margin:0 0 8px;"><strong><?php esc_html_e('Travel date', 'yatra'); ?>:</strong> <?php echo esc_html(date_i18n(get_option('date_format'), strtotime($travel_date))); ?></p> |
| 3438 | 3469 | <p style="margin:0 0 8px;"><strong><?php esc_html_e('Duration', 'yatra'); ?>:</strong> <?php /* translators: 1: number of days, 2: number of nights. */ |
| 3439 | -echo esc_html(sprintf(__('%1$d days / %2$d nights', 'yatra'), (int) $trip->duration_days, (int) $trip->duration_nights)); ?></p> | |
| 3470 | +echo esc_html(yatra_format_duration((int) $trip->duration_days, (int) $trip->duration_nights, (int) ($trip->duration_hours ?? 0))); ?></p> | |
| 3440 | 3471 | <p style="margin:0;"><strong><?php esc_html_e('Travelers', 'yatra'); ?>:</strong> <?php echo esc_html((string) count($travelers)); ?></p> |
| 3441 | 3472 | </div> |
| 3442 | 3473 | <h3 style="font-size:16px;"><?php esc_html_e('Payment details', 'yatra'); ?></h3> |
| 3443 | 3474 | <p><?php /* translators: %s: total amount (formatted). */ |
| @@ -4511,9 +4542,11 @@ | ||
| 4511 | 4542 | // Pro can already override per-trip via trip.deposit_percentage), then |
| 4512 | 4543 | // hand off to `yatra_calculate_amount_due` so Pro can apply absolute |
| 4513 | 4544 | // overrides too (e.g. trip.deposit_amount as a fixed cap). Doing both |
| 4514 | 4545 | // keeps the math consistent with CalculationService::calculatePaymentAmounts(). |
| 4515 | - $context = ['trip_id' => $trip_id]; | |
| 4546 | + // Tour start → Pro can force full payment when the tour is within the | |
| 4547 | + // balance-due window (tour-anchored scheduled payments). | |
| 4548 | + $context = ['trip_id' => $trip_id, 'travel_date' => (string) ($travel_date ?? '')]; | |
| 4516 | 4549 | $flexible_payments_enabled = apply_filters('yatra_flexible_payments_enabled', false); |
| 4517 | 4550 | $deposit_percentage = (int) apply_filters('yatra_deposit_percentage', 20, $context); |
| 4518 | 4551 | $partial_percentage = (int) apply_filters('yatra_partial_payment_percentage', 30, $context); |
| 4519 | 4552 | |