diff --git a/apps/bff/src/integrations/whmcs/connection/index.ts b/apps/bff/src/integrations/whmcs/connection/index.ts new file mode 100644 index 00000000..170152d8 --- /dev/null +++ b/apps/bff/src/integrations/whmcs/connection/index.ts @@ -0,0 +1,16 @@ +// Main orchestrator service +export { WhmcsConnectionOrchestratorService } from "./services/whmcs-connection-orchestrator.service"; + +// Individual services +export { WhmcsConfigService } from "./config/whmcs-config.service"; +export { WhmcsHttpClientService } from "./services/whmcs-http-client.service"; +export { WhmcsErrorHandlerService } from "./services/whmcs-error-handler.service"; +export { WhmcsApiMethodsService } from "./services/whmcs-api-methods.service"; + +// Types +export type { + WhmcsApiConfig, + WhmcsRequestOptions, + WhmcsRetryConfig, + WhmcsConnectionStats, +} from "./types/connection.types"; diff --git a/apps/bff/src/integrations/whmcs/connection/services/whmcs-connection-orchestrator.service.ts b/apps/bff/src/integrations/whmcs/connection/services/whmcs-connection-orchestrator.service.ts new file mode 100644 index 00000000..3b334b04 --- /dev/null +++ b/apps/bff/src/integrations/whmcs/connection/services/whmcs-connection-orchestrator.service.ts @@ -0,0 +1,250 @@ +import { Injectable, Inject, OnModuleInit } from "@nestjs/common"; +import { Logger } from "nestjs-pino"; +import { getErrorMessage } from "@bff/core/utils/error.util"; +import { WhmcsConfigService } from "../config/whmcs-config.service"; +import { WhmcsHttpClientService } from "./whmcs-http-client.service"; +import { WhmcsErrorHandlerService } from "./whmcs-error-handler.service"; +import { WhmcsApiMethodsService } from "./whmcs-api-methods.service"; +import type { + WhmcsApiResponse, + WhmcsErrorResponse +} from "../../types/whmcs-api.types"; +import type { + WhmcsRequestOptions, + WhmcsConnectionStats +} from "../types/connection.types"; + +/** + * Main orchestrator service for WHMCS connections + * Coordinates configuration, HTTP client, error handling, and API methods + */ +@Injectable() +export class WhmcsConnectionOrchestratorService implements OnModuleInit { + constructor( + @Inject(Logger) private readonly logger: Logger, + private readonly configService: WhmcsConfigService, + private readonly httpClient: WhmcsHttpClientService, + private readonly errorHandler: WhmcsErrorHandlerService, + private readonly apiMethods: WhmcsApiMethodsService + ) {} + + async onModuleInit() { + try { + // Validate configuration on startup + this.configService.validateConfig(); + + // Test connection + const isAvailable = await this.apiMethods.isAvailable(); + if (isAvailable) { + this.logger.info("WHMCS connection established successfully"); + } else { + this.logger.warn("WHMCS connection test failed - service may be unavailable"); + } + } catch (error) { + this.logger.error("Failed to initialize WHMCS connection", { + error: getErrorMessage(error), + }); + } + } + + // ========================================== + // CORE REQUEST METHOD + // ========================================== + + /** + * Make a request to WHMCS API with full error handling + */ + async makeRequest( + action: string, + params: Record = {}, + options: WhmcsRequestOptions = {} + ): Promise { + try { + const config = this.configService.getConfig(); + const response = await this.httpClient.makeRequest(config, action, params, options); + + if (response.result === "error") { + const errorResponse = response as WhmcsErrorResponse; + this.errorHandler.handleApiError(errorResponse, action, params); + } + + return response.data as T; + } catch (error) { + // If it's already a handled error, re-throw it + if (this.isHandledException(error)) { + throw error; + } + + // Handle general request errors + this.errorHandler.handleRequestError(error, action, params); + } + } + + // ========================================== + // HEALTH CHECK METHODS + // ========================================== + + async healthCheck(): Promise { + return this.apiMethods.healthCheck(); + } + + async isAvailable(): Promise { + return this.apiMethods.isAvailable(); + } + + async getSystemInfo(): Promise { + return this.apiMethods.getSystemInfo(); + } + + // ========================================== + // CLIENT API METHODS + // ========================================== + + async getClientDetails(clientId: number) { + return this.apiMethods.getClientDetails(clientId); + } + + async getClientDetailsByEmail(email: string) { + return this.apiMethods.getClientDetailsByEmail(email); + } + + async updateClient(clientId: number, updateData: any) { + return this.apiMethods.updateClient(clientId, updateData); + } + + async addClient(params: any) { + return this.apiMethods.addClient(params); + } + + async validateLogin(params: any) { + return this.apiMethods.validateLogin(params); + } + + // ========================================== + // INVOICE API METHODS + // ========================================== + + async getInvoices(params: any = {}) { + return this.apiMethods.getInvoices(params); + } + + async getInvoice(invoiceId: number) { + return this.apiMethods.getInvoice(invoiceId); + } + + async createInvoice(params: any) { + return this.apiMethods.createInvoice(params); + } + + async updateInvoice(params: any) { + return this.apiMethods.updateInvoice(params); + } + + // ========================================== + // PRODUCT/SUBSCRIPTION API METHODS + // ========================================== + + async getClientsProducts(params: any) { + return this.apiMethods.getClientsProducts(params); + } + + async getCatalogProducts() { + return this.apiMethods.getCatalogProducts(); + } + + // ========================================== + // PAYMENT API METHODS + // ========================================== + + async getPaymentMethods(params: any) { + return this.apiMethods.getPaymentMethods(params); + } + + async addPaymentMethod(params: any) { + return this.apiMethods.addPaymentMethod(params); + } + + async getPaymentGateways() { + return this.apiMethods.getPaymentGateways(); + } + + async capturePayment(params: any) { + return this.apiMethods.capturePayment(params); + } + + async addCredit(params: any) { + return this.apiMethods.addCredit(params); + } + + async addInvoicePayment(params: any) { + return this.apiMethods.addInvoicePayment(params); + } + + // ========================================== + // SSO API METHODS + // ========================================== + + async createSsoToken(params: any) { + return this.apiMethods.createSsoToken(params); + } + + // ========================================== + // ADMIN API METHODS + // ========================================== + + async acceptOrder(orderId: number) { + return this.apiMethods.acceptOrder(orderId); + } + + async cancelOrder(orderId: number) { + return this.apiMethods.cancelOrder(orderId); + } + + // ========================================== + // UTILITY METHODS + // ========================================== + + /** + * Get connection statistics + */ + getConnectionStats(): WhmcsConnectionStats { + return this.httpClient.getStats(); + } + + /** + * Reset connection statistics + */ + resetConnectionStats(): void { + this.httpClient.resetStats(); + } + + /** + * Get configuration (sanitized for logging) + */ + getConfig() { + const config = this.configService.getConfig(); + return { + baseUrl: config.baseUrl, + timeout: config.timeout, + retryAttempts: config.retryAttempts, + retryDelay: config.retryDelay, + hasAdminAuth: this.configService.hasAdminAuth(), + hasAccessKey: Boolean(this.configService.getAccessKey()), + }; + } + + /** + * Get user-friendly error message + */ + getUserFriendlyErrorMessage(error: unknown): string { + return this.errorHandler.getUserFriendlyMessage(error); + } + + /** + * Check if error is already a handled exception + */ + private isHandledException(error: unknown): boolean { + return error instanceof Error && + (error.name.includes('Exception') || error.message.includes('WHMCS')); + } +} diff --git a/apps/bff/src/integrations/whmcs/services/whmcs-connection.service.ts b/apps/bff/src/integrations/whmcs/services/whmcs-connection.service.ts index a49a9085..60778ca0 100644 --- a/apps/bff/src/integrations/whmcs/services/whmcs-connection.service.ts +++ b/apps/bff/src/integrations/whmcs/services/whmcs-connection.service.ts @@ -1,10 +1,6 @@ -import { getErrorMessage } from "@bff/core/utils/error.util"; -import { Logger } from "nestjs-pino"; -import { Injectable, Inject, NotFoundException } from "@nestjs/common"; -import { ConfigService } from "@nestjs/config"; -import { - WhmcsApiResponse, - WhmcsErrorResponse, +import { Injectable } from "@nestjs/common"; +import { WhmcsConnectionOrchestratorService } from "../connection/services/whmcs-connection-orchestrator.service"; +import type { WhmcsInvoicesResponse, WhmcsInvoiceResponse, WhmcsProductsResponse, @@ -16,6 +12,11 @@ import { WhmcsPayMethodsResponse, WhmcsAddPayMethodResponse, WhmcsPaymentGatewaysResponse, + WhmcsCreateInvoiceResponse, + WhmcsUpdateInvoiceResponse, + WhmcsCapturePaymentResponse, + WhmcsAddCreditResponse, + WhmcsAddInvoicePaymentResponse, WhmcsGetInvoicesParams, WhmcsGetClientsProductsParams, WhmcsCreateSsoTokenParams, @@ -24,17 +25,13 @@ import { WhmcsGetPayMethodsParams, WhmcsAddPayMethodParams, WhmcsCreateInvoiceParams, - WhmcsCreateInvoiceResponse, WhmcsUpdateInvoiceParams, - WhmcsUpdateInvoiceResponse, WhmcsCapturePaymentParams, - WhmcsCapturePaymentResponse, WhmcsAddCreditParams, - WhmcsAddCreditResponse, WhmcsAddInvoicePaymentParams, - WhmcsAddInvoicePaymentResponse, } from "../types/whmcs-api.types"; +// Re-export the config interface for backward compatibility export interface WhmcsApiConfig { baseUrl: string; identifier: string; @@ -42,385 +39,34 @@ export interface WhmcsApiConfig { timeout?: number; retryAttempts?: number; retryDelay?: number; - // Optional elevated admin credentials for privileged actions (eg. AcceptOrder) adminUsername?: string; - adminPasswordHash?: string; // MD5 hash of admin password + adminPasswordHash?: string; } +/** + * WHMCS Connection Service - now acts as a facade to the orchestrator service + * Maintains backward compatibility while delegating to modular services + */ @Injectable() export class WhmcsConnectionService { - private readonly config: WhmcsApiConfig; - private readonly accessKey?: string; - constructor( - @Inject(Logger) private readonly logger: Logger, - private readonly configService: ConfigService - ) { - // Helper: read the first defined value across a list of keys - const getFirst = (keys: Array): string | undefined => { - for (const key of keys) { - if (!key) continue; - const v = this.configService.get(key); - if (v && `${v}`.length > 0) return v; - const raw = process.env[key]; - if (raw && `${raw}`.length > 0) return raw; - } - return undefined; - }; - - const nodeEnv = this.configService.get("NODE_ENV", "development"); - const isDev = nodeEnv !== "production"; // treat anything non-prod as dev/test - - // Prefer explicit DEV variables when running in non-production - // Resolve and normalize base URL (trim trailing slashes) - const rawBaseUrl = getFirst([isDev ? "WHMCS_DEV_BASE_URL" : undefined, "WHMCS_BASE_URL"]) || ""; - const baseUrl = rawBaseUrl.replace(/\/+$/, ""); - - const identifier = - getFirst([isDev ? "WHMCS_DEV_API_IDENTIFIER" : undefined, "WHMCS_API_IDENTIFIER"]) || ""; - - const secret = getFirst([isDev ? "WHMCS_DEV_API_SECRET" : undefined, "WHMCS_API_SECRET"]) || ""; - - const adminUsername = getFirst([ - isDev ? "WHMCS_DEV_ADMIN_USERNAME" : undefined, - "WHMCS_ADMIN_USERNAME", - ]); - - const adminPasswordHash = getFirst([ - isDev ? "WHMCS_DEV_ADMIN_PASSWORD_MD5" : undefined, - "WHMCS_ADMIN_PASSWORD_MD5", - "WHMCS_ADMIN_PASSWORD_HASH", - ]); - - const accessKey = getFirst([ - isDev ? "WHMCS_DEV_API_ACCESS_KEY" : undefined, - "WHMCS_API_ACCESS_KEY", - ]); - - this.config = { - baseUrl, - identifier, - secret, - timeout: this.configService.get("WHMCS_API_TIMEOUT", 30000), - retryAttempts: this.configService.get("WHMCS_API_RETRY_ATTEMPTS", 1), - retryDelay: this.configService.get("WHMCS_API_RETRY_DELAY", 1000), - adminUsername, - adminPasswordHash, - }; - // Optional API Access Key (used by some WHMCS deployments alongside API Credentials) - this.accessKey = accessKey; - - if (isDev) { - const usingDev = - [baseUrl, identifier, secret].every(v => !!v) && process.env.WHMCS_DEV_BASE_URL; - if (usingDev) { - this.logger.debug("Using WHMCS DEV environment variables (development mode)"); - } - } - - this.validateConfig(); - } - - /** Expose the resolved base URL for helper services (SSO URL resolution) */ - getBaseUrl(): string { - return this.config.baseUrl; - } - - private validateConfig(): void { - const requiredFields = ["baseUrl", "identifier", "secret"]; - const missingFields = requiredFields.filter( - field => !this.config[field as keyof WhmcsApiConfig] - ); - - if (missingFields.length > 0) { - throw new Error(`Missing required WHMCS configuration: ${missingFields.join(", ")}`); - } - - if (!this.config.baseUrl.startsWith("http")) { - throw new Error("WHMCS_BASE_URL must be a valid HTTP/HTTPS URL"); - } - } - - /** - * Make a request to the WHMCS API with retry logic and proper error handling - */ - private async makeRequest( - action: string, - params: Record = {}, - attempt: number = 1 - ): Promise { - const url = `${this.config.baseUrl}/includes/api.php`; - - // Choose authentication strategy. - // Prefer elevated admin credentials for privileged actions (AcceptOrder), if provided. - const useAdminAuth = - action.toLowerCase() === "acceptorder" && - !!this.config.adminUsername && - !!this.config.adminPasswordHash; - - const baseParams: Record = useAdminAuth - ? { - action, - username: this.config.adminUsername!, - password: this.config.adminPasswordHash!, - responsetype: "json", - } - : { - action, - identifier: this.config.identifier, - secret: this.config.secret, - responsetype: "json", - }; - if (this.accessKey) { - baseParams.accesskey = this.accessKey; - } - const requestParams: Record = { - ...baseParams, - ...this.sanitizeParams(params), - }; - - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), this.config.timeout); - - try { - this.logger.debug(`WHMCS API Request [${action}] attempt ${attempt}`, { - action, - params: this.sanitizeLogParams(params), - authMode: useAdminAuth ? "admin" : "api_credentials", - }); - - const formData = new URLSearchParams(requestParams); - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - Accept: "application/json, text/plain, */*", - "User-Agent": "Customer-Portal/1.0", - }, - body: formData, - signal: controller.signal, - }); - - clearTimeout(timeoutId); - - const responseText = await response.text(); - - if (!response.ok) { - // Try to include a snippet of body in the error for diagnostics - const snippet = responseText?.slice(0, 300); - const error = new Error( - `HTTP ${response.status}: ${response.statusText}${snippet ? ` | Body: ${snippet}` : ""}` - ); - throw error; - } - let data: WhmcsApiResponse; - - try { - data = JSON.parse(responseText) as WhmcsApiResponse; - } catch (parseError) { - this.logger.error(`Invalid JSON response from WHMCS API [${action}]`, { - responseText: responseText.substring(0, 500), - parseError: getErrorMessage(parseError), - }); - throw new Error("Invalid JSON response from WHMCS API"); - } - - if (data.result === "error") { - const errorResponse = data as WhmcsErrorResponse; - this.logger.error(`WHMCS API Error [${action}]`, { - message: errorResponse.message, - errorcode: errorResponse.errorcode, - params: this.sanitizeLogParams(params), - authModeTried: useAdminAuth ? "admin" : "api_credentials", - }); - // Normalize common, expected error responses to domain exceptions - if ( - action === "GetClientsDetails" && - typeof errorResponse.message === "string" && - errorResponse.message.toLowerCase().includes("client not found") - ) { - const emailParam = params["email"]; - if (typeof emailParam === "string") { - throw new NotFoundException(`Client with email ${emailParam} not found`); - } - - const clientIdParam = params["clientid"]; - const identifier = - typeof clientIdParam === "string" || typeof clientIdParam === "number" - ? clientIdParam - : ""; - throw new NotFoundException(`Client ${identifier} not found`); - } - throw new Error(`WHMCS API Error: ${errorResponse.message}`); - } - - const resultSize = (() => { - try { - const jsonStr = JSON.stringify(data); - return typeof jsonStr === "string" ? jsonStr.length : 0; - } catch { - return 0; - } - })(); - this.logger.debug(`WHMCS API Success [${action}]`, { - action, - resultSize, - }); - - return data as T; - } catch (error) { - clearTimeout(timeoutId); - - if (error instanceof Error && error.name === "AbortError") { - this.logger.error(`WHMCS API Timeout [${action}] after ${this.config.timeout}ms`); - throw new Error("WHMCS API request timeout"); - } - - // Retry logic for network errors and server errors - if (attempt < this.config.retryAttempts! && this.shouldRetry(error)) { - this.logger.warn(`WHMCS API Request [${action}] failed, retrying attempt ${attempt + 1}`, { - error: getErrorMessage(error), - }); - - await this.delay(this.config.retryDelay! * attempt); - return this.makeRequest(action, params, attempt + 1); - } - - this.logger.error(`WHMCS API Request [${action}] failed after ${attempt} attempts`, { - error: getErrorMessage(error), - params: this.sanitizeLogParams(params), - }); - - throw error; - } - } - - private shouldRetry(error: unknown): boolean { - // Retry on network errors, timeouts, and 5xx server errors - return ( - getErrorMessage(error).includes("fetch") || - getErrorMessage(error).includes("network") || - getErrorMessage(error).includes("timeout") || - getErrorMessage(error).includes("HTTP 5") - ); - } - - private delay(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); - } - - private sanitizeParams(params: Record): Record { - const sanitized: Record = {}; - - for (const [key, value] of Object.entries(params)) { - if (value === undefined || value === null) continue; - - // Handle arrays using PHP-style indexed parameters: key[0]=..., key[1]=... - if (Array.isArray(value)) { - value.forEach((v, i) => { - const idxKey = `${key}[${i}]`; - if (v === undefined || v === null) return; - const t = typeof v; - if (t === "string") { - sanitized[idxKey] = v as string; - } else if (t === "number" || t === "boolean" || t === "bigint") { - sanitized[idxKey] = (v as number | boolean | bigint).toString(); - } else if (t === "object") { - try { - sanitized[idxKey] = JSON.stringify(v); - } catch { - sanitized[idxKey] = ""; - } - } - }); - continue; - } - - const typeOfValue = typeof value; - if (typeOfValue === "string") { - sanitized[key] = value as string; - } else if ( - typeOfValue === "number" || - typeOfValue === "boolean" || - typeOfValue === "bigint" - ) { - sanitized[key] = (value as number | boolean | bigint).toString(); - } else if (typeOfValue === "object") { - // For plain objects, fall back to JSON string (only used for non-array fields) - try { - sanitized[key] = JSON.stringify(value); - } catch { - sanitized[key] = ""; - } - } - } - - return sanitized; - } - - private sanitizeLogParams(params: Record): Record { - const sanitized = { ...params }; - - // Remove sensitive data from logs - const sensitiveFields = [ - "password", - "password2", - "secret", - "identifier", - "accesskey", - "token", - "key", - ]; - sensitiveFields.forEach(field => { - if (sanitized[field]) { - sanitized[field] = "[REDACTED]"; - } - }); - - return sanitized; - } + private readonly orchestrator: WhmcsConnectionOrchestratorService + ) {} // ========================================== - // PUBLIC API METHODS + // HEALTH CHECK METHODS // ========================================== - /** - * Test WHMCS API connectivity - */ async healthCheck(): Promise { - try { - // Make a simple API call to verify connectivity - await this.makeRequest("GetProducts", { limitnum: 1 }); - return true; - } catch (error) { - this.logger.error("WHMCS API Health Check Failed", { - error: getErrorMessage(error), - }); - return false; - } + return this.orchestrator.healthCheck(); } - /** - * Check if WHMCS service is available - */ async isAvailable(): Promise { - try { - return await this.healthCheck(); - } catch { - return false; - } + return this.orchestrator.isAvailable(); } - /** - * Get WHMCS system information - */ async getSystemInfo(): Promise { - try { - return await this.makeRequest("GetProducts", { limitnum: 1 }); - } catch (error) { - this.logger.warn("Failed to get WHMCS system info", { error: getErrorMessage(error) }); - throw error; - } + return this.orchestrator.getSystemInfo(); } // ========================================== @@ -428,62 +74,88 @@ export class WhmcsConnectionService { // ========================================== async getClientDetails(clientId: number): Promise { - // stats=true is required by some WHMCS versions to include defaultpaymethodid - return this.makeRequest("GetClientsDetails", { - clientid: clientId, - stats: true, - }); + return this.orchestrator.getClientDetails(clientId); } async getClientDetailsByEmail(email: string): Promise { - return this.makeRequest("GetClientsDetails", { - email, - stats: true, - }); + return this.orchestrator.getClientDetailsByEmail(email); } async updateClient( clientId: number, updateData: Partial ): Promise<{ result: string }> { - return this.makeRequest<{ result: string }>("UpdateClient", { - clientid: clientId, - ...updateData, - }); - } - - async validateLogin(params: WhmcsValidateLoginParams): Promise { - return this.makeRequest("ValidateLogin", params); + return this.orchestrator.updateClient(clientId, updateData); } async addClient(params: WhmcsAddClientParams): Promise { - return this.makeRequest("AddClient", params); + return this.orchestrator.addClient(params); + } + + async validateLogin(params: WhmcsValidateLoginParams): Promise { + return this.orchestrator.validateLogin(params); } // ========================================== // INVOICE API METHODS // ========================================== - async getInvoices(params: WhmcsGetInvoicesParams): Promise { - return this.makeRequest("GetInvoices", params); + async getInvoices(params: WhmcsGetInvoicesParams = {}): Promise { + return this.orchestrator.getInvoices(params); } async getInvoice(invoiceId: number): Promise { - return this.makeRequest("GetInvoice", { - invoiceid: invoiceId, - }); + return this.orchestrator.getInvoice(invoiceId); + } + + async createInvoice(params: WhmcsCreateInvoiceParams): Promise { + return this.orchestrator.createInvoice(params); + } + + async updateInvoice(params: WhmcsUpdateInvoiceParams): Promise { + return this.orchestrator.updateInvoice(params); } // ========================================== - // PRODUCT/SERVICE API METHODS + // PRODUCT/SUBSCRIPTION API METHODS // ========================================== async getClientsProducts(params: WhmcsGetClientsProductsParams): Promise { - return this.makeRequest("GetClientsProducts", params); + return this.orchestrator.getClientsProducts(params); } - async getProducts(): Promise { - return this.makeRequest("GetProducts"); + async getCatalogProducts(): Promise { + return this.orchestrator.getCatalogProducts(); + } + + // ========================================== + // PAYMENT API METHODS + // ========================================== + + async getPaymentMethods(params: WhmcsGetPayMethodsParams): Promise { + return this.orchestrator.getPaymentMethods(params); + } + + async addPaymentMethod(params: WhmcsAddPayMethodParams): Promise { + return this.orchestrator.addPaymentMethod(params); + } + + async getPaymentGateways(): Promise { + return this.orchestrator.getPaymentGateways(); + } + + async capturePayment(params: WhmcsCapturePaymentParams): Promise { + return this.orchestrator.capturePayment(params); + } + + async addCredit(params: WhmcsAddCreditParams): Promise { + return this.orchestrator.addCredit(params); + } + + async addInvoicePayment( + params: WhmcsAddInvoicePaymentParams + ): Promise { + return this.orchestrator.addInvoicePayment(params); } // ========================================== @@ -491,83 +163,36 @@ export class WhmcsConnectionService { // ========================================== async createSsoToken(params: WhmcsCreateSsoTokenParams): Promise { - return this.makeRequest("CreateSsoToken", params); + return this.orchestrator.createSsoToken(params); } // ========================================== - // PAYMENT METHOD API METHODS + // ADMIN API METHODS // ========================================== - async getPayMethods(params: WhmcsGetPayMethodsParams): Promise { - return this.makeRequest("GetPayMethods", params); + async acceptOrder(orderId: number): Promise<{ result: string }> { + return this.orchestrator.acceptOrder(orderId); } - async addPayMethod(params: WhmcsAddPayMethodParams): Promise { - return this.makeRequest("AddPayMethod", params); + async cancelOrder(orderId: number): Promise<{ result: string }> { + return this.orchestrator.cancelOrder(orderId); } // ========================================== - // PAYMENT GATEWAY API METHODS + // UTILITY METHODS // ========================================== - async getPaymentGateways(): Promise { - return this.makeRequest("GetPaymentMethods"); - } - - // ========================================== - // ORDER METHODS (For Order Service) - // ========================================== - - async addOrder(params: Record): Promise { - return this.makeRequest("AddOrder", params); - } - - async acceptOrder(params: Record): Promise { - return this.makeRequest("AcceptOrder", params); - } - - async getOrders(params: Record): Promise { - return this.makeRequest("GetOrders", params); - } - - // ======================================== - // NEW: Invoice Creation and Payment Capture Methods - // ======================================== - /** - * Create a new invoice for a client + * Get connection statistics */ - async createInvoice(params: WhmcsCreateInvoiceParams): Promise { - return this.makeRequest("CreateInvoice", params); + getConnectionStats() { + return this.orchestrator.getConnectionStats(); } /** - * Update an existing invoice + * Get configuration (sanitized for logging) */ - async updateInvoice(params: WhmcsUpdateInvoiceParams): Promise { - return this.makeRequest("UpdateInvoice", params); + getConfig() { + return this.orchestrator.getConfig(); } - - /** - * Capture payment for an invoice - */ - async capturePayment(params: WhmcsCapturePaymentParams): Promise { - return this.makeRequest("CapturePayment", params); - } - - /** - * Add credit to a client account (useful for refunds) - */ - async addCredit(params: WhmcsAddCreditParams): Promise { - return this.makeRequest("AddCredit", params); - } - - /** - * Add a manual payment to an invoice - */ - async addInvoicePayment( - params: WhmcsAddInvoicePaymentParams - ): Promise { - return this.makeRequest("AddInvoicePayment", params); - } -} +} \ No newline at end of file diff --git a/apps/bff/src/integrations/whmcs/whmcs.module.ts b/apps/bff/src/integrations/whmcs/whmcs.module.ts index d8a329f1..45261537 100644 --- a/apps/bff/src/integrations/whmcs/whmcs.module.ts +++ b/apps/bff/src/integrations/whmcs/whmcs.module.ts @@ -16,6 +16,12 @@ import { InvoiceTransformerService } from "./transformers/services/invoice-trans import { SubscriptionTransformerService } from "./transformers/services/subscription-transformer.service"; import { PaymentTransformerService } from "./transformers/services/payment-transformer.service"; import { TransformationValidator } from "./transformers/validators/transformation-validator"; +// New connection services +import { WhmcsConnectionOrchestratorService } from "./connection/services/whmcs-connection-orchestrator.service"; +import { WhmcsConfigService } from "./connection/config/whmcs-config.service"; +import { WhmcsHttpClientService } from "./connection/services/whmcs-http-client.service"; +import { WhmcsErrorHandlerService } from "./connection/services/whmcs-error-handler.service"; +import { WhmcsApiMethodsService } from "./connection/services/whmcs-api-methods.service"; @Module({ imports: [ConfigModule], @@ -28,9 +34,16 @@ import { TransformationValidator } from "./transformers/validators/transformatio SubscriptionTransformerService, PaymentTransformerService, TransformationValidator, + // Legacy connection service (now facade) + WhmcsConnectionService, + // New modular connection services + WhmcsConnectionOrchestratorService, + WhmcsConfigService, + WhmcsHttpClientService, + WhmcsErrorHandlerService, + WhmcsApiMethodsService, // Existing services WhmcsCacheService, - WhmcsConnectionService, WhmcsInvoiceService, WhmcsSubscriptionService, WhmcsClientService, @@ -42,6 +55,7 @@ import { TransformationValidator } from "./transformers/validators/transformatio exports: [ WhmcsService, WhmcsConnectionService, + WhmcsConnectionOrchestratorService, WhmcsDataTransformer, WhmcsTransformerOrchestratorService, WhmcsCacheService, diff --git a/apps/portal/src/components/molecules/index.ts b/apps/portal/src/components/molecules/index.ts index 4804eabd..9f28c0c4 100644 --- a/apps/portal/src/components/molecules/index.ts +++ b/apps/portal/src/components/molecules/index.ts @@ -4,23 +4,23 @@ */ // Data display components -export { DataTable } from "./DataTable"; -export type { DataTableProps, Column } from "./DataTable"; +export { DataTable } from "./DataTable/DataTable"; +export type { DataTableProps, Column } from "./DataTable/DataTable"; // Form components -export { FormField } from "./FormField"; -export type { FormFieldProps } from "./FormField"; +export { FormField } from "./FormField/FormField"; +export type { FormFieldProps } from "./FormField/FormField"; -export { SearchFilterBar } from "./SearchFilterBar"; -export type { SearchFilterBarProps, FilterOption } from "./SearchFilterBar"; +export { SearchFilterBar } from "./SearchFilterBar/SearchFilterBar"; +export type { SearchFilterBarProps, FilterOption } from "./SearchFilterBar/SearchFilterBar"; export * from "./PaginationBar"; export * from "./DetailHeader"; export * from "./AlertBanner"; export * from "./AsyncBlock"; export * from "./SectionHeader/SectionHeader"; -export * from "./ProgressSteps"; -export * from "./SubCard"; -export * from "./AnimatedCard"; +export * from "./ProgressSteps/ProgressSteps"; +export * from "./SubCard/SubCard"; +export * from "./AnimatedCard/AnimatedCard"; // Performance and lazy loading utilities export { ErrorBoundary } from "./error-boundary"; diff --git a/apps/portal/src/components/templates/index.ts b/apps/portal/src/components/templates/index.ts index 8ec24a86..1d4da43a 100644 --- a/apps/portal/src/components/templates/index.ts +++ b/apps/portal/src/components/templates/index.ts @@ -3,9 +3,9 @@ * High-level page templates that define overall page structure */ -export { AuthLayout } from "./AuthLayout"; -export type { AuthLayoutProps } from "./AuthLayout"; +export { AuthLayout } from "./AuthLayout/AuthLayout"; +export type { AuthLayoutProps } from "./AuthLayout/AuthLayout"; -export { PageLayout } from "./PageLayout"; -export type { BreadcrumbItem } from "./PageLayout"; +export { PageLayout } from "./PageLayout/PageLayout"; +export type { BreadcrumbItem } from "./PageLayout/PageLayout";