Files
dticket.tootaio.com/server/utils/whatsapp.ts
xiaomai c214d643dd feat: send ticket receipts via WhatsApp and normalize phone numbers
Add WhatsApp API integration for automated receipt delivery
Enforce country codes for all phone number inputs (defaults to +60)
2026-04-27 13:12:25 +08:00

112 lines
3.1 KiB
TypeScript

import type { H3Event } from 'h3'
import type { PublicBooking, WhatsAppDeliveryResult } from '~~/shared/booking'
import {
DINNER_EVENT_DATE_LABEL,
DINNER_EVENT_TIME_LABEL,
DINNER_EVENT_TITLE,
DINNER_EVENT_VENUE,
formatBookingCurrency,
getTicketCatalogItem
} from '~~/shared/booking'
import { normalizePhoneNumber } from '~~/shared/auth'
import { buildAppUrl } from './app-url'
type WhatsAppMessagesResponse = {
messages?: Array<{
id?: string
}>
}
export function toWhatsAppPhoneNumber(phoneNumber: string) {
return normalizePhoneNumber(phoneNumber).replace(/\D/g, '')
}
export function buildWhatsAppDeepLink(phoneNumber: string, message: string) {
return `https://wa.me/${toWhatsAppPhoneNumber(phoneNumber)}?text=${encodeURIComponent(message)}`
}
export function buildBookingTicketReceiptMessage(event: H3Event, booking: PublicBooking) {
const ticket = getTicketCatalogItem(booking.ticketType)
const ticketLabel = ticket?.label || booking.ticketType.toUpperCase()
const receiptUrl = buildAppUrl(event, `/receipt/${booking.receiptToken}`)
return [
DINNER_EVENT_TITLE,
'',
`Hi ${booking.customerName}, your ticket receipt has been confirmed.`,
'',
`Receipt: ${receiptUrl}`,
`Seats: ${booking.seatCount}`,
`Ticket Category: ${ticketLabel}`,
`Total Price: ${formatBookingCurrency(booking.totalPrice)}`,
`Date: ${DINNER_EVENT_DATE_LABEL}`,
`Time: ${DINNER_EVENT_TIME_LABEL}`,
`Venue: ${DINNER_EVENT_VENUE}`,
'',
'Please present the QR code from the receipt at the event.'
].join('\n')
}
export async function sendBookingTicketReceiptViaWhatsApp(
event: H3Event,
booking: PublicBooking
): Promise<WhatsAppDeliveryResult> {
const recipientPhone = normalizePhoneNumber(booking.customerPhone)
const to = toWhatsAppPhoneNumber(recipientPhone)
const config = useRuntimeConfig()
const accessToken = String(config.whatsappAccessToken || '')
const phoneNumberId = String(config.whatsappPhoneNumberId || '')
const apiVersion = String(config.whatsappApiVersion || 'v23.0')
if (!accessToken || !phoneNumberId) {
return {
sent: false,
skipped: true,
recipientPhone,
apiRecipientPhone: to,
error: 'WhatsApp API credentials are not configured.'
}
}
try {
const response = await $fetch<WhatsAppMessagesResponse>(
`https://graph.facebook.com/${apiVersion}/${phoneNumberId}/messages`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`
},
body: {
messaging_product: 'whatsapp',
recipient_type: 'individual',
to,
type: 'text',
text: {
preview_url: true,
body: buildBookingTicketReceiptMessage(event, booking)
}
}
}
)
return {
sent: true,
skipped: false,
recipientPhone,
apiRecipientPhone: to,
messageId: response.messages?.[0]?.id
}
} catch (error: any) {
return {
sent: false,
skipped: false,
recipientPhone,
apiRecipientPhone: to,
error: error?.data?.error?.message || error?.message || 'WhatsApp API request failed.'
}
}
}