import Api from '../../services/api'; import type { OrderRequestItem, OrderRequestPayload } from '../../types/api'; import { AnalyticsConsent } from '../../services/analyticsConsent'; import { Identity } from '../../services/identity'; const MAX_ITEMS = 50; const MAX_URL_LENGTH = 500; const MAX_STRING_LENGTH = 128; export interface TrackOrderItemInput { id?: string; productId?: string; product_id?: string; quantity?: number; price?: number; } export interface TrackOrderPayload { orderId: string; revenue: number; currency: string; items?: TrackOrderItemInput[]; products?: TrackOrderItemInput[]; pageLocation?: string; pageReferrer?: string; } export interface TrackOrderResult { success: boolean; error?: string; } export const trackOrder = async (input: TrackOrderPayload): Promise => { try { if (!AnalyticsConsent.has()) { return { success: false, error: 'Analytics consent not granted' }; } const payload = buildRequestPayload(input); await Api.trackOrder(payload); return { success: true }; } catch (error) { const message = error instanceof Error ? error.message : 'Unknown error'; console.warn('AdalongTrack: failed to send order', message); return { success: false, error: message }; } }; const sanitizeString = (value: unknown, maxLength = MAX_STRING_LENGTH): string | undefined => { if (typeof value !== 'string') { return undefined; } const trimmed = value.trim(); return trimmed ? trimmed.slice(0, maxLength) : undefined; }; const sanitizeNumber = (value: unknown): number | undefined => { const numeric = typeof value === 'number' ? value : Number(value); return Number.isFinite(numeric) ? numeric : undefined; }; const sanitizeCurrency = (value: unknown): string | undefined => { const sanitized = sanitizeString(value, 10); return sanitized ? sanitized.toUpperCase() : undefined; }; const roundCurrency = (value: number): number => Math.round(value * 100) / 100; const getDefaultLocation = (): string | undefined => { if (typeof window === 'undefined' || !window.location) { return undefined; } return window.location.href.slice(0, MAX_URL_LENGTH); }; const getDefaultReferrer = (): string | undefined => { if (typeof document === 'undefined') { return undefined; } return document.referrer ? document.referrer.slice(0, MAX_URL_LENGTH) : undefined; }; const normalizeItems = (items?: TrackOrderItemInput[]): OrderRequestItem[] | undefined => { if (!Array.isArray(items) || items.length === 0) { return undefined; } const normalized = items .slice(0, MAX_ITEMS) .map((item) => { const productId = sanitizeString(item.productId ?? item.product_id ?? item.id, MAX_STRING_LENGTH); if (!productId) { return undefined; } const normalizedItem: OrderRequestItem = { productId }; const quantity = sanitizeNumber(item.quantity); if (quantity !== undefined && quantity > 0) { normalizedItem.quantity = Math.trunc(quantity); } const price = sanitizeNumber(item.price); if (price !== undefined && price >= 0) { normalizedItem.price = roundCurrency(price); } return normalizedItem; }) .filter((value): value is OrderRequestItem => Boolean(value)); return normalized.length > 0 ? normalized : undefined; }; export const buildRequestPayload = (input: TrackOrderPayload): OrderRequestPayload => { if (!input || typeof input !== 'object') { throw new Error('trackOrder: payload must be an object'); } const orderId = sanitizeString(input.orderId); if (!orderId) { throw new Error('trackOrder: orderId is required'); } const revenue = sanitizeNumber(input.revenue); if (revenue === undefined) { throw new Error('trackOrder: revenue must be a number'); } const currency = sanitizeCurrency(input.currency); if (!currency) { throw new Error('trackOrder: currency is required'); } const identityPayload = (() => { if (!AnalyticsConsent.has()) { return undefined; } const identity = Identity.getIdentity(); if (!identity) { return undefined; } Identity.touchSession(); return { visitor_id: identity.visitorId, session_id: identity.sessionId, }; })(); const rawItems = input.items ?? input.products; const normalizedItems = normalizeItems(rawItems); const pageLocation = sanitizeString(input.pageLocation, MAX_URL_LENGTH) ?? getDefaultLocation(); const pageReferrer = sanitizeString(input.pageReferrer, MAX_URL_LENGTH) ?? getDefaultReferrer(); const payload: OrderRequestPayload = { order_id: orderId, revenue: roundCurrency(revenue), currency, ...(identityPayload ?? {}), ...(normalizedItems ? { items: normalizedItems } : {}), ...(pageLocation ? { page_location: pageLocation } : {}), ...(pageReferrer ? { page_referrer: pageReferrer } : {}), }; return payload; }; export type { TrackOrderPayload as OrderPayload, TrackOrderItemInput as OrderItemPayload };