T. Narantuya a95ec60859 Refactor address management and update related services for improved clarity and functionality
- Updated address retrieval in user service to replace billing info with a dedicated address method.
- Adjusted API endpoints to use `PATCH /api/me/address` for address updates instead of billing updates.
- Enhanced documentation to reflect changes in address management processes and API usage.
- Removed deprecated types and services related to billing address handling, streamlining the codebase.
2025-09-17 18:43:43 +09:00

400 lines
12 KiB
TypeScript

import { getErrorMessage } from "@bff/core/utils/error.util";
import { Injectable, Inject } from "@nestjs/common";
import type {
Invoice,
InvoiceList,
Subscription,
SubscriptionList,
PaymentMethodList,
PaymentGatewayList,
} from "@customer-portal/domain";
import { WhmcsConnectionService } from "./services/whmcs-connection.service";
import type { Address } from "@customer-portal/domain";
import { WhmcsInvoiceService, InvoiceFilters } from "./services/whmcs-invoice.service";
import {
WhmcsSubscriptionService,
SubscriptionFilters,
} from "./services/whmcs-subscription.service";
import { WhmcsClientService } from "./services/whmcs-client.service";
import { WhmcsPaymentService } from "./services/whmcs-payment.service";
import { WhmcsSsoService } from "./services/whmcs-sso.service";
import { WhmcsOrderService } from "./services/whmcs-order.service";
import {
WhmcsAddClientParams,
WhmcsClientResponse,
WhmcsCatalogProductsResponse,
} from "./types/whmcs-api.types";
import { Logger } from "nestjs-pino";
// Re-export interfaces for backward compatibility
export type { InvoiceFilters, SubscriptionFilters };
@Injectable()
export class WhmcsService {
constructor(
private readonly connectionService: WhmcsConnectionService,
private readonly invoiceService: WhmcsInvoiceService,
private readonly subscriptionService: WhmcsSubscriptionService,
private readonly clientService: WhmcsClientService,
private readonly paymentService: WhmcsPaymentService,
private readonly ssoService: WhmcsSsoService,
private readonly orderService: WhmcsOrderService,
@Inject(Logger) private readonly logger: Logger
) {}
// ==========================================
// INVOICE OPERATIONS (delegate to InvoiceService)
// ==========================================
/**
* Get paginated invoices for a client with caching
*/
async getInvoices(
clientId: number,
userId: string,
filters: InvoiceFilters = {}
): Promise<InvoiceList> {
return this.invoiceService.getInvoices(clientId, userId, filters);
}
/**
* Get invoices with items (for subscription linking)
*/
async getInvoicesWithItems(
clientId: number,
userId: string,
filters: InvoiceFilters = {}
): Promise<InvoiceList> {
return this.invoiceService.getInvoicesWithItems(clientId, userId, filters);
}
/**
* Get individual invoice by ID with caching
*/
async getInvoiceById(clientId: number, userId: string, invoiceId: number): Promise<Invoice> {
return this.invoiceService.getInvoiceById(clientId, userId, invoiceId);
}
/**
* Invalidate cache for a specific invoice
*/
async invalidateInvoiceCache(userId: string, invoiceId: number): Promise<void> {
return this.invoiceService.invalidateInvoiceCache(userId, invoiceId);
}
// ==========================================
// SUBSCRIPTION OPERATIONS (delegate to SubscriptionService)
// ==========================================
/**
* Get client subscriptions/services with caching
*/
async getSubscriptions(
clientId: number,
userId: string,
filters: SubscriptionFilters = {}
): Promise<SubscriptionList> {
return this.subscriptionService.getSubscriptions(clientId, userId, filters);
}
/**
* Get individual subscription by ID
*/
async getSubscriptionById(
clientId: number,
userId: string,
subscriptionId: number
): Promise<Subscription> {
return this.subscriptionService.getSubscriptionById(clientId, userId, subscriptionId);
}
/**
* Invalidate cache for a specific subscription
*/
async invalidateSubscriptionCache(userId: string, subscriptionId: number): Promise<void> {
return this.subscriptionService.invalidateSubscriptionCache(userId, subscriptionId);
}
/**
* Get subscription statistics for a client
*/
async getSubscriptionStats(
clientId: number,
userId: string
): Promise<{
total: number;
active: number;
suspended: number;
cancelled: number;
pending: number;
}> {
try {
const subscriptionList = await this.subscriptionService.getSubscriptions(clientId, userId);
const subscriptions: Subscription[] = subscriptionList.subscriptions;
const stats = {
total: subscriptions.length,
active: subscriptions.filter((s: Subscription) => s.status === "Active").length,
suspended: subscriptions.filter((s: Subscription) => s.status === "Suspended").length,
cancelled: subscriptions.filter((s: Subscription) => s.status === "Cancelled").length,
pending: subscriptions.filter((s: Subscription) => s.status === "Pending").length,
};
this.logger.debug(`Generated subscription stats for client ${clientId}:`, stats);
return stats;
} catch (error) {
this.logger.error(`Failed to get subscription stats for client ${clientId}`, {
error: getErrorMessage(error),
userId,
});
throw error;
}
}
// ==========================================
// CLIENT OPERATIONS (delegate to ClientService)
// ==========================================
/**
* Validate client login credentials
*/
async validateLogin(
email: string,
password: string
): Promise<{ userId: number; passwordHash: string }> {
return this.clientService.validateLogin(email, password);
}
/**
* Get client details by ID
*/
async getClientDetails(clientId: number): Promise<WhmcsClientResponse["client"]> {
return this.clientService.getClientDetails(clientId);
}
/**
* Get client details by email
*/
async getClientDetailsByEmail(email: string): Promise<WhmcsClientResponse["client"]> {
return this.clientService.getClientDetailsByEmail(email);
}
/**
* Update client details in WHMCS
*/
async updateClient(
clientId: number,
updateData: Partial<WhmcsClientResponse["client"]>
): Promise<void> {
return this.clientService.updateClient(clientId, updateData);
}
/**
* Convenience helpers for address get/update on WHMCS client
*/
async getClientAddress(clientId: number): Promise<Address> {
const client = await this.clientService.getClientDetails(clientId);
return {
street: client.address1 || null,
streetLine2: client.address2 || null,
city: client.city || null,
state: client.state || null,
postalCode: client.postcode || null,
country: client.country || null,
};
}
async updateClientAddress(clientId: number, address: Partial<Address>): Promise<void> {
const updateData: Partial<WhmcsClientResponse["client"]> = {};
if (address.street !== undefined) updateData.address1 = address.street ?? undefined;
if (address.streetLine2 !== undefined) updateData.address2 = address.streetLine2 ?? undefined;
if (address.city !== undefined) updateData.city = address.city ?? undefined;
if (address.state !== undefined) updateData.state = address.state ?? undefined;
if (address.postalCode !== undefined) updateData.postcode = address.postalCode ?? undefined;
if (address.country !== undefined) updateData.country = address.country ?? undefined;
if (Object.keys(updateData).length === 0) return;
await this.clientService.updateClient(clientId, updateData);
}
/**
* Add new client
*/
async addClient(clientData: WhmcsAddClientParams): Promise<{ clientId: number }> {
return this.clientService.addClient(clientData);
}
/**
* Invalidate cache for a user
*/
async invalidateUserCache(userId: string): Promise<void> {
return this.clientService.invalidateUserCache(userId);
}
// ==========================================
// PAYMENT OPERATIONS (delegate to PaymentService)
// ==========================================
/**
* Get payment methods for a client
*/
async getPaymentMethods(clientId: number, userId: string): Promise<PaymentMethodList> {
return this.paymentService.getPaymentMethods(clientId, userId);
}
/**
* Get available payment gateways
*/
async getPaymentGateways(): Promise<PaymentGatewayList> {
return this.paymentService.getPaymentGateways();
}
/**
* Invalidate payment methods cache for a user
*/
async invalidatePaymentMethodsCache(userId: string): Promise<void> {
return this.paymentService.invalidatePaymentMethodsCache(userId);
}
/**
* Create SSO token with payment method for invoice payment
*/
async createPaymentSsoToken(
clientId: number,
invoiceId: number,
paymentMethodId?: number,
gatewayName?: string
): Promise<{ url: string; expiresAt: string }> {
return this.paymentService.createPaymentSsoToken(
clientId,
invoiceId,
paymentMethodId,
gatewayName
);
}
/**
* Get products catalog
*/
async getProducts(): Promise<WhmcsCatalogProductsResponse> {
return this.paymentService.getProducts() as Promise<WhmcsCatalogProductsResponse>;
}
/**
* Transform product data (delegate to transformer)
*/
transformProduct(whmcsProduct: WhmcsCatalogProductsResponse["products"]["product"][0]): unknown {
return this.paymentService.transformProduct(whmcsProduct);
}
// ==========================================
// SSO OPERATIONS (delegate to SsoService)
// ==========================================
/**
* Create SSO token for WHMCS access
*/
async createSsoToken(
clientId: number,
destination?: string,
ssoRedirectPath?: string
): Promise<{ url: string; expiresAt: string }> {
return this.ssoService.createSsoToken(clientId, destination, ssoRedirectPath);
}
/**
* Helper function to create SSO links for invoices
*/
async whmcsSsoForInvoice(
clientId: number,
invoiceId: number,
target: "view" | "download" | "pay"
): Promise<string> {
return this.ssoService.whmcsSsoForInvoice(clientId, invoiceId, target);
}
// ==========================================
// CONNECTION & HEALTH (delegate to ConnectionService)
// ==========================================
/**
* Health check for WHMCS API
*/
async healthCheck(): Promise<boolean> {
return this.connectionService.healthCheck();
}
/**
* Check if WHMCS service is available
*/
async isAvailable(): Promise<boolean> {
return this.connectionService.isAvailable();
}
/**
* Get WHMCS system information
*/
async getSystemInfo(): Promise<unknown> {
return this.connectionService.getSystemInfo();
}
// ==========================================
// INVOICE CREATION AND PAYMENT OPERATIONS
// ==========================================
/**
* Create a new invoice for a client
*/
async createInvoice(params: {
clientId: number;
description: string;
amount: number;
currency?: string;
dueDate?: Date;
notes?: string;
}): Promise<{ id: number; number: string; total: number; status: string }> {
return this.invoiceService.createInvoice(params);
}
/**
* Update an existing invoice
*/
async updateInvoice(params: {
invoiceId: number;
status?:
| "Draft"
| "Unpaid"
| "Paid"
| "Cancelled"
| "Refunded"
| "Collections"
| "Payment Pending";
dueDate?: Date;
notes?: string;
}): Promise<{ success: boolean; message?: string }> {
return this.invoiceService.updateInvoice(params);
}
/**
* Capture payment for an invoice
*/
async capturePayment(params: {
invoiceId: number;
amount: number;
currency?: string;
}): Promise<{ success: boolean; transactionId?: string; error?: string }> {
return this.invoiceService.capturePayment(params);
}
// ==========================================
// ORDER OPERATIONS (delegate to OrderService)
// ==========================================
/**
* Get order service for direct access to order operations
* Used by OrderProvisioningService for complex order workflows
*/
getOrderService(): WhmcsOrderService {
return this.orderService;
}
}