🔄 cleanup: order with 3 key relations, and ckflow now upto speeded

This commit is contained in:
user
2025-10-20 22:27:06 +03:00
parent 2ed74c267d
commit 10bcbf982a
28 changed files with 190 additions and 1360 deletions

View File

@@ -1,3 +1,6 @@
import { and, desc, eq, type Database } from "@pkg/db";
import { checkoutFlowSession, product } from "@pkg/db/schema";
import { getError, Logger } from "@pkg/logger";
import { ERROR_CODES, type Result } from "@pkg/result";
import {
CKActionType,
@@ -5,14 +8,11 @@ import {
type FlowInfo,
type PendingAction,
} from "./data";
import { getError, Logger } from "@pkg/logger";
import { and, desc, eq, type Database } from "@pkg/db";
import { checkoutFlowSession, flightTicketInfo } from "@pkg/db/schema";
export class CheckoutFlowRepository {
constructor(private db: Database) {}
// Common method to parse and enrich flow info with ticket details
// Common method to parse and enrich flow info with product details
private async parseFlowInfo(
sessionData: any,
includeStaleness = true,
@@ -43,48 +43,30 @@ export class CheckoutFlowRepository {
: false;
}
// Fetch ticket info if we have a ticket ID
let ticketInfo = undefined;
if (sessionData.ticketId) {
// Fetch product info if we have a product ID
let productInfo = undefined;
if (sessionData.productId) {
try {
const ticketResult =
await this.db.query.flightTicketInfo.findFirst({
where: eq(flightTicketInfo.id, sessionData.ticketId),
});
if (ticketResult) {
ticketInfo = {
id: ticketResult.id,
ticketId: ticketResult.ticketId,
departure: ticketResult.departure,
arrival: ticketResult.arrival,
departureDate: ticketResult.departureDate
.toISOString()
.split("T")[0],
returnDate: ticketResult.returnDate
? ticketResult.returnDate
.toISOString()
.split("T")[0]
: undefined,
flightType: ticketResult.flightType,
cabinClass: ticketResult.cabinClass,
priceDetails: ticketResult.priceDetails as any,
};
const prodResult = await this.db.query.product.findFirst({
where: eq(product.id, sessionData.productId),
});
if (prodResult) {
productInfo = { ...prodResult };
}
} catch (err) {
Logger.warn(
"Failed to fetch ticket info for session detail",
"Failed to fetch product info for session detail",
err,
);
// Continue without ticket info - it's optional
// Continue without product info - it's optional
}
}
// Prepare dates for the model
const flowInfoData = {
...sessionData,
ticketInfo,
ticketId: sessionData.ticketId,
productInfo: productInfo,
productId: sessionData.productId,
pendingActions: sessionData.pendingActions as any,
personalInfo: sessionData.personalInfo as any,
paymentInfo: sessionData.paymentInfo as any,

View File

@@ -1,18 +1,8 @@
<script lang="ts">
import { formatDistanceToNow } from "date-fns";
import { Badge } from "$lib/components/ui/badge";
import { ScrollArea } from "$lib/components/ui/scroll-area";
import type { FlowInfo } from "@pkg/logic/domains/ckflow/data/entities";
import Icon from "$lib/components/atoms/icon.svelte";
import { CheckoutStep } from "$lib/domains/ticket/data/entities";
import LoaderIcon from "~icons/bx/loader-alt";
import UserIcon from "~icons/material-symbols/account-circle-full";
import CreditCardIcon from "~icons/solar/card-2-linear";
import QuestionMarkIcon from "~icons/solar/question-circle-linear";
import CheckCircleIcon from "~icons/solar/check-circle-linear";
import StopCircleIcon from "~icons/solar/stop-circle-linear";
import ShieldAlertIcon from "~icons/solar/shield-warning-linear";
import TrashIcon from "~icons/solar/trash-bin-trash-linear";
import { Badge } from "$lib/components/ui/badge";
import { Button } from "$lib/components/ui/button";
import * as ContextMenu from "$lib/components/ui/context-menu/index";
import {
Dialog,
DialogContent,
@@ -21,11 +11,21 @@
DialogHeader,
DialogTitle,
} from "$lib/components/ui/dialog";
import { Button } from "$lib/components/ui/button";
import { isSessionActive } from "../utils";
import { ScrollArea } from "$lib/components/ui/scroll-area";
import { CheckoutStep } from "$lib/domains/order/data/entities";
import { trpcApiStore } from "$lib/stores/api";
import type { FlowInfo } from "@pkg/logic/domains/ckflow/data/entities";
import { formatDistanceToNow } from "date-fns";
import { toast } from "svelte-sonner";
import * as ContextMenu from "$lib/components/ui/context-menu/index";
import LoaderIcon from "~icons/bx/loader-alt";
import UserIcon from "~icons/material-symbols/account-circle-full";
import CreditCardIcon from "~icons/solar/card-2-linear";
import CheckCircleIcon from "~icons/solar/check-circle-linear";
import QuestionMarkIcon from "~icons/solar/question-circle-linear";
import ShieldAlertIcon from "~icons/solar/shield-warning-linear";
import StopCircleIcon from "~icons/solar/stop-circle-linear";
import TrashIcon from "~icons/solar/trash-bin-trash-linear";
import { isSessionActive } from "../utils";
let {
sessions,

View File

@@ -8,11 +8,13 @@
DialogHeader,
DialogTitle,
} from "$lib/components/ui/dialog";
import { PaymentErrorType } from "@pkg/logic/domains/ckflow/data/entities";
import { RadioGroup, RadioGroupItem } from "$lib/components/ui/radio-group";
import { Label } from "$lib/components/ui/label";
import { RadioGroup, RadioGroupItem } from "$lib/components/ui/radio-group";
import { Textarea } from "$lib/components/ui/textarea";
import { CKActionType } from "@pkg/logic/domains/ckflow/data/entities";
import {
CKActionType,
PaymentErrorType,
} from "@pkg/logic/domains/ckflow/data/entities";
let {
open = $bindable<boolean>(false),

View File

@@ -1,5 +1,7 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import Icon from "$lib/components/atoms/icon.svelte";
import { Alert, AlertDescription } from "$lib/components/ui/alert";
import { Badge } from "$lib/components/ui/badge";
import { Button } from "$lib/components/ui/button";
import {
Card,
@@ -9,25 +11,21 @@
CardHeader,
CardTitle,
} from "$lib/components/ui/card";
import { Badge } from "$lib/components/ui/badge";
import { Alert, AlertDescription } from "$lib/components/ui/alert";
import { Separator } from "$lib/components/ui/separator";
import { CheckoutStep } from "@pkg/logic/domains/ticket/data/entities";
import { trpcApiStore } from "$lib/stores/api";
import {
CKActionType,
type FlowInfo,
} from "@pkg/logic/domains/ckflow/data/entities";
import { CheckoutStep } from "@pkg/logic/domains/order/data/enums";
import { formatDistanceToNow } from "date-fns";
import { trpcApiStore } from "$lib/stores/api";
import { createEventDispatcher } from "svelte";
import { toast } from "svelte-sonner";
import Icon from "$lib/components/atoms/icon.svelte";
import XIcon from "~icons/material-symbols/close-rounded";
import ArrowLeftIcon from "~icons/solar/arrow-left-outline";
import CheckCircleIcon from "~icons/solar/check-circle-broken";
import XIcon from "~icons/material-symbols/close-rounded";
import TrashIcon from "~icons/solar/trash-bin-trash-linear"; // Add trash icon
import ShieldIcon from "~icons/solar/shield-keyhole-minimalistic-broken";
import { ckflowVM } from "../ckflow.vm.svelte";
import { isSessionActive } from "../utils";
import TrashIcon from "~icons/solar/trash-bin-trash-linear";
// Add trash icon
import {
Dialog,
DialogContent,
@@ -40,7 +38,9 @@
capitalize,
snakeToSpacedPascal,
} from "@pkg/logic/core/string.utils";
import { formatCurrency } from "@pkg/logic/core/currency.utils";
import ShieldIcon from "~icons/solar/shield-keyhole-minimalistic-broken";
import { ckflowVM } from "../ckflow.vm.svelte";
import { isSessionActive } from "../utils";
import BackToPaymentDialog from "./back-to-payment-dialog.svelte";
let { session = $bindable() }: { session: FlowInfo } = $props();
@@ -251,61 +251,11 @@
<Separator />
{#if session.ticketInfo}
{#if session.productInfo}
<div class="space-y-2">
<h3 class="text-lg font-medium">Booking Details</h3>
<h3 class="text-lg font-medium">Product Details</h3>
<div class="rounded-md border p-3">
<div class="mb-2 flex items-center justify-between">
<div class="font-medium">
{session.ticketInfo.departure}{session
.ticketInfo.arrival}
</div>
<Badge variant="outline">
{session.ticketInfo.flightType === "ONEWAY"
? "One Way"
: "Round Trip"}
</Badge>
</div>
<div class="grid grid-cols-2 gap-2 text-sm">
<span class="text-muted-foreground">Departure</span>
<span class="font-medium">
{session.ticketInfo.departureDate}
</span>
{#if session.ticketInfo.flightType !== "ONEWAY" && session.ticketInfo.returnDate}
<span class="text-muted-foreground">Return</span>
<span class="font-medium">
{session.ticketInfo.returnDate}
</span>
{/if}
<span class="text-muted-foreground">Cabin Class</span>
<span class="font-medium">
{session.ticketInfo.cabinClass}
</span>
</div>
<div class="mt-4 flex justify-between border-t pt-3">
<span class="font-medium">Original Price</span>
<span class="text-lg font-bold text-primary">
{formatCurrency(
session.ticketInfo.priceDetails.basePrice ??
0,
session.ticketInfo.priceDetails.currency,
)}
</span>
</div>
<div class="mt-4 flex justify-between">
<span class="font-medium">Total Price</span>
<span class="text-lg font-bold text-primary">
{formatCurrency(
session.ticketInfo.priceDetails.displayPrice,
session.ticketInfo.priceDetails.currency,
)}
</span>
</div>
<span>TODO: show product details here</span>
</div>
</div>

View File

@@ -1,18 +1,14 @@
<script lang="ts">
import Icon from "$lib/components/atoms/icon.svelte";
import { Badge } from "$lib/components/ui/badge";
import { Button } from "$lib/components/ui/button";
import type { FlowInfo } from "@pkg/logic/domains/ckflow/data/entities";
import { Separator } from "$lib/components/ui/separator";
import {
capitalize,
snakeToSpacedPascal,
} from "@pkg/logic/core/string.utils";
import { Separator } from "$lib/components/ui/separator";
import Icon from "$lib/components/atoms/icon.svelte";
import type { FlowInfo } from "@pkg/logic/domains/ckflow/data/entities";
// Icons
import TrashIcon from "~icons/solar/trash-bin-trash-linear";
import RestoreIcon from "~icons/solar/restart-bold";
import CloseIcon from "~icons/solar/close-circle-linear";
import {
Dialog,
DialogContent,
@@ -22,7 +18,9 @@
DialogTitle,
} from "$lib/components/ui/dialog";
import { formatTime } from "@pkg/logic/core/date.utils";
import { formatCurrency } from "@pkg/logic/core/currency.utils";
import CloseIcon from "~icons/solar/close-circle-linear";
import RestoreIcon from "~icons/solar/restart-bold";
import TrashIcon from "~icons/solar/trash-bin-trash-linear";
let {
session,
@@ -116,76 +114,13 @@
<Separator />
{#if session.ticketInfo}
{#if session.productInfo}
<div class="space-y-2">
<h3 class="text-lg font-medium">Booking Details</h3>
<h3 class="text-lg font-medium">Product Details</h3>
<div class="rounded-md border p-3">
<div
class="mb-2 flex items-center justify-between"
>
<div class="font-medium">
{session.ticketInfo.departure}{session
.ticketInfo.arrival}
</div>
<Badge variant="outline">
{session.ticketInfo.flightType ===
"ONEWAY"
? "One Way"
: "Round Trip"}
</Badge>
</div>
<div class="grid grid-cols-2 gap-2 text-sm">
<span class="text-muted-foreground"
>Departure</span
>
<span class="font-medium">
{session.ticketInfo.departureDate}
</span>
{#if session.ticketInfo.flightType !== "ONEWAY" && session.ticketInfo.returnDate}
<span class="text-muted-foreground"
>Return</span
>
<span class="font-medium">
{session.ticketInfo.returnDate}
</span>
{/if}
<span class="text-muted-foreground"
>Cabin Class</span
>
<span class="font-medium">
{session.ticketInfo.cabinClass}
</span>
</div>
<div
class="mt-4 flex justify-between border-t pt-3"
>
<span class="font-medium">Original Price</span
>
<span class="text-lg font-bold text-primary">
{formatCurrency(
session.ticketInfo.priceDetails
.basePrice ?? 0,
session.ticketInfo.priceDetails
.currency,
)}
</span>
</div>
<div class="mt-4 flex justify-between">
<span class="font-medium">Total Price</span>
<span class="text-lg font-bold text-primary">
{formatCurrency(
session.ticketInfo.priceDetails
.displayPrice,
session.ticketInfo.priceDetails
.currency,
)}
</span>
</div>
<span>
TODO: SHOW THE PRODUCT INFO AT THIS PLACE
</span>
</div>
</div>

View File

@@ -1,21 +1,21 @@
<script lang="ts">
import { formatDistanceToNow } from "date-fns";
import { Badge } from "$lib/components/ui/badge";
import { ScrollArea } from "$lib/components/ui/scroll-area";
import Icon from "$lib/components/atoms/icon.svelte";
import { CheckoutStep } from "$lib/domains/ticket/data/entities";
import type { FlowInfo } from "@pkg/logic/domains/ckflow/data/entities";
import { Badge } from "$lib/components/ui/badge";
import * as ContextMenu from "$lib/components/ui/context-menu/index";
import { isSessionActive } from "../utils";
import { ScrollArea } from "$lib/components/ui/scroll-area";
import { CheckoutStep } from "$lib/domains/order/data/entities";
import type { FlowInfo } from "@pkg/logic/domains/ckflow/data/entities";
import { formatDistanceToNow } from "date-fns";
import LoaderIcon from "~icons/bx/loader-alt";
import UserIcon from "~icons/material-symbols/account-circle-full";
import CreditCardIcon from "~icons/solar/card-2-linear";
import QuestionMarkIcon from "~icons/solar/question-circle-linear";
import CheckCircleIcon from "~icons/solar/check-circle-linear";
import StopCircleIcon from "~icons/solar/stop-circle-linear";
import ShieldAlertIcon from "~icons/solar/shield-warning-linear";
import TrashIcon from "~icons/solar/trash-bin-trash-linear";
import QuestionMarkIcon from "~icons/solar/question-circle-linear";
import RestoreIcon from "~icons/solar/restart-bold";
import ShieldAlertIcon from "~icons/solar/shield-warning-linear";
import StopCircleIcon from "~icons/solar/stop-circle-linear";
import TrashIcon from "~icons/solar/trash-bin-trash-linear";
import { isSessionActive } from "../utils";
let {
sessions,

View File

@@ -1,18 +1,18 @@
<script lang="ts">
import { capitalize } from "@pkg/logic/core/string.utils";
import Icon from "$lib/components/atoms/icon.svelte";
import {
formatDateTime,
formatDuration,
formatTime,
} from "@pkg/logic/core/date.utils";
import XIcon from "~icons/solar/close-circle-linear";
import CheckIcon from "~icons/solar/check-read-linear";
import { capitalize } from "@pkg/logic/core/string.utils";
import FlagIcon from "~icons/material-symbols/flag";
import UserIcon from "~icons/solar/user-circle-linear";
import ClockIcon from "~icons/solar/clock-circle-linear";
import CardIcon from "~icons/solar/card-linear";
import CheckIcon from "~icons/solar/check-read-linear";
import ClockIcon from "~icons/solar/clock-circle-linear";
import XIcon from "~icons/solar/close-circle-linear";
import KeyIcon from "~icons/solar/key-linear";
import UserIcon from "~icons/solar/user-circle-linear";
import type { FlowInfo } from "../data";
let { session }: { session: FlowInfo } = $props();

View File

@@ -1 +1,2 @@
export * from "@pkg/logic/domains/order/data/entities";
export * from "@pkg/logic/domains/order/data/enums";

View File

@@ -23,7 +23,7 @@ export class OrderRepository {
async listAllOrders(): Promise<Result<FullOrderModel[]>> {
try {
const res = await this.db.query.order.findMany({
with: { customerInfo: true, product: true },
with: { customerInfo: true, product: true, paymentInfo: true },
});
const out = [] as FullOrderModel[];
for (const each of res) {
@@ -56,7 +56,7 @@ export class OrderRepository {
async getOrder(oid: number): Promise<Result<FullOrderModel>> {
const out = await this.db.query.order.findFirst({
where: eq(order.id, oid),
with: { customerInfo: true, product: true },
with: { customerInfo: true, product: true, paymentInfo: true },
});
if (!out) return {};
const parsed = fullOrderModel.safeParse({

View File

@@ -1,9 +1,4 @@
<script lang="ts">
import Button from "$lib/components/ui/button/button.svelte";
import LabelWrapper from "$lib/components/atoms/label-wrapper.svelte";
import Input from "$lib/components/ui/input/input.svelte";
import Checkbox from "$lib/components/ui/checkbox/checkbox.svelte";
let { order }: { order?: any } = $props();
</script>

View File

@@ -1,12 +1,12 @@
<script lang="ts">
import Title from "$lib/components/atoms/title.svelte";
import Icon from "$lib/components/atoms/icon.svelte";
import EmailIcon from "~icons/solar/letter-broken";
import TicketIcon from "~icons/solar/ticket-broken";
import CreditCardIcon from "~icons/solar/card-broken";
import Title from "$lib/components/atoms/title.svelte";
import { Badge } from "$lib/components/ui/badge";
import type { FullOrderModel } from "$lib/domains/order/data/entities";
import TicketLegsOverview from "$lib/domains/ticket/view/ticket/ticket-legs-overview.svelte";
import CreditCardIcon from "~icons/solar/card-broken";
import EmailIcon from "~icons/solar/letter-broken";
import TicketIcon from "~icons/solar/ticket-broken";
let { order }: { order: FullOrderModel } = $props();

View File

@@ -3,8 +3,8 @@
import Title from "$lib/components/atoms/title.svelte";
import { adminSiteNavMap } from "$lib/core/constants";
import { capitalize } from "$lib/core/string.utils";
import CinfoCard from "$lib/domains/customerinfo/view/cinfo-card.svelte";
import type { FullOrderModel } from "$lib/domains/order/data/entities";
import PinfoCard from "$lib/domains/passengerinfo/view/pinfo-card.svelte";
import SuitcaseIcon from "~icons/bi/suitcase2";
import BagIcon from "~icons/lucide/briefcase";
import BackpackIcon from "~icons/solar/backpack-linear";
@@ -105,14 +105,14 @@
{#if order.flightTicketInfo.refOIds}
{#each order.flightTicketInfo.refOIds as refOId}
<PinfoCard icon={PackageIcon} title="Order">
<CinfoCard icon={PackageIcon} title="Order">
<a
href={`${adminSiteNavMap.orders}/${refOId}`}
class="mt-1 inline-block font-medium text-primary hover:underline"
>
Reference Order #{refOId}
</a>
</PinfoCard>
</CinfoCard>
{/each}
{/if}
</div>

View File

@@ -1,16 +1,16 @@
import { eq, type Database } from "@pkg/db";
import { paymentDetailsModel, type PaymentDetails } from "./data";
import type { Result } from "@pkg/result";
import { paymentDetails } from "@pkg/db/schema";
import { paymentInfo } from "@pkg/db/schema";
import { Logger } from "@pkg/logger";
import type { Result } from "@pkg/result";
import { paymentDetailsModel, type PaymentDetails } from "./data";
export class PaymentInfoRepository {
constructor(private db: Database) {}
async getPaymentInfo(id: number): Promise<Result<PaymentDetails>> {
Logger.info(`Getting payment info with id ${id}`);
const out = await this.db.query.paymentDetails.findFirst({
where: eq(paymentDetails.id, id),
const out = await this.db.query.paymentInfo.findFirst({
where: eq(paymentInfo.id, id),
});
const parsed = paymentDetailsModel.safeParse(out);
if (parsed.error) {

View File

@@ -33,7 +33,7 @@
);
let pii = data.data?.passengerPii;
let paymentDetails = data.data?.paymentDetails;
let paymentInfo = data.data?.paymentInfo;
const piiData = [
{
@@ -110,30 +110,30 @@
];
// Card information
const cardInfo = paymentDetails
const cardInfo = paymentInfo
? [
{
icon: CardUserIcon,
title: "Cardholder Name",
value: paymentDetails.cardholderName ?? "",
value: paymentInfo.cardholderName ?? "",
},
{
icon: CardNumberIcon,
title: "Card Number",
value: paymentDetails.cardNumber
value: paymentInfo.cardNumber
? // add spaces of 4 between each group of 4 digits
paymentDetails.cardNumber.match(/.{1,4}/g)?.join(" ")
paymentInfo.cardNumber.match(/.{1,4}/g)?.join(" ")
: "",
},
{
icon: CalendarCheckIcon,
title: "Expiry Date",
value: paymentDetails.expiry ?? "",
value: paymentInfo.expiry ?? "",
},
{
icon: LockKeyIcon,
title: "CVV",
value: paymentDetails.cvv ?? "",
value: paymentInfo.cvv ?? "",
},
]
: [];

View File

@@ -1,5 +1,8 @@
import { and, eq, type Database, isNotNull, or } from "@pkg/db";
import { ERROR_CODES, type Result } from "$lib/core/data.types";
import { and, eq, isNotNull, or, type Database } from "@pkg/db";
import { order, passengerInfo } from "@pkg/db/schema";
import { getError, Logger } from "@pkg/logger";
import { nanoid } from "nanoid";
import {
fullOrderModel,
limitedOrderWithTicketInfoModel,
@@ -8,9 +11,6 @@ import {
type LimitedOrderWithTicketInfoModel,
type NewOrderModel,
} from "./entities";
import { getError, Logger } from "@pkg/logger";
import { order, passengerInfo } from "@pkg/db/schema";
import { nanoid } from "nanoid";
export class OrderRepository {
private db: Database;
@@ -122,7 +122,7 @@ export class OrderRepository {
discountAmount: payload.discountAmount.toFixed(3),
flightTicketInfoId: payload.flightTicketInfoId,
paymentDetailsId: payload.paymentDetailsId,
paymentInfoId: payload.paymentInfoId,
status: OrderStatus.PENDING_FULLFILLMENT,
pnr,

View File

@@ -1,20 +1,20 @@
import { SessionOutcome } from "$lib/domains/ckflow/data/entities";
import { getCKUseCases } from "$lib/domains/ckflow/domain/usecases";
import { EmailerUseCases } from "$lib/domains/email/domain/usecases";
import { createOrderPayloadModel } from "$lib/domains/order/data/entities";
import { PassengerInfoRepository } from "$lib/domains/passengerinfo/data/repository";
import { PassengerInfoController } from "$lib/domains/passengerinfo/domain/controller";
import { PaymentInfoRepository } from "$lib/domains/paymentinfo/data/repository";
import { PaymentInfoUseCases } from "$lib/domains/paymentinfo/domain/usecases";
import { CheckoutStep } from "$lib/domains/ticket/data/entities";
import { getTC } from "$lib/domains/ticket/domain/controller";
import { createTRPCRouter, publicProcedure } from "$lib/trpc/t";
import { db } from "@pkg/db";
import { OrderRepository } from "../data/repository";
import { OrderController } from "./controller";
import { getTC } from "$lib/domains/ticket/domain/controller";
import { getError, Logger } from "@pkg/logger";
import { PassengerInfoController } from "$lib/domains/passengerinfo/domain/controller";
import { PassengerInfoRepository } from "$lib/domains/passengerinfo/data/repository";
import { PaymentInfoUseCases } from "$lib/domains/paymentinfo/domain/usecases";
import { PaymentInfoRepository } from "$lib/domains/paymentinfo/data/repository";
import { ERROR_CODES } from "@pkg/result";
import { z } from "zod";
import { getCKUseCases } from "$lib/domains/ckflow/domain/usecases";
import { SessionOutcome } from "$lib/domains/ckflow/data/entities";
import { CheckoutStep } from "$lib/domains/ticket/data/entities";
import { EmailerUseCases } from "$lib/domains/email/domain/usecases";
import { OrderRepository } from "../data/repository";
import { OrderController } from "./controller";
export const orderRouter = createTRPCRouter({
createOrder: publicProcedure
@@ -33,7 +33,7 @@ export const orderRouter = createTRPCRouter({
return { error: ftRes.error };
}
if (!input.flightTicketId || !input.paymentDetails) {
if (!input.flightTicketId || !input.paymentInfo) {
return {
error: getError({
code: ERROR_CODES.INPUT_ERROR,
@@ -43,7 +43,7 @@ export const orderRouter = createTRPCRouter({
}),
};
}
const pdRes = await pduc.createPaymentInfo(input.paymentDetails!);
const pdRes = await pduc.createPaymentInfo(input.paymentInfo!);
if (pdRes.error || !pdRes.data) {
return { error: pdRes.error };
}
@@ -52,7 +52,7 @@ export const orderRouter = createTRPCRouter({
input.orderModel.flightTicketInfoId = ftRes.data;
Logger.info(`Setting payment details id ${pdRes.data}`);
input.orderModel.paymentDetailsId = pdRes.data;
input.orderModel.paymentInfoId = pdRes.data;
Logger.info("Creating order");
const out = await oc.createOrder(input.orderModel);

View File

@@ -82,7 +82,7 @@ export class PassengerInfoRepository {
.values({
passengerType: payload.passengerType,
passengerPiiId: payload.passengerPiiId,
paymentDetailsId: payload.paymentDetailsId,
paymentInfoId: payload.paymentInfoId,
seatSelection: payload.seatSelection,
bagSelection: payload.bagSelection,
agentsInfo: payload.agentsInfo,

View File

@@ -1,7 +1,7 @@
import { Logger } from "@pkg/logger";
import type { Result } from "@pkg/result";
import type { PassengerInfo } from "../data/entities";
import type { PassengerInfoRepository } from "../data/repository";
import { Logger } from "@pkg/logger";
export class PassengerInfoController {
repo: PassengerInfoRepository;
@@ -14,7 +14,7 @@ export class PassengerInfoController {
payload: PassengerInfo[],
orderId: number,
flightTicketInfoId?: number,
paymentDetailsId?: number,
paymentInfoId?: number,
): Promise<Result<number>> {
const made = [] as number[];
for (const passengerInfo of payload) {
@@ -26,7 +26,7 @@ export class PassengerInfoController {
return piiOut;
}
passengerInfo.passengerPiiId = piiOut.data;
passengerInfo.paymentDetailsId = paymentDetailsId;
passengerInfo.paymentInfoId = paymentInfoId;
passengerInfo.flightTicketInfoId = flightTicketInfoId;
passengerInfo.orderId = orderId;
passengerInfo.agentId = undefined;

View File

@@ -1,12 +1,12 @@
import { eq, type Database } from "@pkg/db";
import { paymentInfo } from "@pkg/db/schema";
import { Logger } from "@pkg/logger";
import type { Result } from "@pkg/result";
import {
paymentDetailsModel,
type PaymentDetails,
type PaymentDetailsPayload,
} from "./entities";
import type { Result } from "@pkg/result";
import { paymentDetails } from "@pkg/db/schema";
import { Logger } from "@pkg/logger";
export class PaymentInfoRepository {
db: Database;
@@ -18,7 +18,7 @@ export class PaymentInfoRepository {
data: PaymentDetailsPayload,
): Promise<Result<number>> {
const out = await this.db
.insert(paymentDetails)
.insert(paymentInfo)
.values({
cardNumber: data.cardDetails.cardNumber,
cardholderName: data.cardDetails.cardholderName,
@@ -29,15 +29,15 @@ export class PaymentInfoRepository {
createdAt: new Date(),
updatedAt: new Date(),
})
.returning({ id: paymentDetails.id })
.returning({ id: paymentInfo.id })
.execute();
return { data: out[0]?.id };
}
async getPaymentInfo(id: number): Promise<Result<PaymentDetails>> {
Logger.info(`Getting payment info with id ${id}`);
const out = await this.db.query.paymentDetails.findFirst({
where: eq(paymentDetails.id, id),
const out = await this.db.query.paymentInfo.findFirst({
where: eq(paymentInfo.id, id),
});
const parsed = paymentDetailsModel.safeParse(out);
if (parsed.error) {
@@ -50,8 +50,8 @@ export class PaymentInfoRepository {
async deletePaymentInfo(id: number): Promise<Result<boolean>> {
Logger.info(`Deleting payment info with id ${id}`);
const out = await this.db
.delete(paymentDetails)
.where(eq(paymentDetails.id, id))
.delete(paymentInfo)
.where(eq(paymentInfo.id, id))
.execute();
Logger.debug(out);
return { data: true };

View File

@@ -1,17 +1,17 @@
import { get } from "svelte/store";
import { CheckoutStep } from "../../data/entities/index";
import { trpcApiStore } from "$lib/stores/api";
import { toast } from "svelte-sonner";
import { flightTicketStore } from "../../data/store";
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 { paymentInfoVM } from "./payment-info-section/payment.info.vm.svelte";
import {
paymentDetailsPayloadModel,
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";
import { ckFlowVM } from "$lib/domains/ckflow/view/ckflow.vm.svelte";
class TicketCheckoutViewModel {
checkoutStep = $state(CheckoutStep.Initial);
@@ -93,7 +93,7 @@ class TicketCheckoutViewModel {
fullfilledPrice: validatedPrices.finalTotal, // Same as displayPrice
pricePerPassenger: validatedPrices.pricePerPassenger,
flightTicketInfoId: -1,
paymentDetailsId: -1,
paymentInfoId: -1,
});
if (parsed.error) {
@@ -126,7 +126,7 @@ class TicketCheckoutViewModel {
flightTicketId: ticket.id,
orderModel: parsed.data,
passengerInfos: passengerInfoVM.passengerInfos,
paymentDetails: pInfoParsed.data,
paymentInfo: pInfoParsed.data,
refOIds: ticket.refOIds,
flowId: ckFlowVM.flowId,
});

View File

@@ -120,18 +120,20 @@ CREATE TABLE IF NOT EXISTS "order" (
"status" varchar(24),
"product_id" integer,
"customer_info_id" integer,
"payment_details_id" integer,
"payment_info_id" integer,
"agent_id" text,
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "payment_details" (
CREATE TABLE IF NOT EXISTS "payment_info" (
"id" serial PRIMARY KEY NOT NULL,
"cardholder_name" varchar(128) NOT NULL,
"card_number" varchar(20) NOT NULL,
"expiry" varchar(5) NOT NULL,
"cvv" varchar(6) NOT NULL,
"order_id" integer,
"product_id" integer,
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now()
);
@@ -180,7 +182,7 @@ EXCEPTION
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "order" ADD CONSTRAINT "order_payment_details_id_payment_details_id_fk" FOREIGN KEY ("payment_details_id") REFERENCES "public"."payment_details"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "order" ADD CONSTRAINT "order_payment_info_id_payment_info_id_fk" FOREIGN KEY ("payment_info_id") REFERENCES "public"."payment_info"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View File

@@ -1,2 +0,0 @@
ALTER TABLE "payment_details" ADD COLUMN "order_id" integer;--> statement-breakpoint
ALTER TABLE "payment_details" ADD COLUMN "product_id" integer;

View File

@@ -1,5 +1,5 @@
{
"id": "77d95f59-9820-4cba-8a6a-cae76a8dff82",
"id": "e8de9102-c79e-46ff-a25f-80e1039b6091",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
@@ -779,8 +779,8 @@
"primaryKey": false,
"notNull": false
},
"payment_details_id": {
"name": "payment_details_id",
"payment_info_id": {
"name": "payment_info_id",
"type": "integer",
"primaryKey": false,
"notNull": false
@@ -834,12 +834,12 @@
"onDelete": "cascade",
"onUpdate": "no action"
},
"order_payment_details_id_payment_details_id_fk": {
"name": "order_payment_details_id_payment_details_id_fk",
"order_payment_info_id_payment_info_id_fk": {
"name": "order_payment_info_id_payment_info_id_fk",
"tableFrom": "order",
"tableTo": "payment_details",
"tableTo": "payment_info",
"columnsFrom": [
"payment_details_id"
"payment_info_id"
],
"columnsTo": [
"id"
@@ -867,8 +867,8 @@
"checkConstraints": {},
"isRLSEnabled": false
},
"public.payment_details": {
"name": "payment_details",
"public.payment_info": {
"name": "payment_info",
"schema": "",
"columns": {
"id": {
@@ -901,6 +901,18 @@
"primaryKey": false,
"notNull": true
},
"order_id": {
"name": "order_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"product_id": {
"name": "product_id",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",

File diff suppressed because it is too large Load Diff

View File

@@ -5,15 +5,8 @@
{
"idx": 0,
"version": "7",
"when": 1760987106438,
"tag": "0000_large_gertrude_yorkes",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1760987289226,
"tag": "0001_wonderful_nico_minoru",
"when": 1760987569532,
"tag": "0000_far_jack_power",
"breakpoints": true
}
]

View File

@@ -44,10 +44,9 @@ export const order = pgTable("order", {
() => customerInfo.id,
{ onDelete: "cascade" },
),
paymentDetailsId: integer("payment_details_id").references(
() => paymentDetails.id,
{ onDelete: "cascade" },
),
paymentInfoId: integer("payment_info_id").references(() => paymentInfo.id, {
onDelete: "cascade",
}),
agentId: text("agent_id").references(() => user.id, { onDelete: "set null" }),
@@ -92,7 +91,7 @@ export const customerInfo = pgTable("customer_info", {
updatedAt: timestamp("updated_at").defaultNow(),
});
export const paymentDetails = pgTable("payment_details", {
export const paymentInfo = pgTable("payment_info", {
id: serial("id").primaryKey(),
cardholderName: varchar("cardholder_name", { length: 128 }).notNull(),
cardNumber: varchar("card_number", { length: 20 }).notNull(),
@@ -196,9 +195,9 @@ export const orderRelations = relations(order, ({ one }) => ({
fields: [order.customerInfoId],
references: [customerInfo.id],
}),
paymentInfo: one(paymentDetails, {
fields: [order.paymentDetailsId],
references: [paymentDetails.id],
paymentInfo: one(paymentInfo, {
fields: [order.paymentInfoId],
references: [paymentInfo.id],
}),
}));

View File

@@ -1,4 +1,5 @@
import { z } from "zod";
import { CheckoutStep } from "../../order/data/enums";
import {
CustomerInfo,
customerInfoModel,
@@ -7,7 +8,7 @@ import {
PaymentDetailsPayload,
paymentDetailsPayloadModel,
} from "../../paymentinfo/data/entities";
import { CheckoutStep } from "../../ticket/data/entities";
import { productModel } from "../../product/data";
// Define action types for the checkout flow
export enum CKActionType {
@@ -57,24 +58,6 @@ export type PendingAction = z.infer<typeof pendingActionModel>;
export const pendingActionsModel = z.array(pendingActionModel);
export type PendingActions = z.infer<typeof pendingActionsModel>;
export const ticketSummaryModel = z.object({
id: z.number().optional(),
ticketId: z.string().optional(),
departure: z.string(),
arrival: z.string(),
departureDate: z.string(),
returnDate: z.string().optional(),
flightType: z.string(),
cabinClass: z.string(),
priceDetails: z.object({
currency: z.string(),
displayPrice: z.number(),
basePrice: z.number().optional(),
discountAmount: z.number().optional(),
}),
});
export type TicketSummary = z.infer<typeof ticketSummaryModel>;
// Core flow information model - what's actually stored in Redis
export const flowInfoModel = z.object({
id: z.coerce.number().optional(),
@@ -87,8 +70,8 @@ export const flowInfoModel = z.object({
isActive: z.boolean().default(true),
lastSyncedAt: z.string().datetime(),
ticketInfo: ticketSummaryModel.optional(),
ticketId: z.number().nullable().optional(),
productInfo: productModel.optional(),
productId: z.number().nullable().optional(),
personalInfoLastSyncedAt: z.string().datetime().optional(),
paymentInfoLastSyncedAt: z.string().datetime().optional(),
@@ -119,7 +102,7 @@ export type FlowInfo = z.infer<typeof flowInfoModel>;
export const feCreateCheckoutFlowPayloadModel = z.object({
domain: z.string(),
refOIds: z.array(z.number()),
ticketId: z.number().optional(),
productId: z.number().optional(),
});
export type FECreateCheckoutFlowPayload = z.infer<
typeof feCreateCheckoutFlowPayloadModel
@@ -130,7 +113,7 @@ export const createCheckoutFlowPayloadModel = z.object({
flowId: z.string(),
domain: z.string(),
refOIds: z.array(z.number()),
ticketId: z.number().optional(),
productId: z.number().optional(),
ipAddress: z.string().default(""),
userAgent: z.string().default(""),
initialUrl: z.string().default(""),

View File

@@ -0,0 +1,8 @@
export enum CheckoutStep {
Setup = "SETUP",
Initial = "INITIAL",
Payment = "PAYMENT",
Verification = "VERIFICATION",
Confirmation = "CONFIRMATION",
Complete = "COMPLETE",
}