Add CurrencyModule to router configuration and enhance WHMCS subscription service response handling
- Integrated CurrencyModule into the API routes for improved currency management. - Updated WHMCS subscription service to utilize a new response schema for better error handling and data normalization. - Refactored address and profile API endpoints in account service to use consistent API paths. - Introduced normalization functions for WHMCS product response types to enhance data integrity.
This commit is contained in:
parent
afa0c5306b
commit
cff6c21bae
@ -6,6 +6,7 @@ import { CatalogModule } from "@bff/modules/catalog/catalog.module";
|
||||
import { OrdersModule } from "@bff/modules/orders/orders.module";
|
||||
import { InvoicesModule } from "@bff/modules/invoices/invoices.module";
|
||||
import { SubscriptionsModule } from "@bff/modules/subscriptions/subscriptions.module";
|
||||
import { CurrencyModule } from "@bff/modules/currency/currency.module";
|
||||
import { SecurityModule } from "@bff/core/security/security.module";
|
||||
|
||||
export const apiRoutes: Routes = [
|
||||
@ -19,6 +20,7 @@ export const apiRoutes: Routes = [
|
||||
{ path: "", module: OrdersModule },
|
||||
{ path: "", module: InvoicesModule },
|
||||
{ path: "", module: SubscriptionsModule },
|
||||
{ path: "", module: CurrencyModule },
|
||||
{ path: "", module: SecurityModule },
|
||||
],
|
||||
},
|
||||
|
||||
@ -5,8 +5,10 @@ import { Subscription, SubscriptionList, Providers } from "@customer-portal/doma
|
||||
import { WhmcsConnectionOrchestratorService } from "../connection/services/whmcs-connection-orchestrator.service";
|
||||
import { WhmcsCurrencyService } from "./whmcs-currency.service";
|
||||
import { WhmcsCacheService } from "../cache/whmcs-cache.service";
|
||||
import type { WhmcsGetClientsProductsParams } from "@customer-portal/domain/subscriptions";
|
||||
import type { WhmcsProductListResponse } from "@customer-portal/domain/subscriptions";
|
||||
import {
|
||||
type WhmcsGetClientsProductsParams,
|
||||
whmcsProductListResponseSchema,
|
||||
} from "@customer-portal/domain/subscriptions";
|
||||
|
||||
export interface SubscriptionFilters {
|
||||
status?: string;
|
||||
@ -57,11 +59,20 @@ export class WhmcsSubscriptionService {
|
||||
order: "DESC",
|
||||
};
|
||||
|
||||
const response = await this.connectionService.getClientsProducts(params);
|
||||
const rawResponse = await this.connectionService.getClientsProducts(params);
|
||||
|
||||
if (!response || response.result !== "success") {
|
||||
const message = response?.message || "GetClientsProducts call failed";
|
||||
this.logger.error("WHMCS GetClientsProducts returned error", {
|
||||
if (!rawResponse) {
|
||||
this.logger.error("WHMCS GetClientsProducts returned empty response", {
|
||||
clientId,
|
||||
});
|
||||
throw new Error("GetClientsProducts call failed");
|
||||
}
|
||||
|
||||
const response = whmcsProductListResponseSchema.parse(rawResponse);
|
||||
|
||||
if (response.result === "error") {
|
||||
const message = response.message || "GetClientsProducts call failed";
|
||||
this.logger.error("WHMCS GetClientsProducts returned error result", {
|
||||
clientId,
|
||||
response,
|
||||
});
|
||||
@ -75,20 +86,20 @@ export class WhmcsSubscriptionService {
|
||||
? [productContainer]
|
||||
: [];
|
||||
|
||||
const totalResults =
|
||||
response.totalresults !== undefined ? Number(response.totalresults) : products.length;
|
||||
|
||||
this.logger.debug(`WHMCS GetClientsProducts response structure for client ${clientId}`, {
|
||||
totalresults: response.totalresults,
|
||||
startnumber: response.startnumber,
|
||||
numreturned: response.numreturned,
|
||||
totalresults: totalResults,
|
||||
startnumber: response.startnumber ?? 0,
|
||||
numreturned: response.numreturned ?? products.length,
|
||||
productCount: products.length,
|
||||
});
|
||||
|
||||
if (products.length === 0) {
|
||||
this.logger.warn(`No products found for client ${clientId}`, {
|
||||
responseStructure: response ? Object.keys(response) : "null response",
|
||||
});
|
||||
return {
|
||||
subscriptions: [],
|
||||
totalCount: 0,
|
||||
totalCount: totalResults,
|
||||
};
|
||||
}
|
||||
|
||||
@ -111,7 +122,7 @@ export class WhmcsSubscriptionService {
|
||||
|
||||
const result: SubscriptionList = {
|
||||
subscriptions,
|
||||
totalCount: subscriptions.length,
|
||||
totalCount: totalResults,
|
||||
};
|
||||
|
||||
// Cache the result
|
||||
|
||||
@ -11,22 +11,22 @@ type ProfileUpdateInput = {
|
||||
|
||||
export const accountService = {
|
||||
async getProfile() {
|
||||
const response = await apiClient.GET<UserProfile>("/me");
|
||||
const response = await apiClient.GET<UserProfile>("/api/me");
|
||||
return getNullableData<UserProfile>(response);
|
||||
},
|
||||
|
||||
async updateProfile(update: ProfileUpdateInput) {
|
||||
const response = await apiClient.PATCH<UserProfile>("/me", { body: update });
|
||||
const response = await apiClient.PATCH<UserProfile>("/api/me", { body: update });
|
||||
return getDataOrThrow<UserProfile>(response, "Failed to update profile");
|
||||
},
|
||||
|
||||
async getAddress() {
|
||||
const response = await apiClient.GET<Address>("/me/address");
|
||||
const response = await apiClient.GET<Address>("/api/me/address");
|
||||
return getNullableData<Address>(response);
|
||||
},
|
||||
|
||||
async updateAddress(address: Address) {
|
||||
const response = await apiClient.PATCH<Address>("/me/address", { body: address });
|
||||
const response = await apiClient.PATCH<Address>("/api/me/address", { body: address });
|
||||
return getDataOrThrow<Address>(response, "Failed to update address");
|
||||
},
|
||||
};
|
||||
|
||||
@ -35,3 +35,7 @@ export type {
|
||||
// Response types
|
||||
WhmcsProductListResponse,
|
||||
} from "./providers/whmcs/raw.types";
|
||||
|
||||
export {
|
||||
whmcsProductListResponseSchema,
|
||||
} from "./providers/whmcs/raw.types";
|
||||
|
||||
@ -8,6 +8,39 @@
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
const normalizeRequiredNumber = z.preprocess(
|
||||
value => {
|
||||
if (typeof value === "number") return value;
|
||||
if (typeof value === "string" && value.trim().length > 0) {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : value;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
z.number()
|
||||
);
|
||||
|
||||
const normalizeOptionalNumber = z.preprocess(
|
||||
value => {
|
||||
if (value === undefined || value === null || value === "") return undefined;
|
||||
if (typeof value === "number") return value;
|
||||
if (typeof value === "string") {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : undefined;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
z.number().optional()
|
||||
);
|
||||
|
||||
const normalizeOptionalString = z.preprocess(
|
||||
value => {
|
||||
if (value === undefined || value === null || value === "") return undefined;
|
||||
return String(value);
|
||||
},
|
||||
z.string().optional()
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// Request Parameter Types
|
||||
// ============================================================================
|
||||
@ -44,12 +77,12 @@ export const whmcsCustomFieldsContainerSchema = z.object({
|
||||
|
||||
// Raw WHMCS Product/Service (Subscription)
|
||||
export const whmcsProductRawSchema = z.object({
|
||||
id: z.number(),
|
||||
clientid: z.number(),
|
||||
serviceid: z.number().optional(),
|
||||
pid: z.number().optional(),
|
||||
orderid: z.number().optional(),
|
||||
ordernumber: z.string().optional(),
|
||||
id: normalizeRequiredNumber,
|
||||
clientid: normalizeRequiredNumber,
|
||||
serviceid: normalizeOptionalNumber,
|
||||
pid: normalizeOptionalNumber,
|
||||
orderid: normalizeOptionalNumber,
|
||||
ordernumber: normalizeOptionalString,
|
||||
regdate: z.string(),
|
||||
name: z.string(),
|
||||
translated_name: z.string().optional(),
|
||||
@ -57,12 +90,12 @@ export const whmcsProductRawSchema = z.object({
|
||||
translated_groupname: z.string().optional(),
|
||||
domain: z.string().optional(),
|
||||
dedicatedip: z.string().optional(),
|
||||
serverid: z.number().optional(),
|
||||
serverid: normalizeOptionalNumber,
|
||||
servername: z.string().optional(),
|
||||
serverip: z.string().optional(),
|
||||
serverhostname: z.string().optional(),
|
||||
suspensionreason: z.string().optional(),
|
||||
promoid: z.number().optional(),
|
||||
promoid: normalizeOptionalNumber,
|
||||
subscriptionid: z.string().optional(),
|
||||
|
||||
// Pricing
|
||||
@ -84,10 +117,10 @@ export const whmcsProductRawSchema = z.object({
|
||||
|
||||
// Notes
|
||||
notes: z.string().optional(),
|
||||
diskusage: z.number().optional(),
|
||||
disklimit: z.number().optional(),
|
||||
bwusage: z.number().optional(),
|
||||
bwlimit: z.number().optional(),
|
||||
diskusage: normalizeOptionalNumber,
|
||||
disklimit: normalizeOptionalNumber,
|
||||
bwusage: normalizeOptionalNumber,
|
||||
bwlimit: normalizeOptionalNumber,
|
||||
lastupdate: z.string().optional(),
|
||||
|
||||
// Custom fields
|
||||
@ -113,19 +146,18 @@ export type WhmcsCustomField = z.infer<typeof whmcsCustomFieldSchema>;
|
||||
* WHMCS GetClientsProducts API response schema
|
||||
*/
|
||||
export const whmcsProductListResponseSchema = z.object({
|
||||
result: z.enum(["success", "error"]),
|
||||
result: z.enum(["success", "error"]).optional(),
|
||||
message: z.string().optional(),
|
||||
clientid: z.union([z.number(), z.string()]).optional(),
|
||||
serviceid: z.union([z.number(), z.string(), z.null()]).optional(),
|
||||
pid: z.union([z.number(), z.string(), z.null()]).optional(),
|
||||
domain: z.string().nullable().optional(),
|
||||
totalresults: z.union([z.number(), z.string()]).optional(),
|
||||
startnumber: z.number().optional(),
|
||||
numreturned: z.number().optional(),
|
||||
startnumber: normalizeOptionalNumber,
|
||||
numreturned: normalizeOptionalNumber,
|
||||
products: z.object({
|
||||
product: z.union([whmcsProductRawSchema, z.array(whmcsProductRawSchema)]).optional(),
|
||||
}).optional(),
|
||||
});
|
||||
|
||||
export type WhmcsProductListResponse = z.infer<typeof whmcsProductListResponseSchema>;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user