diff --git a/apps/bff/src/modules/auth/infra/workflows/signup/signup-account-resolver.service.ts b/apps/bff/src/modules/auth/infra/workflows/signup/signup-account-resolver.service.ts index 4e9d0dbd..f6f551fb 100644 --- a/apps/bff/src/modules/auth/infra/workflows/signup/signup-account-resolver.service.ts +++ b/apps/bff/src/modules/auth/infra/workflows/signup/signup-account-resolver.service.ts @@ -7,6 +7,7 @@ import { CacheService } from "@bff/infra/cache/cache.service.js"; import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.js"; import { SalesforceAccountService } from "@bff/integrations/salesforce/services/salesforce-account.service.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 { SignupAccountSnapshot, SignupAccountCacheEntry } from "./signup.types.js"; import { PORTAL_SOURCE_NEW_SIGNUP } from "@bff/modules/auth/constants/portal.constants.js"; @@ -37,9 +38,10 @@ export class SignupAccountResolverService { if (normalizedCustomerNumber) { const resolved = await this.getAccountSnapshot(normalizedCustomerNumber); if (!resolved) { - throw new BadRequestException( - `Salesforce account not found for Customer Number: ${normalizedCustomerNumber}` - ); + throw new BadRequestException({ + code: ErrorCode.CUSTOMER_NOT_FOUND, + message: `Salesforce account not found for Customer Number: ${normalizedCustomerNumber}`, + }); } if (resolved.WH_Account__c && resolved.WH_Account__c.trim() !== "") { diff --git a/apps/bff/src/modules/auth/infra/workflows/whmcs-link-workflow.service.ts b/apps/bff/src/modules/auth/infra/workflows/whmcs-link-workflow.service.ts index fb06c4c9..001268a5 100644 --- a/apps/bff/src/modules/auth/infra/workflows/whmcs-link-workflow.service.ts +++ b/apps/bff/src/modules/auth/infra/workflows/whmcs-link-workflow.service.ts @@ -13,6 +13,7 @@ import { WhmcsAccountDiscoveryService } from "@bff/integrations/whmcs/services/w import { WhmcsClientService } from "@bff/integrations/whmcs/services/whmcs-client.service.js"; import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.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 { getCustomFieldValue } from "@customer-portal/domain/customer/providers"; import { safeOperation, OperationCriticality } from "@bff/core/utils/safe-operation.util.js"; @@ -79,9 +80,10 @@ export class WhmcsLinkWorkflowService { getCustomFieldValue(clientDetails.customfields, "Customer Number")?.trim(); if (!customerNumber) { - throw new BadRequestException( - `Customer Number not found in WHMCS custom field 198. Please contact support.` - ); + throw new BadRequestException({ + 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", { @@ -91,7 +93,10 @@ export class WhmcsLinkWorkflowService { const sfAccount = await this.salesforceService.findAccountByCustomerNumber(customerNumber); 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( diff --git a/apps/bff/src/modules/auth/infra/workflows/whmcs-migration-workflow.service.ts b/apps/bff/src/modules/auth/infra/workflows/whmcs-migration-workflow.service.ts index c019b71b..581bde7e 100644 --- a/apps/bff/src/modules/auth/infra/workflows/whmcs-migration-workflow.service.ts +++ b/apps/bff/src/modules/auth/infra/workflows/whmcs-migration-workflow.service.ts @@ -4,6 +4,7 @@ import { Logger } from "nestjs-pino"; import * as argon2 from "argon2"; import { type MigrateWhmcsAccountRequest } from "@customer-portal/domain/get-started"; +import { ErrorCode } from "@customer-portal/domain/common"; import { getCustomFieldValue, serializeWhmcsKeyValueMap, @@ -112,7 +113,10 @@ export class WhmcsMigrationWorkflowService { // Verify WHMCS client still exists and matches session const whmcsClient = await this.whmcsDiscovery.findAccountByEmail(email); 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 @@ -124,9 +128,10 @@ export class WhmcsMigrationWorkflowService { // Find Salesforce account for mapping const sfAccount = await this.findSalesforceAccountForMigration(email, whmcsClientId); if (!sfAccount) { - throw new BadRequestException( - "Unable to find your Salesforce account. Please contact support." - ); + throw new BadRequestException({ + code: ErrorCode.CUSTOMER_NOT_FOUND, + message: `No Salesforce account found for migration: email=${email}, whmcsClientId=${whmcsClientId}`, + }); } // Hash password for portal storage diff --git a/packages/domain/billing/providers/whmcs/raw.types.ts b/packages/domain/billing/providers/whmcs/raw.types.ts index c9a0a8d5..f6ce6ddf 100644 --- a/packages/domain/billing/providers/whmcs/raw.types.ts +++ b/packages/domain/billing/providers/whmcs/raw.types.ts @@ -293,11 +293,7 @@ export const whmcsCurrenciesResponseSchema = z result: z.enum(["success", "error"]).optional(), message: z.string().optional(), errorcode: z.string().optional(), - totalresults: z - .string() - .transform(val => Number.parseInt(val, 10)) - .or(z.number()) - .optional(), + totalresults: whmcsOptionalNumber, currencies: z .object({ currency: z.array(whmcsCurrencySchema).or(whmcsCurrencySchema), diff --git a/packages/domain/customer/schema.ts b/packages/domain/customer/schema.ts index e6329c0c..d36ea4d7 100644 --- a/packages/domain/customer/schema.ts +++ b/packages/domain/customer/schema.ts @@ -208,21 +208,19 @@ const nullableProfileOverrides = nullableProfileFields.reduce ({ ...raw })); +export const whmcsClientSchema = whmcsRawClientSchema.extend({ + ...nullableProfileOverrides, + defaultpaymethodid: whmcsOptionalNumber.nullable(), + currency: whmcsOptionalNumber.nullable(), + allowSingleSignOn: whmcsOptionalBoolean.nullable(), + email_verified: whmcsOptionalBoolean.nullable(), + marketing_emails_opt_in: whmcsOptionalBoolean.nullable(), + address: addressSchema.nullable().optional(), + email_preferences: emailPreferencesSchema.nullable().optional(), + customfields: whmcsCustomFieldsSchema, + users: whmcsUsersSchema, + stats: statsSchema.optional(), +}); // ============================================================================ // User Schema (API Response - Normalized camelCase)