Store LNbits invoice data on payments, add an invoice endpoint that reuses valid invoices or regenerates expired ones, and wire the booking payment page to fetch and display invoices via a shared watcher hook. Co-authored-by: Cursor <cursoragent@cursor.com>
152 lines
4.8 KiB
TypeScript
152 lines
4.8 KiB
TypeScript
import { fetchApi, API_BASE } from './client';
|
|
import type {
|
|
Ticket,
|
|
Payment,
|
|
BookingData,
|
|
TicketValidationResult,
|
|
TicketSearchResult,
|
|
LiveSearchResult,
|
|
LightningInvoice,
|
|
} from './types';
|
|
|
|
export const ticketsApi = {
|
|
book: (data: BookingData) =>
|
|
fetchApi<{ ticket: Ticket; payment: Payment; message: string }>('/api/tickets', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
}),
|
|
|
|
getById: (id: string) => fetchApi<{ ticket: Ticket }>(`/api/tickets/${id}`),
|
|
|
|
getAll: (params?: { eventId?: string; status?: string }) => {
|
|
const query = new URLSearchParams();
|
|
if (params?.eventId) query.set('eventId', params.eventId);
|
|
if (params?.status) query.set('status', params.status);
|
|
return fetchApi<{ tickets: Ticket[] }>(`/api/tickets?${query}`);
|
|
},
|
|
|
|
// Validate ticket by QR code (for scanner)
|
|
validate: (code: string, eventId?: string) =>
|
|
fetchApi<TicketValidationResult>('/api/tickets/validate', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ code, eventId }),
|
|
}),
|
|
|
|
// Search tickets by name/email (for scanner manual search)
|
|
search: (query: string, eventId?: string) =>
|
|
fetchApi<{ tickets: TicketSearchResult[] }>('/api/tickets/search', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ query, eventId }),
|
|
}),
|
|
|
|
// Get event check-in stats (for scanner header counter)
|
|
getCheckinStats: (eventId: string) =>
|
|
fetchApi<{ eventId: string; capacity: number; checkedIn: number; totalActive: number }>(
|
|
`/api/tickets/stats/checkin?eventId=${eventId}`
|
|
),
|
|
|
|
// Live search tickets (GET - for scanner live search with debounce)
|
|
searchLive: (q: string, eventId?: string) => {
|
|
const params = new URLSearchParams();
|
|
params.set('q', q);
|
|
if (eventId) params.set('eventId', eventId);
|
|
return fetchApi<{ tickets: LiveSearchResult[] }>(`/api/tickets/search?${params}`);
|
|
},
|
|
|
|
checkin: (id: string) =>
|
|
fetchApi<{ ticket: Ticket & { attendeeName?: string }; event?: { id: string; title: string }; message: string }>(`/api/tickets/${id}/checkin`, {
|
|
method: 'POST',
|
|
}),
|
|
|
|
removeCheckin: (id: string) =>
|
|
fetchApi<{ ticket: Ticket; message: string }>(`/api/tickets/${id}/remove-checkin`, {
|
|
method: 'POST',
|
|
}),
|
|
|
|
cancel: (id: string) =>
|
|
fetchApi<{ message: string }>(`/api/tickets/${id}/cancel`, { method: 'POST' }),
|
|
|
|
updateStatus: (id: string, status: string) =>
|
|
fetchApi<{ ticket: Ticket }>(`/api/tickets/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify({ status }),
|
|
}),
|
|
|
|
updateNote: (id: string, note: string) =>
|
|
fetchApi<{ ticket: Ticket; message: string }>(`/api/tickets/${id}/note`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ note }),
|
|
}),
|
|
|
|
markPaid: (id: string) =>
|
|
fetchApi<{ ticket: Ticket; message: string }>(`/api/tickets/${id}/mark-paid`, {
|
|
method: 'POST',
|
|
}),
|
|
|
|
// For manual payment methods (bank_transfer, tpago) - user marks payment as sent
|
|
markPaymentSent: (id: string, payerName?: string) =>
|
|
fetchApi<{ payment: Payment; message: string }>(`/api/tickets/${id}/mark-payment-sent`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ payerName }),
|
|
}),
|
|
|
|
adminCreate: (data: {
|
|
eventId: string;
|
|
firstName: string;
|
|
lastName?: string;
|
|
email?: string;
|
|
phone?: string;
|
|
preferredLanguage?: 'en' | 'es';
|
|
autoCheckin?: boolean;
|
|
adminNote?: string;
|
|
}) =>
|
|
fetchApi<{ ticket: Ticket; payment: Payment; message: string }>('/api/tickets/admin/create', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
}),
|
|
|
|
manualCreate: (data: {
|
|
eventId: string;
|
|
firstName: string;
|
|
lastName?: string;
|
|
email: string;
|
|
phone?: string;
|
|
preferredLanguage?: 'en' | 'es';
|
|
adminNote?: string;
|
|
}) =>
|
|
fetchApi<{ ticket: Ticket; payment: Payment; message: string }>('/api/tickets/admin/manual', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
}),
|
|
|
|
guestCreate: (data: {
|
|
eventId: string;
|
|
firstName: string;
|
|
lastName?: string;
|
|
email?: string;
|
|
phone?: string;
|
|
preferredLanguage?: 'en' | 'es';
|
|
adminNote?: string;
|
|
}) =>
|
|
fetchApi<{ ticket: Ticket; payment: Payment; message: string }>('/api/tickets/admin/guest', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
}),
|
|
|
|
checkPaymentStatus: (ticketId: string) =>
|
|
fetchApi<{ ticketStatus: string; paymentStatus: string; lnbitsStatus?: string; isPaid: boolean }>(
|
|
`/api/lnbits/status/${ticketId}`
|
|
),
|
|
|
|
// Get a Lightning invoice to pay/re-pay a ticket - reuses the stored invoice
|
|
// if it's still valid, otherwise generates a fresh one.
|
|
getLightningInvoice: (ticketId: string) =>
|
|
fetchApi<{ invoice?: LightningInvoice; reused?: boolean; alreadyPaid?: boolean }>(
|
|
`/api/lnbits/invoice/${ticketId}`,
|
|
{ method: 'POST' }
|
|
),
|
|
|
|
// Get PDF download URL (returns the URL, not the PDF itself)
|
|
getPdfUrl: (id: string) => `${API_BASE}/api/tickets/${id}/pdf`,
|
|
};
|