Files
dticket.tootaio.com/server/api/public/bookings/[token]/transaction-document.post.ts
xiaomai a56a6706b0 feat(bookings): add payment and document upload to confirmation page
Allow users to select payment method and upload receipts before confirming.
Add public API endpoints for payment updates and document management.
2026-05-09 13:15:45 +08:00

84 lines
2.5 KiB
TypeScript

import type { UpdateBookingDetailsResponse } from '~~/shared/booking'
import { getHeader, readMultipartFormData } from 'h3'
import {
getBookingByConfirmationToken,
replaceBookingTransactionDocumentByConfirmationToken
} from '../../../../utils/booking-repository'
import { getRequiredRouteParam, httpError } from '../../../../utils/http'
import {
deleteTransactionDocument,
saveTransactionDocument,
TRANSACTION_DOCUMENT_MAX_SIZE,
validateTransactionDocumentUpload
} from '../../../../utils/transaction-documents'
export default defineEventHandler(async (event): Promise<UpdateBookingDetailsResponse> => {
const token = getRequiredRouteParam(event, 'token', 'Confirmation token')
const booking = await getBookingByConfirmationToken(token, { includeTransactionDocument: true })
if (!booking) {
httpError(404, 'Booking not found')
}
if (booking.status !== 'pending') {
httpError(409, 'Transaction document can only be changed before confirmation')
}
if (booking.paymentMethod !== 'bank') {
httpError(400, 'Transaction document can only be uploaded for Bank payments')
}
const contentType = String(getHeader(event, 'content-type') || '').toLowerCase()
if (!contentType.startsWith('multipart/form-data;')) {
httpError(400, 'Transaction document upload must use multipart form data')
}
const contentLength = Number(getHeader(event, 'content-length') || 0)
if (contentLength > TRANSACTION_DOCUMENT_MAX_SIZE + 1024 * 1024) {
httpError(413, 'Transaction document must be 10MB or smaller')
}
const formData = await readMultipartFormData(event)
const filePart = formData?.find((part) => part.name === 'document' && part.filename)
if (!filePart) {
httpError(400, 'Transaction document is required')
}
const upload = validateTransactionDocumentUpload({
data: filePart.data,
filename: filePart.filename,
contentType: filePart.type
})
const storageName = await saveTransactionDocument(filePart.data, upload.fileType)
try {
const result = await replaceBookingTransactionDocumentByConfirmationToken({
confirmationToken: token,
originalName: upload.originalName,
storageName,
mimeType: upload.fileType.mimeType,
size: filePart.data.length
})
if (!result) {
await deleteTransactionDocument(storageName)
httpError(404, 'Booking not found')
}
await deleteTransactionDocument(result.previousStorageName)
return {
booking: result.booking
}
} catch (error) {
await deleteTransactionDocument(storageName)
throw error
}
})