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 +74 -14 3.0.11 → 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;
@@ -951,8 +979,9 @@
951 979
952 980 $templateData = [
953 981 'company_name' => $companyName,
954 982 'company_address' => $companyAddress,
983 + 'company_address_lines' => \Yatra\Helpers\FormatHelper::companyAddressLines(),
955 984 'company_email' => $companyEmail,
956 985 'company_phone' => $companyPhone,
957 986 'customer_name' => trim(($payment->contact_first_name ?? '') . ' ' . ($payment->contact_last_name ?? '')) ?: ($payment->customer_name ?? __('Customer', 'yatra')),
958 987 'customer_email' => $payment->contact_email ?? $payment->customer_email ?? '',
@@ -968,10 +997,15 @@
968 997 'payment_date' => $paymentDate,
969 998 'payment_status' => ucfirst($payment->status ?? 'paid'),
970 999 'status_class' => in_array(strtolower((string) ($payment->status ?? '')), ['paid', 'completed', 'success'], true) ? 'paid' : 'pending',
971 1000 'trip_title' => $trip->title ?? $payment->trip_title ?? __('Trip Booking', 'yatra'),
972 - 'payment_method' => ucfirst($payment->gateway ?? $payment->payment_method ?? 'Online'),
973 - '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 ?? ''),
974 1008 'travel_date' => $travelDate,
975 1009 'currency_symbol' => $currencySymbol,
976 1010 'amount' => yatra_format_price((float) ($payment->amount ?? 0), $currency, false),
977 1011 'booking_total' => yatra_format_price((float) ($payment->booking_total_amount ?? $payment->amount ?? 0), $currency, false),
@@ -1002,8 +1036,23 @@
1002 1036 }
1003 1037 }
1004 1038
1005 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 + /**
1006 1055 * Download a PRO-FORMA invoice for a booking that has no payment yet
1007 1056 * (offline gateways such as Bank Transfer). Shows the amount due and any
1008 1057 * gateway-supplied payment instructions (via yatra_invoice_payment_instructions)
1009 1058 * so the customer knows how to pay. Renders the same pdf/invoice.php template.
@@ -1024,14 +1073,17 @@
1024 1073 if (!$booking) {
1025 1074 return new WP_Error('booking_not_found', __('Booking not found.', 'yatra'), ['status' => 404]);
1026 1075 }
1027 1076
1028 - // Authorisation mirrors download_invoice: admin -> owner -> signed
1077 + // Authorisation mirrors download_invoice: staff -> owner -> signed
1029 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.
1030 1082 $currentUserId = (int) get_current_user_id();
1031 1083 $bookingUserId = (int) ($booking->user_id ?? 0);
1032 1084 $authorised = false;
1033 - if (current_user_can('manage_options')) {
1085 + if (current_user_can('manage_options') || current_user_can('yatra_view_bookings')) {
1034 1086 $authorised = true;
1035 1087 } elseif ($currentUserId && $bookingUserId && $currentUserId === $bookingUserId) {
1036 1088 $authorised = true;
1037 1089 } elseif ($invoiceToken !== '' && self::verifyInvoiceToken($invoiceToken, 0, $bookingId)) {
@@ -1070,8 +1122,9 @@
1070 1122
1071 1123 $templateData = [
1072 1124 'company_name' => SettingsService::get('company_name', get_bloginfo('name')),
1073 1125 'company_address' => SettingsService::get('company_address', ''),
1126 + 'company_address_lines' => \Yatra\Helpers\FormatHelper::companyAddressLines(),
1074 1127 'company_email' => SettingsService::get('company_email', get_option('admin_email')),
1075 1128 'company_phone' => SettingsService::get('company_phone', ''),
1076 1129 'customer_name' => trim(($booking->contact_first_name ?? '') . ' ' . ($booking->contact_last_name ?? '')) ?: __('Customer', 'yatra'),
1077 1130 'customer_email' => $booking->contact_email ?? '',
@@ -1084,9 +1137,12 @@
1084 1137 ? __('Paid', 'yatra')
1085 1138 : ($paid > 0.0 ? __('Partially Paid', 'yatra') : __('Payment Pending', 'yatra')),
1086 1139 'status_class' => $due <= 0.0 ? 'paid' : ($paid > 0.0 ? 'partial' : 'pending'),
1087 1140 'trip_title' => $trip->title ?? $booking->trip_title ?? __('Trip Booking', 'yatra'),
1088 - 'payment_method' => ucwords(str_replace('_', ' ', (string) ($booking->payment_gateway ?? 'offline'))),
1141 + 'payment_method' => $this->gatewayLabel(
1142 + $booking->payment_gateway ?? null,
1143 + __('Offline', 'yatra')
1144 + ),
1089 1145 'booking_ref' => $bookingRef,
1090 1146 'travel_date' => $travelDate,
1091 1147 'currency_symbol' => $currencySymbol,
1092 1148 'amount' => yatra_format_price((float) $due, $currency, false),
@@ -1203,8 +1259,9 @@
1203 1259
1204 1260 $templateData = [
1205 1261 'company_name' => $companyName,
1206 1262 'company_address' => $companyAddress,
1263 + 'company_address_lines' => \Yatra\Helpers\FormatHelper::companyAddressLines(),
1207 1264 'company_email' => $companyEmail,
1208 1265 'company_phone' => $companyPhone,
1209 1266 'customer_name' => trim(($payment->contact_first_name ?? '') . ' ' . ($payment->contact_last_name ?? '')) ?: ($payment->customer_name ?? __('Customer', 'yatra')),
1210 1267 'customer_email' => $payment->contact_email ?? $payment->customer_email ?? '',
@@ -1221,9 +1278,12 @@
1221 1278 'trip_duration' => yatra_format_duration(
1222 1279 (int) ($payment->trip_duration_days ?? ($trip->duration_days ?? 0)),
1223 1280 isset($payment->trip_duration_nights)
1224 1281 ? (int) $payment->trip_duration_nights
1225 - : (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)
1226 1286 ),
1227 1287 'trip_difficulty' => $trip ? ($trip->difficulty_name ?? '') : '',
1228 1288 'departure_location' => $trip ? ($trip->departure_location ?? '') : '',
1229 1289 'destination' => $trip ? ($trip->destination ?? $payment->destination ?? '') : ($payment->destination ?? ''),