feat(bookings): implement ticket receipts and seat sharing system

Add receipt tokens and booking_seats table to track individual tickets
Create receipt and seat view pages with QR code generation
This commit is contained in:
2026-04-12 22:48:26 +08:00
parent 7f582b530c
commit 6194c96ead
15 changed files with 1663 additions and 61 deletions

View File

@@ -0,0 +1,23 @@
import type { PublicBookingReceipt } from '~~/shared/booking'
import { getBookingReceiptByReceiptToken } from '../../../utils/booking-repository'
import { buildAppUrl } from '../../../utils/app-url'
import { getRequiredRouteParam, httpError } from '../../../utils/http'
export default defineEventHandler(async (event): Promise<PublicBookingReceipt> => {
const token = getRequiredRouteParam(event, 'token', 'Receipt token')
const receipt = await getBookingReceiptByReceiptToken(token)
if (!receipt) {
httpError(404, 'Receipt not found')
}
return {
booking: receipt.booking,
receiptUrl: buildAppUrl(event, `/receipt/${receipt.booking.receiptToken}`),
seats: receipt.seats.map((seat) => ({
...seat,
seatUrl: buildAppUrl(event, `/seat/${seat.seatToken}`)
}))
}
})

View File

@@ -0,0 +1,34 @@
import type { PublicBookingSeatWithUrl } from '~~/shared/booking'
import { updateBookingSeatShareByReceiptToken } from '../../../../../utils/booking-repository'
import { parseSeatShareInput } from '../../../../../utils/bookings'
import { buildAppUrl } from '../../../../../utils/app-url'
import { getRequiredRouteParam, httpError } from '../../../../../utils/http'
export default defineEventHandler(async (event): Promise<{ seat: PublicBookingSeatWithUrl }> => {
const token = getRequiredRouteParam(event, 'token', 'Receipt token')
const seatId = getRequiredRouteParam(event, 'seatId', 'Seat')
const body = await readBody<{
shared?: boolean
recipientName?: string | null
recipientPhone?: string | null
}>(event)
const input = parseSeatShareInput(body)
const seat = await updateBookingSeatShareByReceiptToken({
receiptToken: token,
seatId,
...input
})
if (!seat) {
httpError(404, 'Seat not found')
}
return {
seat: {
...seat,
seatUrl: buildAppUrl(event, `/seat/${seat.seatToken}`)
}
}
})

View File

@@ -0,0 +1,23 @@
import type { PublicSeatReceipt } from '~~/shared/booking'
import { getSeatReceiptBySeatToken } from '../../../utils/booking-repository'
import { buildAppUrl } from '../../../utils/app-url'
import { getRequiredRouteParam, httpError } from '../../../utils/http'
export default defineEventHandler(async (event): Promise<PublicSeatReceipt> => {
const token = getRequiredRouteParam(event, 'token', 'Seat token')
const receipt = await getSeatReceiptBySeatToken(token)
if (!receipt) {
httpError(404, 'Seat ticket not found')
}
return {
booking: receipt.booking,
receiptUrl: buildAppUrl(event, `/receipt/${receipt.booking.receiptToken}`),
seat: {
...receipt.seat,
seatUrl: buildAppUrl(event, `/seat/${receipt.seat.seatToken}`)
}
}
})