refactor: enhance error handling in WHMCS and signup workflows

- Introduced structured error codes in BadRequestException for better clarity in WHMCS and signup workflows.
- Updated error messages to include specific context, improving user feedback during account verification and migration processes.
- Refined validation logic to ensure consistent error handling across services.
This commit is contained in:
barsa 2026-03-02 18:15:13 +09:00
parent 230a61c520
commit 49e9dba3a3
5 changed files with 37 additions and 31 deletions

View File

@ -7,6 +7,7 @@ import { CacheService } from "@bff/infra/cache/cache.service.js";
import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.js"; import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.js";
import { SalesforceAccountService } from "@bff/integrations/salesforce/services/salesforce-account.service.js"; import { SalesforceAccountService } from "@bff/integrations/salesforce/services/salesforce-account.service.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js"; import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { ErrorCode } from "@customer-portal/domain/common";
import type { SignupRequest } from "@customer-portal/domain/auth"; import type { SignupRequest } from "@customer-portal/domain/auth";
import type { SignupAccountSnapshot, SignupAccountCacheEntry } from "./signup.types.js"; import type { SignupAccountSnapshot, SignupAccountCacheEntry } from "./signup.types.js";
import { PORTAL_SOURCE_NEW_SIGNUP } from "@bff/modules/auth/constants/portal.constants.js"; import { PORTAL_SOURCE_NEW_SIGNUP } from "@bff/modules/auth/constants/portal.constants.js";
@ -37,9 +38,10 @@ export class SignupAccountResolverService {
if (normalizedCustomerNumber) { if (normalizedCustomerNumber) {
const resolved = await this.getAccountSnapshot(normalizedCustomerNumber); const resolved = await this.getAccountSnapshot(normalizedCustomerNumber);
if (!resolved) { if (!resolved) {
throw new BadRequestException( throw new BadRequestException({
`Salesforce account not found for Customer Number: ${normalizedCustomerNumber}` code: ErrorCode.CUSTOMER_NOT_FOUND,
); message: `Salesforce account not found for Customer Number: ${normalizedCustomerNumber}`,
});
} }
if (resolved.WH_Account__c && resolved.WH_Account__c.trim() !== "") { if (resolved.WH_Account__c && resolved.WH_Account__c.trim() !== "") {

View File

@ -13,6 +13,7 @@ import { WhmcsAccountDiscoveryService } from "@bff/integrations/whmcs/services/w
import { WhmcsClientService } from "@bff/integrations/whmcs/services/whmcs-client.service.js"; import { WhmcsClientService } from "@bff/integrations/whmcs/services/whmcs-client.service.js";
import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.js"; import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.js";
import { extractErrorMessage } from "@bff/core/utils/error.util.js"; import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { ErrorCode } from "@customer-portal/domain/common";
import { mapPrismaUserToDomain } from "@bff/infra/mappers/index.js"; import { mapPrismaUserToDomain } from "@bff/infra/mappers/index.js";
import { getCustomFieldValue } from "@customer-portal/domain/customer/providers"; import { getCustomFieldValue } from "@customer-portal/domain/customer/providers";
import { safeOperation, OperationCriticality } from "@bff/core/utils/safe-operation.util.js"; import { safeOperation, OperationCriticality } from "@bff/core/utils/safe-operation.util.js";
@ -79,9 +80,10 @@ export class WhmcsLinkWorkflowService {
getCustomFieldValue(clientDetails.customfields, "Customer Number")?.trim(); getCustomFieldValue(clientDetails.customfields, "Customer Number")?.trim();
if (!customerNumber) { if (!customerNumber) {
throw new BadRequestException( throw new BadRequestException({
`Customer Number not found in WHMCS custom field 198. Please contact support.` code: ErrorCode.ACCOUNT_MAPPING_MISSING,
); message: `Customer Number not found in WHMCS custom field 198 for client ${clientNumericId}`,
});
} }
this.logger.log("Found Customer Number for WHMCS client", { this.logger.log("Found Customer Number for WHMCS client", {
@ -91,7 +93,10 @@ export class WhmcsLinkWorkflowService {
const sfAccount = await this.salesforceService.findAccountByCustomerNumber(customerNumber); const sfAccount = await this.salesforceService.findAccountByCustomerNumber(customerNumber);
if (!sfAccount) { if (!sfAccount) {
throw new BadRequestException("Salesforce account not found. Please contact support."); throw new BadRequestException({
code: ErrorCode.CUSTOMER_NOT_FOUND,
message: `Salesforce account not found for Customer Number: ${customerNumber}`,
});
} }
const createdUser = await this.usersService.create( const createdUser = await this.usersService.create(

View File

@ -4,6 +4,7 @@ import { Logger } from "nestjs-pino";
import * as argon2 from "argon2"; import * as argon2 from "argon2";
import { type MigrateWhmcsAccountRequest } from "@customer-portal/domain/get-started"; import { type MigrateWhmcsAccountRequest } from "@customer-portal/domain/get-started";
import { ErrorCode } from "@customer-portal/domain/common";
import { import {
getCustomFieldValue, getCustomFieldValue,
serializeWhmcsKeyValueMap, serializeWhmcsKeyValueMap,
@ -112,7 +113,10 @@ export class WhmcsMigrationWorkflowService {
// Verify WHMCS client still exists and matches session // Verify WHMCS client still exists and matches session
const whmcsClient = await this.whmcsDiscovery.findAccountByEmail(email); const whmcsClient = await this.whmcsDiscovery.findAccountByEmail(email);
if (!whmcsClient || whmcsClient.id !== whmcsClientId) { if (!whmcsClient || whmcsClient.id !== whmcsClientId) {
throw new BadRequestException("WHMCS account verification failed. Please start over."); throw new BadRequestException({
code: ErrorCode.VALIDATION_FAILED,
message: `WHMCS client mismatch for email ${email}: expected ${whmcsClientId}, got ${whmcsClient?.id ?? "none"}`,
});
} }
// Check for existing portal user // Check for existing portal user
@ -124,9 +128,10 @@ export class WhmcsMigrationWorkflowService {
// Find Salesforce account for mapping // Find Salesforce account for mapping
const sfAccount = await this.findSalesforceAccountForMigration(email, whmcsClientId); const sfAccount = await this.findSalesforceAccountForMigration(email, whmcsClientId);
if (!sfAccount) { if (!sfAccount) {
throw new BadRequestException( throw new BadRequestException({
"Unable to find your Salesforce account. Please contact support." code: ErrorCode.CUSTOMER_NOT_FOUND,
); message: `No Salesforce account found for migration: email=${email}, whmcsClientId=${whmcsClientId}`,
});
} }
// Hash password for portal storage // Hash password for portal storage

View File

@ -293,11 +293,7 @@ export const whmcsCurrenciesResponseSchema = z
result: z.enum(["success", "error"]).optional(), result: z.enum(["success", "error"]).optional(),
message: z.string().optional(), message: z.string().optional(),
errorcode: z.string().optional(), errorcode: z.string().optional(),
totalresults: z totalresults: whmcsOptionalNumber,
.string()
.transform(val => Number.parseInt(val, 10))
.or(z.number())
.optional(),
currencies: z currencies: z
.object({ .object({
currency: z.array(whmcsCurrencySchema).or(whmcsCurrencySchema), currency: z.array(whmcsCurrencySchema).or(whmcsCurrencySchema),

View File

@ -208,21 +208,19 @@ const nullableProfileOverrides = nullableProfileFields.reduce<Record<string, z.Z
{} {}
); );
export const whmcsClientSchema = whmcsRawClientSchema export const whmcsClientSchema = whmcsRawClientSchema.extend({
.extend({ ...nullableProfileOverrides,
...nullableProfileOverrides, defaultpaymethodid: whmcsOptionalNumber.nullable(),
defaultpaymethodid: whmcsOptionalNumber.nullable(), currency: whmcsOptionalNumber.nullable(),
currency: whmcsOptionalNumber.nullable(), allowSingleSignOn: whmcsOptionalBoolean.nullable(),
allowSingleSignOn: whmcsOptionalBoolean.nullable(), email_verified: whmcsOptionalBoolean.nullable(),
email_verified: whmcsOptionalBoolean.nullable(), marketing_emails_opt_in: whmcsOptionalBoolean.nullable(),
marketing_emails_opt_in: whmcsOptionalBoolean.nullable(), address: addressSchema.nullable().optional(),
address: addressSchema.nullable().optional(), email_preferences: emailPreferencesSchema.nullable().optional(),
email_preferences: emailPreferencesSchema.nullable().optional(), customfields: whmcsCustomFieldsSchema,
customfields: whmcsCustomFieldsSchema, users: whmcsUsersSchema,
users: whmcsUsersSchema, stats: statsSchema.optional(),
stats: statsSchema.optional(), });
})
.transform(raw => ({ ...raw }));
// ============================================================================ // ============================================================================
// User Schema (API Response - Normalized camelCase) // User Schema (API Response - Normalized camelCase)