/** * WHMCS Subscriptions Provider - Mapper * * Transforms raw WHMCS product/service data into normalized subscription types. */ import type { Subscription, SubscriptionStatus, SubscriptionCycle } from "../../contract.js"; import { subscriptionSchema } from "../../schema.js"; import { type WhmcsProductRaw, whmcsProductRawSchema, whmcsCustomFieldsContainerSchema, } from "./raw.types.js"; export interface TransformSubscriptionOptions { defaultCurrencyCode?: string; defaultCurrencySymbol?: string; } // Status mapping const STATUS_MAP: Record = { active: "Active", inactive: "Inactive", pending: "Pending", cancelled: "Cancelled", canceled: "Cancelled", terminated: "Terminated", completed: "Completed", suspended: "Suspended", fraud: "Cancelled", }; // Cycle mapping const CYCLE_MAP: Record = { monthly: "Monthly", annually: "Annually", annual: "Annually", yearly: "Annually", quarterly: "Quarterly", "semi annually": "Semi-Annually", semiannually: "Semi-Annually", "semi-annually": "Semi-Annually", biennially: "Biennially", triennially: "Triennially", "one time": "One-time", onetime: "One-time", "one-time": "One-time", "one time fee": "One-time", free: "Free", }; function mapStatus(status?: string | null): SubscriptionStatus { if (!status) return "Cancelled"; const mapped = STATUS_MAP[status.trim().toLowerCase()]; return mapped ?? "Cancelled"; } function mapCycle(cycle?: string | null): SubscriptionCycle { if (!cycle) return "One-time"; const normalized = cycle.trim().toLowerCase().replace(/[_\s-]+/g, " "); return CYCLE_MAP[normalized] ?? "One-time"; } function parseAmount(amount: string | number | undefined): number { if (typeof amount === "number") { return amount; } if (!amount) { return 0; } const cleaned = String(amount).replace(/[^\d.-]/g, ""); const parsed = Number.parseFloat(cleaned); return Number.isNaN(parsed) ? 0 : parsed; } function formatDate(input?: string | null): string | undefined { if (!input) { return undefined; } const date = new Date(input); if (Number.isNaN(date.getTime())) { return undefined; } return date.toISOString(); } function extractCustomFields(raw: unknown): Record | undefined { if (!raw) return undefined; const container = whmcsCustomFieldsContainerSchema.safeParse(raw); if (!container.success) return undefined; const customfield = container.data.customfield; const fieldsArray = Array.isArray(customfield) ? customfield : [customfield]; const entries = fieldsArray.reduce>((acc, field) => { if (field?.name && field.value) { acc[field.name] = field.value; } return acc; }, {}); return Object.keys(entries).length > 0 ? entries : undefined; } /** * Transform raw WHMCS product/service into normalized Subscription */ export function transformWhmcsSubscription( rawProduct: unknown, options: TransformSubscriptionOptions = {} ): Subscription { // Validate raw data const product = whmcsProductRawSchema.parse(rawProduct); // Extract currency info const currency = product.pricing?.currency || options.defaultCurrencyCode || "JPY"; const currencySymbol = product.pricing?.currencyprefix || product.pricing?.currencysuffix || options.defaultCurrencySymbol; // Determine amount const amount = parseAmount( product.amount || product.recurringamount || product.pricing?.amount || product.firstpaymentamount || 0 ); // Transform to domain model const subscription: Subscription = { id: product.id, serviceId: product.serviceid || product.id, productName: product.name || product.translated_name || "Unknown Product", domain: product.domain || undefined, cycle: mapCycle(product.billingcycle), status: mapStatus(product.status), nextDue: formatDate(product.nextduedate || product.nextinvoicedate), amount, currency, currencySymbol, registrationDate: formatDate(product.regdate) || new Date().toISOString(), notes: product.notes || undefined, customFields: extractCustomFields(product.customfields), orderNumber: product.ordernumber || undefined, groupName: product.groupname || product.translated_groupname || undefined, paymentMethod: product.paymentmethodname || product.paymentmethod || undefined, serverName: product.servername || product.serverhostname || undefined, }; // Validate against domain schema return subscriptionSchema.parse(subscription); } /** * Transform multiple WHMCS subscriptions */ export function transformWhmcsSubscriptions( rawProducts: unknown[], options: TransformSubscriptionOptions = {} ): Subscription[] { return rawProducts.map(raw => transformWhmcsSubscription(raw, options)); }