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 | app/Controllers/PaymentGatewayController.php +71 -14 3.0.13 → 3.0.16 View file →
@@ -481,9 +481,12 @@
481 481 $paymentData['return_url'] = add_query_arg('payment', 'success', $this->getConfirmationUrl($reference));
482 482 }
483 483
484 484 $cancelParam = esc_url_raw($request->get_param('cancel_url'));
485 - $paymentData['cancel_url'] = $cancelParam ?: home_url('/book/?payment=cancelled&ref=' . ($paymentData['reference'] ?? $paymentData['booking_id']));
485 + // Fall back to the booking-confirmation page (always a resolvable route) rather
486 + // than `home_url('/book/?...')`, which 404s under a custom booking base/page.
487 + $cancelReference = (string) ($paymentData['reference'] ?? $paymentData['booking_id']);
488 + $paymentData['cancel_url'] = $cancelParam ?: add_query_arg('payment', 'cancelled', $this->getConfirmationUrl($cancelReference));
486 489
487 490 if ($paymentData['amount'] <= 0) {
488 491 return new WP_Error('invalid_amount', __('Invalid payment amount', 'yatra'), ['status' => 400]);
489 492 }
@@ -539,8 +542,22 @@
539 542 if (!$gateway) {
540 543 return new WP_Error('invalid_gateway', __('Gateway not found', 'yatra'), ['status' => 404]);
541 544 }
542 545
546 + // Offline gateways (Bank Transfer, Pay Later) settle out of band and take
547 + // no money at checkout. They must NEVER be auto-completed through this
548 + // endpoint — doing so marks the booking paid + records a "completed"
549 + // payment before any funds have arrived. Confirmation is a manual admin
550 + // action once the operator sees the money. Guard here as well as in the
551 + // gateways' verifyPayment() so a future offline gateway can't regress.
552 + if ($gateway->isOffline()) {
553 + return new WP_Error(
554 + 'offline_gateway_manual',
555 + __('This payment method is settled manually and cannot be confirmed automatically.', 'yatra'),
556 + ['status' => 400]
557 + );
558 + }
559 +
543 560 if ($bookingId <= 0 || $transactionId === '') {
544 561 return new WP_Error('invalid_request', __('booking_id and transaction_id are required.', 'yatra'), ['status' => 400]);
545 562 }
546 563
@@ -646,10 +663,19 @@
646 663 wp_redirect(home_url('/booking-failed/'));
647 664 exit;
648 665 }
649 666
667 + // Offline gateways never redirect here, and must never be auto-completed:
668 + // they settle out of band and are confirmed manually by the operator.
669 + // Bounce a spoofed `?status=success` callback to the confirmation page
670 + // (still pending) rather than recording a payment that never happened.
671 + if ($gateway->isOffline()) {
672 + wp_redirect(yatra_get_booking_confirmation_url($bookingId > 0 ? (string) $bookingId : ''));
673 + exit;
674 + }
675 +
650 676 // Get transaction ID from request (varies by gateway)
651 - $transactionId = $request->get_param('refId')
677 + $transactionId = $request->get_param('refId')
652 678 ?? $request->get_param('pidx')
653 679 ?? $request->get_param('transaction_id')
654 680 ?? '';
655 681
@@ -786,11 +812,11 @@
786 812 }
787 813
788 814 $previousBookingStatus = (string) ($booking->status ?? 'pending');
789 815
790 - // Only auto-confirm when the operator allows it (or the booking is now
791 - // fully paid). A deposit / partial payment leaves the booking pending
792 - // when "Auto-Confirm Bookings" is off, for the operator to confirm.
816 + // Only auto-confirm when "Auto-Confirm Bookings" is on; otherwise the
817 + // booking stays pending for the operator to confirm manually, regardless
818 + // of a successful (full or partial) payment.
793 819 $should_confirm = \yatra_should_confirm_booking_on_payment($new_amount_due <= 0, $bookingId);
794 820
795 821 // Update booking
796 822 $booking_update = [
@@ -803,9 +829,9 @@
803 829 }
804 830 $this->bookingRepository->update($bookingId, $booking_update);
805 831
806 832 if ($should_confirm) {
807 - \yatra_trigger_booking_confirmed($bookingId, $previousBookingStatus);
833 + \yatra_trigger_booking_confirmed($bookingId, $previousBookingStatus, true);
808 834 }
809 835
810 836 // Clear remaining payment session if this was a remaining payment
811 837 if (function_exists('yatra_has_remaining_session') && yatra_has_remaining_session()) {
@@ -842,9 +868,11 @@
842 868 return new WP_Error('payment_not_found', __('Payment not found.', 'yatra'), ['status' => 404]);
843 869 }
844 870
845 871 // Authorisation:
846 - // 1. Administrators can always access (no further checks).
872 + // 1. Staff can always access (no further checks): a WP administrator,
873 + // or a user holding Yatra's yatra_view_bookings capability, which is
874 + // the same audience that already sees every booking in the list.
847 875 // 2. Logged-in owner of the booking can access.
848 876 // 3. Anyone with a valid signed `invoice_token` (HMAC) can access — used on the
849 877 // booking-confirmation page so guest checkouts and post-session views work.
850 878 // 4. Legacy guest path: `booking_token` (active checkout transient) — kept for BC.
@@ -850,9 +878,9 @@
850 878 // 4. Legacy guest path: `booking_token` (active checkout transient) — kept for BC.
851 879 $currentUserId = (int) get_current_user_id();
852 880 $bookingUserId = (int) ($payment->booking_user_id ?? $payment->user_id ?? 0);
853 881 $paymentBookingId = (int) ($payment->booking_id ?? 0);
854 - $isAdmin = current_user_can('manage_options');
882 + $isAdmin = current_user_can('manage_options') || current_user_can('yatra_view_bookings');
855 883 $authorised = false;
856 884
857 885 if ($isAdmin) {
858 886 $authorised = true;
@@ -969,10 +997,15 @@
969 997 'payment_date' => $paymentDate,
970 998 'payment_status' => ucfirst($payment->status ?? 'paid'),
971 999 'status_class' => in_array(strtolower((string) ($payment->status ?? '')), ['paid', 'completed', 'success'], true) ? 'paid' : 'pending',
972 1000 'trip_title' => $trip->title ?? $payment->trip_title ?? __('Trip Booking', 'yatra'),
973 - 'payment_method' => ucfirst($payment->gateway ?? $payment->payment_method ?? 'Online'),
974 - 'booking_ref' => $payment->booking_reference ?? $payment->booking_number ?? '',
1001 + 'payment_method' => $this->gatewayLabel(
1002 + $payment->gateway ?? $payment->payment_method ?? null,
1003 + __('Online', 'yatra')
1004 + ),
1005 + // Booking-only fallback chain (never a payment identifier) so the
1006 + // invoice number always resolves to the booking reference.
1007 + 'booking_ref' => $payment->booking_reference ?? $payment->booking_number ?? (string) ($payment->booking_id ?? ''),
975 1008 'travel_date' => $travelDate,
976 1009 'currency_symbol' => $currencySymbol,
977 1010 'amount' => yatra_format_price((float) ($payment->amount ?? 0), $currency, false),
978 1011 'booking_total' => yatra_format_price((float) ($payment->booking_total_amount ?? $payment->amount ?? 0), $currency, false),
@@ -1003,8 +1036,23 @@
1003 1036 }
1004 1037 }
1005 1038
1006 1039 /**
1040 + * Customer-facing label for a gateway id on a document.
1041 + *
1042 + * Uses the same title the checkout shows (operator's custom title, else the
1043 + * gateway's translated one). Falls back to the prettified id — the previous
1044 + * behaviour — when the gateway is not registered any more, so an invoice for
1045 + * a payment taken through a since-removed gateway still reads sensibly.
1046 + */
1047 + private function gatewayLabel(?string $gatewayId, string $fallback = 'Online'): string
1048 + {
1049 + return function_exists('yatra_payment_gateway_label')
1050 + ? yatra_payment_gateway_label($gatewayId, $fallback)
1051 + : ($gatewayId ? ucwords(str_replace(['_', '-'], ' ', $gatewayId)) : $fallback);
1052 + }
1053 +
1054 + /**
1007 1055 * Download a PRO-FORMA invoice for a booking that has no payment yet
1008 1056 * (offline gateways such as Bank Transfer). Shows the amount due and any
1009 1057 * gateway-supplied payment instructions (via yatra_invoice_payment_instructions)
1010 1058 * so the customer knows how to pay. Renders the same pdf/invoice.php template.
@@ -1025,14 +1073,17 @@
1025 1073 if (!$booking) {
1026 1074 return new WP_Error('booking_not_found', __('Booking not found.', 'yatra'), ['status' => 404]);
1027 1075 }
1028 1076
1029 - // Authorisation mirrors download_invoice: admin -> owner -> signed
1077 + // Authorisation mirrors download_invoice: staff -> owner -> signed
1030 1078 // booking-scoped invoice_token (paymentId 0) -> guest booking_token.
1079 + // Staff means a WP admin OR a user holding Yatra's booking-view
1080 + // capability, so Pro Team roles (which deliberately don't carry
1081 + // manage_options) can use the admin "Download invoice" action.
1031 1082 $currentUserId = (int) get_current_user_id();
1032 1083 $bookingUserId = (int) ($booking->user_id ?? 0);
1033 1084 $authorised = false;
1034 - if (current_user_can('manage_options')) {
1085 + if (current_user_can('manage_options') || current_user_can('yatra_view_bookings')) {
1035 1086 $authorised = true;
1036 1087 } elseif ($currentUserId && $bookingUserId && $currentUserId === $bookingUserId) {
1037 1088 $authorised = true;
1038 1089 } elseif ($invoiceToken !== '' && self::verifyInvoiceToken($invoiceToken, 0, $bookingId)) {
@@ -1086,9 +1137,12 @@
1086 1137 ? __('Paid', 'yatra')
1087 1138 : ($paid > 0.0 ? __('Partially Paid', 'yatra') : __('Payment Pending', 'yatra')),
1088 1139 'status_class' => $due <= 0.0 ? 'paid' : ($paid > 0.0 ? 'partial' : 'pending'),
1089 1140 'trip_title' => $trip->title ?? $booking->trip_title ?? __('Trip Booking', 'yatra'),
1090 - 'payment_method' => ucwords(str_replace('_', ' ', (string) ($booking->payment_gateway ?? 'offline'))),
1141 + 'payment_method' => $this->gatewayLabel(
1142 + $booking->payment_gateway ?? null,
1143 + __('Offline', 'yatra')
1144 + ),
1091 1145 'booking_ref' => $bookingRef,
1092 1146 'travel_date' => $travelDate,
1093 1147 'currency_symbol' => $currencySymbol,
1094 1148 'amount' => yatra_format_price((float) $due, $currency, false),
@@ -1224,9 +1278,12 @@
1224 1278 'trip_duration' => yatra_format_duration(
1225 1279 (int) ($payment->trip_duration_days ?? ($trip->duration_days ?? 0)),
1226 1280 isset($payment->trip_duration_nights)
1227 1281 ? (int) $payment->trip_duration_nights
1228 - : (isset($trip->duration_nights) ? (int) $trip->duration_nights : null)
1282 + : (isset($trip->duration_nights) ? (int) $trip->duration_nights : null),
1283 + // Hour-based day tours: hours come from the loaded trip (the payment
1284 + // join carries only days/nights); a soft-deleted trip falls back to days.
1285 + (int) ($trip->duration_hours ?? 0)
1229 1286 ),
1230 1287 'trip_difficulty' => $trip ? ($trip->difficulty_name ?? '') : '',
1231 1288 'departure_location' => $trip ? ($trip->departure_location ?? '') : '',
1232 1289 'destination' => $trip ? ($trip->destination ?? $payment->destination ?? '') : ($payment->destination ?? ''),