/** * WHMCS Custom Field Utilities (domain-internal) */ const isObject = (value: unknown): value is Record => typeof value === "object" && value !== null; const normalizeCustomFieldEntries = (value: unknown): Array> => { if (Array.isArray(value)) return value.filter(isObject); if (isObject(value) && "customfield" in value) { const custom = (value as { customfield?: unknown }).customfield; if (Array.isArray(custom)) return custom.filter(isObject); if (isObject(custom)) return [custom]; return []; } return []; }; /** * Convert raw id value (string or number) to trimmed string key */ const toIdKey = (raw: unknown): string | undefined => { if (typeof raw === "string") return raw.trim() || undefined; if (typeof raw === "number") return String(raw); return undefined; }; /** * Convert raw value to string (handles string, number, boolean) */ const toStringValue = (raw: unknown): string | undefined => { if (raw === undefined || raw === null) return undefined; if (typeof raw === "string") return raw; if (typeof raw === "number" || typeof raw === "boolean") return String(raw); return undefined; }; /** * Process a plain object as a field map (key-value pairs) */ const processPlainObjectFields = (obj: Record): Record => { const result: Record = {}; for (const [key, value] of Object.entries(obj)) { if (typeof value === "string") { const trimmedKey = key.trim(); if (trimmedKey) result[trimmedKey] = value; } } return result; }; /** * Process a single custom field entry and add to map */ const processCustomFieldEntry = ( entry: Record, map: Record ): void => { const id = toIdKey("id" in entry ? entry["id"] : undefined); const name = typeof entry["name"] === "string" ? entry["name"].trim() : undefined; const value = toStringValue("value" in entry ? entry["value"] : undefined); if (!value) return; if (id) map[id] = value; if (name) map[name] = value; }; /** * Build a lightweight map of WHMCS custom field identifiers to values. * Accepts the documented WHMCS response shapes (array or { customfield }). */ export function getCustomFieldsMap(customFields: unknown): Record { if (!customFields) return {}; // Handle plain object (key-value pairs) if (isObject(customFields) && !Array.isArray(customFields) && !("customfield" in customFields)) { return processPlainObjectFields(customFields); } // Handle array or { customfield } structure const map: Record = {}; for (const entry of normalizeCustomFieldEntries(customFields)) { processCustomFieldEntry(entry, map); } return map; } /** * Retrieve a custom field value by numeric id or name. */ export function getCustomFieldValue( customFields: unknown, key: string | number ): string | undefined { if (key === undefined || key === null) return undefined; const map = getCustomFieldsMap(customFields); const primary = map[String(key)]; if (primary !== undefined) return primary; if (typeof key === "string") { const numeric = Number.parseInt(key, 10); if (!Number.isNaN(numeric)) { const numericValue = map[String(numeric)]; if (numericValue !== undefined) return numericValue; } } return undefined; }