import { env } from "$env/dynamic/private"; import { convertAndFormatCurrency } from "$lib/domains/currency/view/currency.vm.svelte"; import { db } from "@pkg/db"; import { getError, Logger } from "@pkg/logger"; import { DiscountType, type CouponModel } from "@pkg/logic/domains/coupon/data"; import { ERROR_CODES, type Result } from "@pkg/result"; import type { FlightPriceDetails, FlightTicket, TicketSearchPayload, } from "../data/entities"; import { TicketRepository } from "../data/repository"; import { ScrapedTicketsDataSource } from "../data/scrape.data.source"; export class TicketController { private repo: TicketRepository; private TICKET_SCRAPERS = ["kiwi"]; constructor(repo: TicketRepository) { this.repo = repo; } async searchForTickets( payload: TicketSearchPayload, coupon: CouponModel | undefined, ): Promise> { const result = await this.repo.searchForTickets({ sessionId: payload.sessionId, ticketSearchPayload: payload, providers: this.TICKET_SCRAPERS, }); if (result.error || !result.data) { return result; } if (!coupon) { return result; } Logger.info(`Auto-applying coupon ${coupon.code} to all search results`); return { data: result.data.map((ticket) => { return this.applyDiscountToTicket(ticket, coupon); }), }; } private applyDiscountToTicket( ticket: FlightTicket, coupon: CouponModel, ): FlightTicket { // Calculate discount amount let discountAmount = 0; if (coupon.discountType === DiscountType.PERCENTAGE) { discountAmount = (ticket.priceDetails.displayPrice * Number(coupon.discountValue)) / 100; } else { discountAmount = Number(coupon.discountValue); } // Apply maximum discount limit if specified if ( coupon.maxDiscountAmount && discountAmount > Number(coupon.maxDiscountAmount) ) { discountAmount = Number(coupon.maxDiscountAmount); } // Skip if minimum order value is not met if ( coupon.minOrderValue && ticket.priceDetails.displayPrice < Number(coupon.minOrderValue) ) { return ticket; } // Create a copy of the ticket with the discount applied const discountedTicket = { ...ticket }; discountedTicket.priceDetails = { ...ticket.priceDetails, discountAmount: (ticket.priceDetails.discountAmount || 0) + discountAmount, displayPrice: ticket.priceDetails.displayPrice - discountAmount, appliedCoupon: coupon.code, couponDescription: coupon.description || `Coupon discount of ${coupon.discountType === DiscountType.PERCENTAGE ? coupon.discountValue + "%" : convertAndFormatCurrency(parseFloat(coupon.discountValue.toString()))}`, }; return discountedTicket; } async cacheTicket(sid: string, payload: FlightTicket) { Logger.info( `Caching ticket for ${sid} | ${payload.departure}:${payload.arrival}`, ); const refOIds = payload.refOIds; if (!refOIds) { // In this case we're not going for any of our fancy checkout jazz return this.repo.createTicket(payload, true); } const areAnyLive = await this.repo.areCheckoutAlreadyLiveForOrder(refOIds); Logger.info(`Any of the OIds has checkout session live ?? ${areAnyLive}`); if (areAnyLive) { return { error: getError({ code: ERROR_CODES.INTERNAL_SERVER_ERROR, message: "This ticket offer has expired", userHint: "Please select another one or perform search again to fetch latest offers", actionable: false, detail: "Failed to ticket", }), }; } Logger.info("nu'uh seems greenlight to make a ticket"); return this.repo.createTicket(payload, true); } async updateTicketPrices(tid: number, payload: FlightPriceDetails) { return this.repo.updateTicketPrices(tid, payload); } async uncacheAndSaveTicket(tid: number) { return this.repo.uncacheAndSaveTicket(tid); } async getCheckoutUrlByRefOId(id: number) { return this.repo.getCheckoutUrlByRefOId(id); } async setRefOIdsForTicket(tid: number, oids: number[]) { return this.repo.setRefOIdsForTicket(tid, oids); } async getTicketById(id: number) { return this.repo.getTicketById(id); } } export function getTC() { const ds = new ScrapedTicketsDataSource( env.TICKET_SCRAPER_API_URL, env.API_KEY, ); return new TicketController(new TicketRepository(db, ds)); }