diff --git a/apps/edr-passenger-api/src/modules/payments/payments.service.ts b/apps/edr-passenger-api/src/modules/payments/payments.service.ts index ac8c7ef66..252ef4cab 100644 --- a/apps/edr-passenger-api/src/modules/payments/payments.service.ts +++ b/apps/edr-passenger-api/src/modules/payments/payments.service.ts @@ -826,6 +826,19 @@ export class PaymentsService { }); if (!intent) throw new NotFoundException("PaymentIntent not found"); if (intent.status === PaymentIntentStatus.SUCCEEDED) { + // Idempotency guard — but still repair missing tickets. They can be absent + // when the first finalization threw from generate() after the transaction + // committed: the caller got a 500, retried, and now hits this early-return. + const ticketCount = await this.prisma.ticket.count({ where: { bookingId: intent.bookingId } }); + if (ticketCount === 0) { + try { + await this.ticketsService.generate(intent.bookingId); + } catch (err) { + this.logger.error( + `Error generating ticket on idempotency retry for booking ${intent.bookingId}: ${err instanceof Error ? err.message : String(err)}`, + ); + } + } return { alreadyFinalized: true }; } if (intent.status === PaymentIntentStatus.CANCELLED) { @@ -877,9 +890,8 @@ export class PaymentsService { await this.ticketsService.generate(booking.id); } catch (err) { this.logger.error( - `Error generating ticket: ${err instanceof Error ? err.message : String(err)}`, + `Error generating ticket for booking ${booking.id}: ${err instanceof Error ? err.message : String(err)}`, ); - throw err; } try { diff --git a/apps/edr-passenger-web/portal/src/lib/generate-voucher.ts b/apps/edr-passenger-web/portal/src/lib/generate-voucher.ts index 439c26c29..abbf6eedf 100644 --- a/apps/edr-passenger-web/portal/src/lib/generate-voucher.ts +++ b/apps/edr-passenger-web/portal/src/lib/generate-voucher.ts @@ -260,7 +260,7 @@ function drawJourneyLeg(doc: jsPDF, schedule: ScheduleInfo, legLabel: string | n doc.setTextColor(...INK); doc.setFontSize(16); doc.setFont('helvetica', 'bold'); doc.text(schedule.origin.code, margin + padX, topY + 7); doc.setTextColor(...BODY); doc.setFontSize(8.5); doc.setFont('helvetica', 'normal'); - doc.text(schedule.origin.city || schedule.origin.name, margin + padX, topY + 11.5); + doc.text(schedule.origin.name, margin + padX, topY + 11.5); const dep = new Date(schedule.departureAt); doc.setTextColor(...BRAND); doc.setFontSize(11.5); doc.setFont('helvetica', 'bold'); @@ -274,7 +274,7 @@ function drawJourneyLeg(doc: jsPDF, schedule: ScheduleInfo, legLabel: string | n doc.setTextColor(...INK); doc.setFontSize(16); doc.setFont('helvetica', 'bold'); doc.text(schedule.destination.code, dx, topY + 7, { align: 'right' }); doc.setTextColor(...BODY); doc.setFontSize(8.5); doc.setFont('helvetica', 'normal'); - doc.text(schedule.destination.city || schedule.destination.name, dx, topY + 11.5, { align: 'right' }); + doc.text(schedule.destination.name, dx, topY + 11.5, { align: 'right' }); const arr = new Date(schedule.arrivalAt); doc.setTextColor(...BRAND); doc.setFontSize(11.5); doc.setFont('helvetica', 'bold');