← Files WixARCHIVED FILE
skills/wix-headless-templates/events/project/src/wix/events/orders-core.ts
3.88 KB · Oct 8, 2026 · 12:02 UTC
// Order rules and DTO mapping for the confirmation page — transport-agnostic, imported by both
// ./orders.ts (SDK) and the REST twin in templates/events/rest/orders.ts (fetch). The fieldsets,
// the "tickets are ready" rule, the polling cadence, status copy, and the invoice mapping live
// HERE, once. Raw orders carry Date objects on the SDK and ISO strings on REST; money is
// `{ value, amount (deprecated), currency }`. Types are imported type-only; the money/date
// formatters come from ./events-core (the same file both transports ship).
import type { OrderSummary } from "./types";
import { formatMoney, isoOf, type Raw } from "./events-core";
/** Get Order fieldsets: TICKETS (the generated tickets), DETAILS (status, created, email, ticketsPdf), INVOICE. */
export const ORDER_FIELDSETS = ["TICKETS", "DETAILS", "INVOICE"] as const;
/** Wix polls the order every 2 s for up to 15 s until the tickets exist (they are generated after payment). */
export const ORDER_POLL_INTERVAL_MS = 2000;
export const ORDER_POLL_TOTAL_MS = 15000;
/** Visitor copy per Wix order status. */
export const ORDER_STATUS_COPY: Record<string, string> = {
FREE: "Confirmed",
PAID: "Paid",
PENDING: "Payment pending",
OFFLINE_PENDING: "Payment due at the event",
INITIATED: "Awaiting payment",
AUTHORIZED: "Payment authorized",
PARTIALLY_PAID: "Partially paid",
CANCELED: "Canceled",
DECLINED: "Payment declined",
VOIDED: "Voided",
};
/** Wix's isOrderReady: every ordered ticket has been generated. */
export function orderReady(raw: Raw | null | undefined): boolean {
const quantity = Number(raw?.ticketsQuantity);
return Number.isFinite(quantity) && quantity === ((raw?.tickets ?? []) as Raw[]).length;
}
/** "Sep 26, 2026" — the order date in the visitor's locale (Wix formats it without a zone). */
export function formatOrderDate(value: unknown): string {
const iso = isoOf(value);
if (!iso) return "";
try {
return new Intl.DateTimeFormat(undefined, { dateStyle: "medium" }).format(new Date(iso));
} catch {
return iso.slice(0, 10);
}
}
const percent = (rate: unknown): number => {
const n = Number(rate);
return Number.isFinite(n) ? n : 0;
};
/** A raw Order (TICKETS + DETAILS + INVOICE) → the confirmation DTO. */
export function toOrder(raw: Raw): OrderSummary {
const invoice: Raw = raw.invoice ?? {};
const discounts: Raw[] = invoice.discount?.discounts ?? [];
const coupon = discounts.find((d) => d.coupon);
const paidPlan = discounts.find((d) => d.paidPlan);
const tax: Raw | undefined = invoice.tax;
const fee: Raw | undefined = ((invoice.fees ?? []) as Raw[]).find((f) => f.type === "FEE_ADDED_AT_CHECKOUT");
const status: string = raw.status ?? "";
const ticketCount = Number(raw.ticketsQuantity);
return {
number: raw.orderNumber ?? "",
status,
statusLabel: ORDER_STATUS_COPY[status] ?? "",
settled: status === "FREE" || status === "PAID",
email: raw.email ?? "",
createdLabel: formatOrderDate(raw.created),
items: ((invoice.items ?? []) as Raw[]).map((i) => ({
name: i.name ?? "",
price: formatMoney(i.price),
quantity: Number(i.quantity) || 0,
total: formatMoney(i.total),
})),
subtotal: formatMoney(invoice.subTotal),
couponDiscount: coupon ? formatMoney(coupon.amount) : "",
paidPlanDiscount: paidPlan ? { amount: formatMoney(paidPlan.amount), ratePercent: percent(paidPlan.paidPlan?.percentDiscount?.rate) } : null,
tax: tax?.amount ? { name: tax.name ?? "Tax", ratePercent: percent(tax.rate), amount: formatMoney(tax.amount) } : null,
fee: fee?.amount ? { amount: formatMoney(fee.amount), ratePercent: percent(fee.rate) } : null,
total: formatMoney(invoice.grandTotal ?? raw.totalPrice),
ticketsPdfUrl: typeof raw.ticketsPdf === "string" ? raw.ticketsPdf : "",
ticketCount: Number.isFinite(ticketCount) ? ticketCount : ((raw.tickets ?? []) as Raw[]).length,
ticketsReady: orderReady(raw),
};
}
SHA-256: f8fad79c507fa30d2583bff43d00e4be82a77c9b419405de3195090f035a28a5