diff --git a/apps/edr-passenger-web/portal/src/app/booking/detail/page.tsx b/apps/edr-passenger-web/portal/src/app/booking/detail/page.tsx index c6f823053..3ac0efd56 100644 --- a/apps/edr-passenger-web/portal/src/app/booking/detail/page.tsx +++ b/apps/edr-passenger-web/portal/src/app/booking/detail/page.tsx @@ -117,6 +117,19 @@ function BookingDetailContent() { retry: 1, }); + // When the booking is CONFIRMED but has no tickets (generate failed silently at + // payment time), call generate now so tickets are ready before the user clicks Download. + useEffect(() => { + if ( + booking?.status === 'CONFIRMED' && + booking?.id && + Array.isArray(booking?.tickets) && + booking.tickets.length === 0 + ) { + apiClient.post(`/tickets/generate/${booking.id}`, {}).then(() => refetch()).catch(() => {}); + } + }, [booking?.id, booking?.status, booking?.tickets?.length]); + const { data: paymentMethods } = useQuery({ queryKey: ["payment-methods"], queryFn: () => apiClient.get("/payments/methods"), @@ -246,19 +259,9 @@ function BookingDetailContent() { alert("Booking data not available. Please try again."); return; } - setIsGeneratingVoucher(true); try { - // If tickets are missing (generate failed silently at payment time), issue them now. - if (!booking.tickets?.length && booking.id) { - try { - await apiClient.post(`/tickets/generate/${booking.id}`, {}); - } catch { - // ignore — generate() will throw if payment not succeeded; voucher will show - // "Not yet issued" in that case, which is correct - } - } - // Always refetch so the voucher has the latest ticket barcodes. + // Always fetch fresh booking data so tickets are included. const fresh = await apiClient.get(`/bookings/${booking.bookingRef}`); const bookingData = (fresh as any)?.data || fresh; const { generateVoucherPDF } = await import("@/lib/generate-voucher");