2025-12-26 17:36:06 +09:00
|
|
|
/**
|
|
|
|
|
* WHMCS Custom Field Utilities (domain-internal)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
const isObject = (value: unknown): value is Record<string, unknown> =>
|
|
|
|
|
typeof value === "object" && value !== null;
|
|
|
|
|
|
|
|
|
|
const normalizeCustomFieldEntries = (value: unknown): Array<Record<string, unknown>> => {
|
|
|
|
|
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 [];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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<string, string> {
|
|
|
|
|
if (!customFields) return {};
|
|
|
|
|
|
|
|
|
|
if (isObject(customFields) && !Array.isArray(customFields) && !("customfield" in customFields)) {
|
|
|
|
|
return Object.entries(customFields).reduce<Record<string, string>>((acc, [key, value]) => {
|
|
|
|
|
if (typeof value === "string") {
|
|
|
|
|
const trimmedKey = key.trim();
|
|
|
|
|
if (trimmedKey) acc[trimmedKey] = value;
|
|
|
|
|
}
|
|
|
|
|
return acc;
|
|
|
|
|
}, {});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const map: Record<string, string> = {};
|
|
|
|
|
for (const entry of normalizeCustomFieldEntries(customFields)) {
|
2026-01-15 11:28:25 +09:00
|
|
|
const idRaw = "id" in entry ? entry["id"] : undefined;
|
2025-12-26 17:36:06 +09:00
|
|
|
const id =
|
|
|
|
|
typeof idRaw === "string"
|
|
|
|
|
? idRaw.trim()
|
|
|
|
|
: typeof idRaw === "number"
|
|
|
|
|
? String(idRaw)
|
|
|
|
|
: undefined;
|
2026-01-15 11:28:25 +09:00
|
|
|
const nameRaw = "name" in entry ? entry["name"] : undefined;
|
|
|
|
|
const name = typeof nameRaw === "string" ? nameRaw.trim() : undefined;
|
|
|
|
|
const rawValue = "value" in entry ? entry["value"] : undefined;
|
2025-12-26 17:36:06 +09:00
|
|
|
if (rawValue === undefined || rawValue === null) continue;
|
|
|
|
|
const value =
|
|
|
|
|
typeof rawValue === "string"
|
|
|
|
|
? rawValue
|
|
|
|
|
: typeof rawValue === "number" || typeof rawValue === "boolean"
|
|
|
|
|
? String(rawValue)
|
|
|
|
|
: undefined;
|
|
|
|
|
if (!value) continue;
|
|
|
|
|
|
|
|
|
|
if (id) map[id] = value;
|
|
|
|
|
if (name) map[name] = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|