Merge pull request #744 from Tria-plc/alpha

Fix payment fallback redirect
This commit is contained in:
robiman
2026-07-16 17:28:42 +03:00
committed by GitHub
4 changed files with 68 additions and 33 deletions

View File

@@ -93,23 +93,16 @@ export default function ConfirmationPage() {
data: _booking,
refetch: refetchBooking,
isLoading: isBookingLoading,
isError: isBookingError,
} = useQuery<BookingWithTicket>({
queryKey: ["booking", bookingId],
queryFn: async (): Promise<BookingWithTicket> => {
try {
return await apiClient.get(`/bookings/${bookingId}`);
} catch (error) {
return {
id: bookingId || "",
pnr: pnr || undefined,
status: "PENDING_PAYMENT",
totalMinor: passengers.reduce(
(sum) => sum + (selectedSchedule?.baseFareAdult || 0),
0,
),
};
}
},
// Let a real fetch failure surface as a real error (React Query's global retry:1
// default then retries once automatically) instead of silently returning a
// fabricated "PENDING_PAYMENT" object — that used to mask genuine failures (a
// transient blip right after a cross-domain redirect from the payment gateway is
// common) as normal pending state forever, since a caught error that returns data
// looks like a success to React Query and never gets retried.
queryFn: (): Promise<BookingWithTicket> => apiClient.get(`/bookings/${bookingId}`),
// Payment status must never be served from a stale cache — the app-wide default
// (providers.tsx) is a 60s staleTime, which would otherwise block React Query's
// own refetch-on-window-focus from firing (it only refetches stale data). Without
@@ -333,6 +326,30 @@ export default function ConfirmationPage() {
);
}
// A real fetch failure (not just "still pending") — surface it honestly instead of
// silently pretending the booking is pending, and let the user retry the check
// without needing a full page refresh.
if (isBookingError) {
return (
<div className="booking-page">
<div className="container mx-auto px-4">
<div className="max-w-6xl mx-auto flex flex-col items-center justify-center py-24 text-center">
<p className="text-gray-600 dark:text-gray-400 mb-4">
We&apos;re having trouble loading your booking status right
now. This is usually temporary tap below to try again.
</p>
<button
onClick={() => refetchBooking()}
className="btn-primary"
>
Check again
</button>
</div>
</div>
</div>
);
}
return (
<div className="booking-page">
<div className="container mx-auto px-4">

View File

@@ -55,13 +55,20 @@ function DmoneySuccessContent() {
// Manage Booking sessions don't carry a bookingId in the client store — the detail page
// it lands on re-fetches the booking's real status itself, so there's nothing to verify
// client-side here; just hand off without claiming an outcome we can't confirm.
if (!bookingId) {
if (!cancelled) {
router.push(target);
}
if (manageBookingRef) {
router.push(target);
return;
}
// Normal booking flow: bookingId comes from a Zustand store persisted to localStorage.
// This page is always reached via a real cross-domain redirect from the payment gateway
// (a full page load, not an in-app navigation), so that store has to rehydrate from
// localStorage asynchronously — bookingId can read as empty on the first render or two.
// Wait for it instead of treating an empty first-render value as "nothing to verify",
// which would silently skip this page's whole verification step and hand off to
// /booking/confirmation without ever having checked payment status here.
if (!bookingId) return;
verifyBookingPaid(bookingId).then((result) => {
if (cancelled) return;
if (result === 'SUCCEEDED') {
@@ -82,8 +89,7 @@ function DmoneySuccessContent() {
return () => {
cancelled = true;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [bookingId, router, updateStatus]);
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center px-4">

View File

@@ -55,13 +55,20 @@ function TelebirrSuccessContent() {
// Manage Booking sessions don't carry a bookingId in the client store — the detail page
// it lands on re-fetches the booking's real status itself, so there's nothing to verify
// client-side here; just hand off without claiming an outcome we can't confirm.
if (!bookingId) {
if (!cancelled) {
router.push(target);
}
if (manageBookingRef) {
router.push(target);
return;
}
// Normal booking flow: bookingId comes from a Zustand store persisted to localStorage.
// This page is always reached via a real cross-domain redirect from the payment gateway
// (a full page load, not an in-app navigation), so that store has to rehydrate from
// localStorage asynchronously — bookingId can read as empty on the first render or two.
// Wait for it instead of treating an empty first-render value as "nothing to verify",
// which would silently skip this page's whole verification step and hand off to
// /booking/confirmation without ever having checked payment status here.
if (!bookingId) return;
verifyBookingPaid(bookingId).then((result) => {
if (cancelled) return;
if (result === 'SUCCEEDED') {
@@ -82,8 +89,7 @@ function TelebirrSuccessContent() {
return () => {
cancelled = true;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [bookingId, router, updateStatus]);
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center px-4">

View File

@@ -55,13 +55,20 @@ function WaafiSuccessContent() {
// Manage Booking sessions don't carry a bookingId in the client store — the detail page
// it lands on re-fetches the booking's real status itself, so there's nothing to verify
// client-side here; just hand off without claiming an outcome we can't confirm.
if (!bookingId) {
if (!cancelled) {
router.push(target);
}
if (manageBookingRef) {
router.push(target);
return;
}
// Normal booking flow: bookingId comes from a Zustand store persisted to localStorage.
// This page is always reached via a real cross-domain redirect from the payment gateway
// (a full page load, not an in-app navigation), so that store has to rehydrate from
// localStorage asynchronously — bookingId can read as empty on the first render or two.
// Wait for it instead of treating an empty first-render value as "nothing to verify",
// which would silently skip this page's whole verification step and hand off to
// /booking/confirmation without ever having checked payment status here.
if (!bookingId) return;
verifyBookingPaid(bookingId).then((result) => {
if (cancelled) return;
if (result === 'SUCCEEDED') {
@@ -82,8 +89,7 @@ function WaafiSuccessContent() {
return () => {
cancelled = true;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [bookingId, router, updateStatus]);
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center px-4">