/** * Orders Domain - WHMCS Provider Mapper * * Transforms normalized order data to WHMCS API format. */ import type { FulfillmentOrderItem } from "../../contract"; import { type WhmcsOrderItem, type WhmcsAddOrderParams, type WhmcsAddOrderPayload, whmcsOrderItemSchema, } from "./raw.types"; import { z } from "zod"; const fulfillmentOrderItemSchema = z.object({ id: z.string(), orderId: z.string(), quantity: z.number().int().min(1), product: z .object({ id: z.string().optional(), sku: z.string().optional(), itemClass: z.string().optional(), whmcsProductId: z.string().min(1), billingCycle: z.string().min(1), }) .nullable(), }); export interface OrderItemMappingResult { whmcsItems: WhmcsOrderItem[]; summary: { totalItems: number; serviceItems: number; activationItems: number; }; } function normalizeBillingCycle(cycle: string): WhmcsOrderItem["billingCycle"] { const normalized = cycle.trim().toLowerCase(); if (normalized.includes("monthly")) return "monthly"; if (normalized.includes("one")) return "onetime"; if (normalized.includes("annual")) return "annually"; if (normalized.includes("quarter")) return "quarterly"; // Default to monthly if unrecognized return "monthly"; } /** * Map a single fulfillment order item to WHMCS format */ export function mapFulfillmentOrderItem( item: FulfillmentOrderItem, index = 0 ): WhmcsOrderItem { const parsed = fulfillmentOrderItemSchema.parse(item); if (!parsed.product) { throw new Error(`Order item ${index} missing product information`); } const whmcsItem: WhmcsOrderItem = { productId: parsed.product.whmcsProductId, billingCycle: normalizeBillingCycle(parsed.product.billingCycle), quantity: parsed.quantity, }; return whmcsItem; } /** * Map multiple fulfillment order items to WHMCS format */ export function mapFulfillmentOrderItems( items: FulfillmentOrderItem[] ): OrderItemMappingResult { if (!Array.isArray(items) || items.length === 0) { throw new Error("No order items provided for WHMCS mapping"); } const whmcsItems: WhmcsOrderItem[] = []; let serviceItems = 0; let activationItems = 0; items.forEach((item, index) => { const mapped = mapFulfillmentOrderItem(item, index); whmcsItems.push(mapped); if (mapped.billingCycle === "monthly") { serviceItems++; } else if (mapped.billingCycle === "onetime") { activationItems++; } }); return { whmcsItems, summary: { totalItems: whmcsItems.length, serviceItems, activationItems, }, }; } /** * Build WHMCS AddOrder API payload from parameters * Converts structured params into WHMCS API array format */ export function buildWhmcsAddOrderPayload(params: WhmcsAddOrderParams): WhmcsAddOrderPayload { const pids: string[] = []; const billingCycles: string[] = []; const quantities: number[] = []; const configOptions: string[] = []; const customFields: string[] = []; params.items.forEach(item => { pids.push(item.productId); billingCycles.push(item.billingCycle); quantities.push(item.quantity); // Handle config options - WHMCS expects base64 encoded serialized arrays if (item.configOptions && Object.keys(item.configOptions).length > 0) { const serialized = serializeForWhmcs(item.configOptions); configOptions.push(serialized); } else { configOptions.push(""); // Empty string for items without config options } // Handle custom fields - WHMCS expects base64 encoded serialized arrays if (item.customFields && Object.keys(item.customFields).length > 0) { const serialized = serializeForWhmcs(item.customFields); customFields.push(serialized); } else { customFields.push(""); // Empty string for items without custom fields } }); const payload: WhmcsAddOrderPayload = { clientid: params.clientId, paymentmethod: params.paymentMethod, pid: pids, billingcycle: billingCycles, qty: quantities, }; // Add optional fields if (params.promoCode) { payload.promocode = params.promoCode; } if (params.noinvoice !== undefined) { payload.noinvoice = params.noinvoice; } if (params.noinvoiceemail !== undefined) { payload.noinvoiceemail = params.noinvoiceemail; } if (params.noemail !== undefined) { payload.noemail = params.noemail; } if (configOptions.some(opt => opt !== "")) { payload.configoptions = configOptions; } if (customFields.some(field => field !== "")) { payload.customfields = customFields; } return payload; } /** * Serialize object for WHMCS API * WHMCS expects base64-encoded serialized data */ function serializeForWhmcs(data: Record): string { const jsonStr = JSON.stringify(data); return Buffer.from(jsonStr).toString("base64"); } /** * Create order notes with Salesforce tracking information */ export function createOrderNotes(sfOrderId: string, additionalNotes?: string): string { const notes: string[] = []; // Always include Salesforce Order ID for tracking notes.push(`sfOrderId=${sfOrderId}`); // Add provisioning timestamp notes.push(`provisionedAt=${new Date().toISOString()}`); // Add additional notes if provided if (additionalNotes) { notes.push(additionalNotes); } return notes.join("; "); }