- Added optional userId parameter to payment capture methods in WhmcsService and WhmcsInvoiceService to improve tracking and management of user-related transactions. - Updated invoice retrieval and user profile services to utilize parseUuidOrThrow for user ID validation, ensuring consistent error messaging for invalid formats. - Refactored SIM billing and activation services to include userId in one-time charge creation, enhancing billing traceability. - Adjusted validation logic in various services to improve clarity and maintainability, ensuring robust handling of user IDs throughout the application.
341 lines
10 KiB
TypeScript
341 lines
10 KiB
TypeScript
import { Injectable, Inject } from "@nestjs/common";
|
|
import type { Invoice, InvoiceList } from "@customer-portal/domain/billing";
|
|
import type { Subscription, SubscriptionList } from "@customer-portal/domain/subscriptions";
|
|
import type { PaymentMethodList, PaymentGatewayList } from "@customer-portal/domain/payments";
|
|
import { addressSchema, type Address, type WhmcsClient } from "@customer-portal/domain/customer";
|
|
import { prepareWhmcsClientAddressUpdate } from "@customer-portal/domain/customer/providers";
|
|
import { WhmcsConnectionOrchestratorService } from "./connection/services/whmcs-connection-orchestrator.service.js";
|
|
import { WhmcsInvoiceService } from "./services/whmcs-invoice.service.js";
|
|
import type { InvoiceFilters } from "./services/whmcs-invoice.service.js";
|
|
import { WhmcsSubscriptionService } from "./services/whmcs-subscription.service.js";
|
|
import type { SubscriptionFilters } from "./services/whmcs-subscription.service.js";
|
|
import { WhmcsClientService } from "./services/whmcs-client.service.js";
|
|
import { WhmcsPaymentService } from "./services/whmcs-payment.service.js";
|
|
import { WhmcsSsoService } from "./services/whmcs-sso.service.js";
|
|
import { WhmcsOrderService } from "./services/whmcs-order.service.js";
|
|
import type {
|
|
WhmcsAddClientParams,
|
|
WhmcsClientResponse,
|
|
} from "@customer-portal/domain/customer/providers";
|
|
import type {
|
|
WhmcsGetClientsProductsParams,
|
|
WhmcsProductListResponse,
|
|
} from "@customer-portal/domain/subscriptions/providers";
|
|
import type { WhmcsCatalogProductNormalized } from "@customer-portal/domain/services/providers";
|
|
import { Logger } from "nestjs-pino";
|
|
|
|
@Injectable()
|
|
export class WhmcsService {
|
|
constructor(
|
|
private readonly connectionService: WhmcsConnectionOrchestratorService,
|
|
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);
|
|
}
|
|
|
|
// ==========================================
|
|
// 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
|
|
* Returns internal WhmcsClient (type inferred)
|
|
*/
|
|
async getClientDetails(clientId: number): Promise<WhmcsClient> {
|
|
return this.clientService.getClientDetails(clientId);
|
|
}
|
|
|
|
/**
|
|
* 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 customer = await this.clientService.getClientDetails(clientId);
|
|
return addressSchema.parse(customer.address ?? {});
|
|
}
|
|
|
|
async updateClientAddress(clientId: number, address: Partial<Address>): Promise<void> {
|
|
const parsed = addressSchema.partial().parse(address ?? {});
|
|
const updateData = prepareWhmcsClientAddressUpdate(parsed);
|
|
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<WhmcsCatalogProductNormalized[]> {
|
|
return this.paymentService.getProducts();
|
|
}
|
|
|
|
// ==========================================
|
|
// 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();
|
|
}
|
|
|
|
async getClientsProducts(
|
|
params: WhmcsGetClientsProductsParams
|
|
): Promise<WhmcsProductListResponse> {
|
|
return this.connectionService.getClientsProducts(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;
|
|
}
|
|
|
|
// ==========================================
|
|
// INVOICE CREATION AND PAYMENT OPERATIONS (Used by SIM/Order services)
|
|
// ==========================================
|
|
|
|
/**
|
|
* 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;
|
|
userId?: string;
|
|
}): Promise<{ success: boolean; transactionId?: string; error?: string }> {
|
|
return this.invoiceService.capturePayment(params);
|
|
}
|
|
}
|