Refactor import paths for components in the portal to enhance module structure and maintainability, ensuring consistent file organization across DataTable, FormField, SearchFilterBar, AuthLayout, and PageLayout.

This commit is contained in:
barsa 2025-09-25 15:15:50 +09:00
parent 5a8e9624ae
commit 3da96d0c84
6 changed files with 381 additions and 476 deletions

View File

@ -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";

View File

@ -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<T>(
action: string,
params: Record<string, unknown> = {},
options: WhmcsRequestOptions = {}
): Promise<T> {
try {
const config = this.configService.getConfig();
const response = await this.httpClient.makeRequest<T>(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<boolean> {
return this.apiMethods.healthCheck();
}
async isAvailable(): Promise<boolean> {
return this.apiMethods.isAvailable();
}
async getSystemInfo(): Promise<unknown> {
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'));
}
}

View File

@ -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>): string | undefined => {
for (const key of keys) {
if (!key) continue;
const v = this.configService.get<string | undefined>(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<string>("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<number>("WHMCS_API_TIMEOUT", 30000),
retryAttempts: this.configService.get<number>("WHMCS_API_RETRY_ATTEMPTS", 1),
retryDelay: this.configService.get<number>("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<T>(
action: string,
params: Record<string, unknown> = {},
attempt: number = 1
): Promise<T> {
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<string, string> = 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<string, string> = {
...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<T>;
try {
data = JSON.parse(responseText) as WhmcsApiResponse<T>;
} 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<T>(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<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
private sanitizeParams(params: Record<string, unknown>): Record<string, string> {
const sanitized: Record<string, string> = {};
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<string, unknown>): Record<string, unknown> {
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<boolean> {
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<boolean> {
try {
return await this.healthCheck();
} catch {
return false;
}
return this.orchestrator.isAvailable();
}
/**
* Get WHMCS system information
*/
async getSystemInfo(): Promise<unknown> {
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<WhmcsClientResponse> {
// stats=true is required by some WHMCS versions to include defaultpaymethodid
return this.makeRequest<WhmcsClientResponse>("GetClientsDetails", {
clientid: clientId,
stats: true,
});
return this.orchestrator.getClientDetails(clientId);
}
async getClientDetailsByEmail(email: string): Promise<WhmcsClientResponse> {
return this.makeRequest<WhmcsClientResponse>("GetClientsDetails", {
email,
stats: true,
});
return this.orchestrator.getClientDetailsByEmail(email);
}
async updateClient(
clientId: number,
updateData: Partial<WhmcsClientResponse["client"]>
): Promise<{ result: string }> {
return this.makeRequest<{ result: string }>("UpdateClient", {
clientid: clientId,
...updateData,
});
}
async validateLogin(params: WhmcsValidateLoginParams): Promise<WhmcsValidateLoginResponse> {
return this.makeRequest<WhmcsValidateLoginResponse>("ValidateLogin", params);
return this.orchestrator.updateClient(clientId, updateData);
}
async addClient(params: WhmcsAddClientParams): Promise<WhmcsAddClientResponse> {
return this.makeRequest<WhmcsAddClientResponse>("AddClient", params);
return this.orchestrator.addClient(params);
}
async validateLogin(params: WhmcsValidateLoginParams): Promise<WhmcsValidateLoginResponse> {
return this.orchestrator.validateLogin(params);
}
// ==========================================
// INVOICE API METHODS
// ==========================================
async getInvoices(params: WhmcsGetInvoicesParams): Promise<WhmcsInvoicesResponse> {
return this.makeRequest<WhmcsInvoicesResponse>("GetInvoices", params);
async getInvoices(params: WhmcsGetInvoicesParams = {}): Promise<WhmcsInvoicesResponse> {
return this.orchestrator.getInvoices(params);
}
async getInvoice(invoiceId: number): Promise<WhmcsInvoiceResponse> {
return this.makeRequest<WhmcsInvoiceResponse>("GetInvoice", {
invoiceid: invoiceId,
});
return this.orchestrator.getInvoice(invoiceId);
}
async createInvoice(params: WhmcsCreateInvoiceParams): Promise<WhmcsCreateInvoiceResponse> {
return this.orchestrator.createInvoice(params);
}
async updateInvoice(params: WhmcsUpdateInvoiceParams): Promise<WhmcsUpdateInvoiceResponse> {
return this.orchestrator.updateInvoice(params);
}
// ==========================================
// PRODUCT/SERVICE API METHODS
// PRODUCT/SUBSCRIPTION API METHODS
// ==========================================
async getClientsProducts(params: WhmcsGetClientsProductsParams): Promise<WhmcsProductsResponse> {
return this.makeRequest<WhmcsProductsResponse>("GetClientsProducts", params);
return this.orchestrator.getClientsProducts(params);
}
async getProducts(): Promise<WhmcsCatalogProductsResponse> {
return this.makeRequest<WhmcsCatalogProductsResponse>("GetProducts");
async getCatalogProducts(): Promise<WhmcsCatalogProductsResponse> {
return this.orchestrator.getCatalogProducts();
}
// ==========================================
// PAYMENT API METHODS
// ==========================================
async getPaymentMethods(params: WhmcsGetPayMethodsParams): Promise<WhmcsPayMethodsResponse> {
return this.orchestrator.getPaymentMethods(params);
}
async addPaymentMethod(params: WhmcsAddPayMethodParams): Promise<WhmcsAddPayMethodResponse> {
return this.orchestrator.addPaymentMethod(params);
}
async getPaymentGateways(): Promise<WhmcsPaymentGatewaysResponse> {
return this.orchestrator.getPaymentGateways();
}
async capturePayment(params: WhmcsCapturePaymentParams): Promise<WhmcsCapturePaymentResponse> {
return this.orchestrator.capturePayment(params);
}
async addCredit(params: WhmcsAddCreditParams): Promise<WhmcsAddCreditResponse> {
return this.orchestrator.addCredit(params);
}
async addInvoicePayment(
params: WhmcsAddInvoicePaymentParams
): Promise<WhmcsAddInvoicePaymentResponse> {
return this.orchestrator.addInvoicePayment(params);
}
// ==========================================
@ -491,83 +163,36 @@ export class WhmcsConnectionService {
// ==========================================
async createSsoToken(params: WhmcsCreateSsoTokenParams): Promise<WhmcsSsoResponse> {
return this.makeRequest<WhmcsSsoResponse>("CreateSsoToken", params);
return this.orchestrator.createSsoToken(params);
}
// ==========================================
// PAYMENT METHOD API METHODS
// ADMIN API METHODS
// ==========================================
async getPayMethods(params: WhmcsGetPayMethodsParams): Promise<WhmcsPayMethodsResponse> {
return this.makeRequest<WhmcsPayMethodsResponse>("GetPayMethods", params);
async acceptOrder(orderId: number): Promise<{ result: string }> {
return this.orchestrator.acceptOrder(orderId);
}
async addPayMethod(params: WhmcsAddPayMethodParams): Promise<WhmcsAddPayMethodResponse> {
return this.makeRequest<WhmcsAddPayMethodResponse>("AddPayMethod", params);
async cancelOrder(orderId: number): Promise<{ result: string }> {
return this.orchestrator.cancelOrder(orderId);
}
// ==========================================
// PAYMENT GATEWAY API METHODS
// UTILITY METHODS
// ==========================================
async getPaymentGateways(): Promise<WhmcsPaymentGatewaysResponse> {
return this.makeRequest<WhmcsPaymentGatewaysResponse>("GetPaymentMethods");
}
// ==========================================
// ORDER METHODS (For Order Service)
// ==========================================
async addOrder(params: Record<string, unknown>): Promise<unknown> {
return this.makeRequest("AddOrder", params);
}
async acceptOrder(params: Record<string, unknown>): Promise<unknown> {
return this.makeRequest("AcceptOrder", params);
}
async getOrders(params: Record<string, unknown>): Promise<unknown> {
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<WhmcsCreateInvoiceResponse> {
return this.makeRequest("CreateInvoice", params);
getConnectionStats() {
return this.orchestrator.getConnectionStats();
}
/**
* Update an existing invoice
* Get configuration (sanitized for logging)
*/
async updateInvoice(params: WhmcsUpdateInvoiceParams): Promise<WhmcsUpdateInvoiceResponse> {
return this.makeRequest("UpdateInvoice", params);
getConfig() {
return this.orchestrator.getConfig();
}
/**
* Capture payment for an invoice
*/
async capturePayment(params: WhmcsCapturePaymentParams): Promise<WhmcsCapturePaymentResponse> {
return this.makeRequest("CapturePayment", params);
}
/**
* Add credit to a client account (useful for refunds)
*/
async addCredit(params: WhmcsAddCreditParams): Promise<WhmcsAddCreditResponse> {
return this.makeRequest("AddCredit", params);
}
/**
* Add a manual payment to an invoice
*/
async addInvoicePayment(
params: WhmcsAddInvoicePaymentParams
): Promise<WhmcsAddInvoicePaymentResponse> {
return this.makeRequest("AddInvoicePayment", params);
}
}
}

View File

@ -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,

View File

@ -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";

View File

@ -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";