import { ckFlowVM } from "$lib/domains/ckflow/view/ckflow.vm.svelte"; import { newOrderModel } from "$lib/domains/order/data/entities"; import { passengerInfoVM } from "$lib/domains/passengerinfo/view/passenger.info.vm.svelte"; import { paymentInfoPayloadModel, PaymentMethod, } from "$lib/domains/paymentinfo/data/entities"; import { trpcApiStore } from "$lib/stores/api"; import { toast } from "svelte-sonner"; import { get } from "svelte/store"; import { CheckoutStep } from "../../data/entities/index"; import { flightTicketStore } from "../../data/store"; import { paymentInfoVM } from "./payment-info-section/payment.info.vm.svelte"; import { calculateTicketPrices } from "./total.calculator"; class TicketCheckoutViewModel { checkoutStep = $state(CheckoutStep.Initial); loading = $state(true); continutingToNextStep = $state(false); checkoutSubmitted = $state(false); livenessPinger: NodeJS.Timer | undefined = $state(undefined); reset() { this.checkoutStep = CheckoutStep.Initial; this.resetPinger(); } setupPinger() { this.resetPinger(); this.livenessPinger = setInterval(() => { this.ping(); }, 5_000); } resetPinger() { if (this.livenessPinger) { clearInterval(this.livenessPinger); } } private async ping() { const api = get(trpcApiStore); if (!api) { return false; } const ticket = get(flightTicketStore); if (!ticket || !ticket.refOIds) { return false; } const out = await api.ticket.ping.query({ tid: ticket.id, refOIds: ticket.refOIds, }); } async checkout() { if (this.checkoutSubmitted || this.loading) { return; } this.checkoutSubmitted = true; const api = get(trpcApiStore); if (!api) { this.checkoutSubmitted = false; return false; } const ticket = get(flightTicketStore); const prices = calculateTicketPrices( ticket, passengerInfoVM.passengerInfos, ); const validatedPrices = { subtotal: isNaN(prices.subtotal) ? 0 : prices.subtotal, discountAmount: isNaN(prices.discountAmount) ? 0 : prices.discountAmount, finalTotal: isNaN(prices.finalTotal) ? 0 : prices.finalTotal, pricePerPassenger: isNaN(prices.pricePerPassenger) ? 0 : prices.pricePerPassenger, }; const parsed = newOrderModel.safeParse({ basePrice: validatedPrices.subtotal, discountAmount: validatedPrices.discountAmount, displayPrice: validatedPrices.finalTotal, orderPrice: validatedPrices.finalTotal, // Same as displayPrice fullfilledPrice: validatedPrices.finalTotal, // Same as displayPrice pricePerPassenger: validatedPrices.pricePerPassenger, flightTicketInfoId: -1, paymentInfoId: -1, }); if (parsed.error) { console.log(parsed.error); const err = parsed.error.errors[0]; toast.error("Failed to perform checkout", { description: err.message, }); return false; } const pInfoParsed = paymentInfoPayloadModel.safeParse({ method: PaymentMethod.Card, cardDetails: paymentInfoVM.cardDetails, flightTicketInfoId: ticket.id, }); if (pInfoParsed.error) { console.log(parsed.error); const err = pInfoParsed.error.errors[0]; toast.error("Failed to perform checkout", { description: err.message, }); return false; } try { console.log("Creating order"); this.loading = true; const out = await api.order.createOrder.mutate({ flightTicketId: ticket.id, orderModel: parsed.data, passengerInfos: passengerInfoVM.passengerInfos, paymentInfo: pInfoParsed.data, refOIds: ticket.refOIds, flowId: ckFlowVM.flowId, }); if (out.error) { this.loading = false; toast.error(out.error.message, { description: out.error.userHint, }); return false; } if (!out.data) { this.loading = false; toast.error("Failed to create order", { description: "Please try again", }); return false; } toast.success("Order created successfully", { description: "Redirecting, please wait...", }); setTimeout(() => { window.location.href = `/checkout/success?oid=${out.data}`; }, 500); return true; } catch (e) { this.checkoutSubmitted = false; toast.error("An error occurred during checkout", { description: "Please try again", }); return false; } } } export const ticketCheckoutVM = new TicketCheckoutViewModel();