239 lines
6.8 KiB
TypeScript
239 lines
6.8 KiB
TypeScript
import { API_BASE_URL } from './client';
|
|
|
|
type ApiEnvelope<T> = { success?: boolean; data?: T; error?: { message?: string } };
|
|
|
|
async function parse<T>(res: Response): Promise<{ data?: T; error?: string }> {
|
|
let body: unknown;
|
|
try {
|
|
body = await res.json();
|
|
} catch {
|
|
return { error: res.statusText || 'Network error' };
|
|
}
|
|
const api = body as ApiEnvelope<T>;
|
|
if (api.success === true && api.data !== undefined) return { data: api.data };
|
|
return { error: api.error?.message || 'Request failed' };
|
|
}
|
|
|
|
function url(path: string) {
|
|
const base = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL;
|
|
const p = path.startsWith('/') ? path : `/${path}`;
|
|
return `${base}${p}`;
|
|
}
|
|
|
|
export type PublicTicketCatalogItem = {
|
|
_id: string;
|
|
name: string;
|
|
description: string;
|
|
keyPoints: string[];
|
|
basePriceMinor: number;
|
|
currency: string;
|
|
maxPerOrder: number | null;
|
|
requiresBillingState: boolean;
|
|
registrationFormId: string | null;
|
|
registrationFormFields: PublicFormField[] | null;
|
|
};
|
|
|
|
export type PublicFormField = {
|
|
label?: string;
|
|
type?: string;
|
|
required?: boolean;
|
|
options?: string[];
|
|
allowOther?: boolean;
|
|
otherLabel?: string;
|
|
placeholder?: string;
|
|
mapsTo?: string;
|
|
};
|
|
|
|
export type TicketQuote = {
|
|
ticketId: string;
|
|
quantity: number;
|
|
billingStateCode: string | null;
|
|
baseMinor: number;
|
|
taxMinor: number;
|
|
totalMinor: number;
|
|
/** Present on manual UPI quotes — Eventbuzz platform fee (paise). */
|
|
platformFeeMinor?: number;
|
|
/** Manual quote only — for UPI `pn` field. */
|
|
ticketName?: string;
|
|
eventName?: string;
|
|
taxLines: { label: string; rateBps: number; amountMinor: number }[];
|
|
currency: string;
|
|
};
|
|
|
|
export type PublicCheckoutOptions = {
|
|
onlineAvailable: boolean;
|
|
onlineGateways?: Array<{
|
|
id: string;
|
|
provider: string;
|
|
label: string;
|
|
isActive: boolean;
|
|
isPrimary: boolean;
|
|
testMode: boolean;
|
|
ticketCheckoutSupported: boolean;
|
|
}>;
|
|
manualUpi: {
|
|
manualQrMode: 'upload' | 'dynamic';
|
|
eventName: string;
|
|
upiQrImageUrl: string;
|
|
upiId: string;
|
|
payeeName: string;
|
|
instructions: string;
|
|
platformFeeInr: number;
|
|
} | null;
|
|
};
|
|
|
|
export type CheckoutOrderResponse = {
|
|
keyId: string;
|
|
orderId: string;
|
|
amount: number;
|
|
currency: string;
|
|
transactionId: string;
|
|
checkoutSource: string;
|
|
};
|
|
|
|
export const publicEventTicketsApi = {
|
|
async getEventSummary(eventId: string): Promise<{
|
|
name: string;
|
|
subtitle?: string;
|
|
startDate?: string;
|
|
endDate?: string;
|
|
location?: string;
|
|
locationType?: string;
|
|
image?: string;
|
|
}> {
|
|
const res = await fetch(url(`/public/events/${eventId}/summary`), { method: 'GET' });
|
|
const out = await parse<{
|
|
name: string;
|
|
subtitle?: string;
|
|
startDate?: string;
|
|
endDate?: string;
|
|
location?: string;
|
|
locationType?: string;
|
|
image?: string;
|
|
}>(res);
|
|
if (out.error) throw new Error(out.error);
|
|
return out.data!;
|
|
},
|
|
|
|
async listTickets(eventId: string): Promise<PublicTicketCatalogItem[]> {
|
|
const res = await fetch(url(`/public/events/${eventId}/tickets`), { method: 'GET' });
|
|
const out = await parse<{ tickets: PublicTicketCatalogItem[] }>(res);
|
|
if (out.error) throw new Error(out.error);
|
|
return out.data?.tickets ?? [];
|
|
},
|
|
|
|
async checkoutOptions(eventId: string): Promise<PublicCheckoutOptions> {
|
|
const res = await fetch(url(`/public/events/${eventId}/tickets/checkout-options`), { method: 'GET' });
|
|
const out = await parse<PublicCheckoutOptions>(res);
|
|
if (out.error) throw new Error(out.error);
|
|
return out.data!;
|
|
},
|
|
|
|
async quote(
|
|
eventId: string,
|
|
payload: { ticketId: string; quantity: number; billingStateCode?: string },
|
|
): Promise<TicketQuote> {
|
|
const res = await fetch(url(`/public/events/${eventId}/tickets/quote`), {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
const out = await parse<TicketQuote>(res);
|
|
if (out.error) throw new Error(out.error);
|
|
return out.data!;
|
|
},
|
|
|
|
async manualQuote(
|
|
eventId: string,
|
|
payload: { ticketId: string; quantity: number; billingStateCode?: string },
|
|
): Promise<TicketQuote> {
|
|
const res = await fetch(url(`/public/events/${eventId}/tickets/manual-quote`), {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
const out = await parse<TicketQuote>(res);
|
|
if (out.error) throw new Error(out.error);
|
|
return out.data!;
|
|
},
|
|
|
|
async checkout(
|
|
eventId: string,
|
|
payload: {
|
|
ticketId: string;
|
|
quantity: number;
|
|
billingStateCode?: string;
|
|
purchaserEmail: string;
|
|
sourceFormId?: string;
|
|
attendees: Array<{
|
|
firstName: string;
|
|
lastName: string;
|
|
email: string;
|
|
phone?: string;
|
|
organization?: string;
|
|
designation?: string;
|
|
displayName?: string;
|
|
formData?: Record<string, unknown>;
|
|
}>;
|
|
expectedTotalMinor: number;
|
|
idempotencyKey?: string;
|
|
orgGatewayId?: string;
|
|
},
|
|
): Promise<CheckoutOrderResponse> {
|
|
const res = await fetch(url(`/public/events/${eventId}/tickets/checkout`), {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
const out = await parse<CheckoutOrderResponse>(res);
|
|
if (out.error) throw new Error(out.error);
|
|
return out.data!;
|
|
},
|
|
|
|
async verifyPayment(
|
|
eventId: string,
|
|
payload: { orderId: string; paymentId: string; signature: string },
|
|
): Promise<void> {
|
|
const res = await fetch(url(`/public/events/${eventId}/payment/verify`), {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
const out = await parse<{ verified: boolean }>(res);
|
|
if (out.error) throw new Error(out.error);
|
|
},
|
|
|
|
async manualSubmit(
|
|
eventId: string,
|
|
payload: {
|
|
ticketId: string;
|
|
quantity: number;
|
|
billingStateCode?: string;
|
|
purchaserEmail: string;
|
|
sourceFormId?: string;
|
|
attendees: Array<{
|
|
firstName: string;
|
|
lastName: string;
|
|
email: string;
|
|
phone?: string;
|
|
organization?: string;
|
|
designation?: string;
|
|
displayName?: string;
|
|
formData?: Record<string, unknown>;
|
|
}>;
|
|
expectedTotalMinor: number;
|
|
screenshotDataUrl: string;
|
|
buyerNote?: string;
|
|
},
|
|
): Promise<{ orderId: string; message: string }> {
|
|
const res = await fetch(url(`/public/events/${eventId}/tickets/manual-submit`), {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
const out = await parse<{ orderId: string; message: string }>(res);
|
|
if (out.error) throw new Error(out.error);
|
|
return out.data!;
|
|
},
|
|
};
|