mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Merge pull request #744 from Tria-plc/alpha
Fix payment fallback redirect
This commit is contained in:
@@ -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'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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user