Refactor various services to improve code organization and error handling. Streamline provider declarations in Salesforce module, enhance WHMCS service with new client product retrieval method, and update pagination logic in AuthAdminController. Clean up unused imports and improve type handling across multiple modules, ensuring better maintainability and consistency.
This commit is contained in:
parent
47a3de6919
commit
640a4e1094
@ -4,11 +4,7 @@ import { SalesforceConnection } from "./services/salesforce-connection.service";
|
|||||||
import { SalesforceAccountService } from "./services/salesforce-account.service";
|
import { SalesforceAccountService } from "./services/salesforce-account.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [
|
providers: [SalesforceConnection, SalesforceAccountService, SalesforceService],
|
||||||
SalesforceConnection,
|
|
||||||
SalesforceAccountService,
|
|
||||||
SalesforceService,
|
|
||||||
],
|
|
||||||
exports: [SalesforceService, SalesforceConnection],
|
exports: [SalesforceService, SalesforceConnection],
|
||||||
})
|
})
|
||||||
export class SalesforceModule {}
|
export class SalesforceModule {}
|
||||||
|
|||||||
@ -23,6 +23,8 @@ import {
|
|||||||
WhmcsAddClientParams,
|
WhmcsAddClientParams,
|
||||||
WhmcsClientResponse,
|
WhmcsClientResponse,
|
||||||
WhmcsCatalogProductsResponse,
|
WhmcsCatalogProductsResponse,
|
||||||
|
WhmcsGetClientsProductsParams,
|
||||||
|
WhmcsProductsResponse,
|
||||||
} from "./types/whmcs-api.types";
|
} from "./types/whmcs-api.types";
|
||||||
import { Logger } from "nestjs-pino";
|
import { Logger } from "nestjs-pino";
|
||||||
|
|
||||||
@ -337,6 +339,12 @@ export class WhmcsService {
|
|||||||
return this.connectionService.getSystemInfo();
|
return this.connectionService.getSystemInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getClientsProducts(
|
||||||
|
params: WhmcsGetClientsProductsParams
|
||||||
|
): Promise<WhmcsProductsResponse> {
|
||||||
|
return this.connectionService.getClientsProducts(params);
|
||||||
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// INVOICE CREATION AND PAYMENT OPERATIONS
|
// INVOICE CREATION AND PAYMENT OPERATIONS
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|||||||
@ -33,8 +33,6 @@ export class AuthAdminController {
|
|||||||
) {
|
) {
|
||||||
const pageNum = parseInt(page, 10);
|
const pageNum = parseInt(page, 10);
|
||||||
const limitNum = parseInt(limit, 10);
|
const limitNum = parseInt(limit, 10);
|
||||||
const skip = (pageNum - 1) * limitNum;
|
|
||||||
|
|
||||||
if (Number.isNaN(pageNum) || Number.isNaN(limitNum) || pageNum < 1 || limitNum < 1) {
|
if (Number.isNaN(pageNum) || Number.isNaN(limitNum) || pageNum < 1 || limitNum < 1) {
|
||||||
throw new BadRequestException("Invalid pagination parameters");
|
throw new BadRequestException("Invalid pagination parameters");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import { ZodValidationPipe } from "@bff/core/validation";
|
|||||||
// Import Zod schemas from domain
|
// Import Zod schemas from domain
|
||||||
import {
|
import {
|
||||||
signupRequestSchema,
|
signupRequestSchema,
|
||||||
loginRequestSchema,
|
|
||||||
passwordResetRequestSchema,
|
passwordResetRequestSchema,
|
||||||
passwordResetSchema,
|
passwordResetSchema,
|
||||||
setPasswordRequestSchema,
|
setPasswordRequestSchema,
|
||||||
@ -22,7 +21,6 @@ import {
|
|||||||
ssoLinkRequestSchema,
|
ssoLinkRequestSchema,
|
||||||
checkPasswordNeededRequestSchema,
|
checkPasswordNeededRequestSchema,
|
||||||
type SignupRequestInput,
|
type SignupRequestInput,
|
||||||
type LoginRequestInput,
|
|
||||||
type PasswordResetRequestInput,
|
type PasswordResetRequestInput,
|
||||||
type PasswordResetInput,
|
type PasswordResetInput,
|
||||||
type SetPasswordRequestInput,
|
type SetPasswordRequestInput,
|
||||||
|
|||||||
@ -1,10 +1,4 @@
|
|||||||
import {
|
import { Injectable, UnauthorizedException, BadRequestException, Inject } from "@nestjs/common";
|
||||||
Injectable,
|
|
||||||
UnauthorizedException,
|
|
||||||
ConflictException,
|
|
||||||
BadRequestException,
|
|
||||||
Inject,
|
|
||||||
} from "@nestjs/common";
|
|
||||||
import { JwtService } from "@nestjs/jwt";
|
import { JwtService } from "@nestjs/jwt";
|
||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
import * as bcrypt from "bcrypt";
|
import * as bcrypt from "bcrypt";
|
||||||
@ -18,8 +12,6 @@ import { getErrorMessage } from "@bff/core/utils/error.util";
|
|||||||
import { Logger } from "nestjs-pino";
|
import { Logger } from "nestjs-pino";
|
||||||
import { sanitizeWhmcsRedirectPath } from "@bff/core/utils/sso.util";
|
import { sanitizeWhmcsRedirectPath } from "@bff/core/utils/sso.util";
|
||||||
import {
|
import {
|
||||||
authResponseSchema,
|
|
||||||
type AuthResponse,
|
|
||||||
type SignupRequestInput,
|
type SignupRequestInput,
|
||||||
type ValidateSignupRequestInput,
|
type ValidateSignupRequestInput,
|
||||||
type LinkWhmcsRequestInput,
|
type LinkWhmcsRequestInput,
|
||||||
|
|||||||
@ -188,13 +188,21 @@ export class AuthTokenService {
|
|||||||
throw new UnauthorizedException("Invalid refresh token");
|
throw new UnauthorizedException("Invalid refresh token");
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenData = JSON.parse(storedToken);
|
const tokenRecord = this.parseRefreshTokenRecord(storedToken);
|
||||||
if (!tokenData.valid) {
|
if (!tokenRecord) {
|
||||||
|
this.logger.warn("Stored refresh token payload was invalid JSON", {
|
||||||
|
tokenHash: refreshTokenHash.slice(0, 8),
|
||||||
|
});
|
||||||
|
await this.redis.del(`${this.REFRESH_TOKEN_PREFIX}${refreshTokenHash}`);
|
||||||
|
throw new UnauthorizedException("Invalid refresh token");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tokenRecord.valid) {
|
||||||
this.logger.warn("Refresh token marked as invalid", {
|
this.logger.warn("Refresh token marked as invalid", {
|
||||||
tokenHash: refreshTokenHash.slice(0, 8),
|
tokenHash: refreshTokenHash.slice(0, 8),
|
||||||
});
|
});
|
||||||
// Invalidate entire token family on reuse attempt
|
// Invalidate entire token family on reuse attempt
|
||||||
await this.invalidateTokenFamily(tokenData.familyId);
|
await this.invalidateTokenFamily(tokenRecord.familyId);
|
||||||
throw new UnauthorizedException("Invalid refresh token");
|
throw new UnauthorizedException("Invalid refresh token");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,13 +236,13 @@ export class AuthTokenService {
|
|||||||
|
|
||||||
if (this.redis.status !== "ready") {
|
if (this.redis.status !== "ready") {
|
||||||
this.logger.warn("Redis unavailable during token refresh; issuing fallback token pair");
|
this.logger.warn("Redis unavailable during token refresh; issuing fallback token pair");
|
||||||
const fallbackPayload = this.jwtService.decode(refreshToken) as
|
const fallbackDecoded = this.jwtService.decode(refreshToken);
|
||||||
| RefreshTokenPayload
|
const fallbackUserId =
|
||||||
| null;
|
fallbackDecoded && typeof fallbackDecoded === "object"
|
||||||
|
? (fallbackDecoded as { userId?: unknown }).userId
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const fallbackUserId = fallbackPayload?.userId;
|
if (typeof fallbackUserId === "string") {
|
||||||
|
|
||||||
if (fallbackUserId) {
|
|
||||||
const fallbackUser = await this.usersService
|
const fallbackUser = await this.usersService
|
||||||
.findByIdInternal(fallbackUserId)
|
.findByIdInternal(fallbackUserId)
|
||||||
.catch(() => null);
|
.catch(() => null);
|
||||||
@ -265,9 +273,12 @@ export class AuthTokenService {
|
|||||||
const storedToken = await this.redis.get(`${this.REFRESH_TOKEN_PREFIX}${refreshTokenHash}`);
|
const storedToken = await this.redis.get(`${this.REFRESH_TOKEN_PREFIX}${refreshTokenHash}`);
|
||||||
|
|
||||||
if (storedToken) {
|
if (storedToken) {
|
||||||
const tokenData = JSON.parse(storedToken);
|
const tokenRecord = this.parseRefreshTokenRecord(storedToken);
|
||||||
await this.redis.del(`${this.REFRESH_TOKEN_PREFIX}${refreshTokenHash}`);
|
await this.redis.del(`${this.REFRESH_TOKEN_PREFIX}${refreshTokenHash}`);
|
||||||
await this.redis.del(`${this.REFRESH_TOKEN_FAMILY_PREFIX}${tokenData.familyId}`);
|
|
||||||
|
if (tokenRecord) {
|
||||||
|
await this.redis.del(`${this.REFRESH_TOKEN_FAMILY_PREFIX}${tokenRecord.familyId}`);
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.debug("Revoked refresh token", { tokenHash: refreshTokenHash.slice(0, 8) });
|
this.logger.debug("Revoked refresh token", { tokenHash: refreshTokenHash.slice(0, 8) });
|
||||||
}
|
}
|
||||||
@ -289,8 +300,8 @@ export class AuthTokenService {
|
|||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const data = await this.redis.get(key);
|
const data = await this.redis.get(key);
|
||||||
if (data) {
|
if (data) {
|
||||||
const family = JSON.parse(data);
|
const family = this.parseRefreshTokenFamilyRecord(data);
|
||||||
if (family.userId === userId) {
|
if (family && family.userId === userId) {
|
||||||
await this.redis.del(key);
|
await this.redis.del(key);
|
||||||
await this.redis.del(`${this.REFRESH_TOKEN_PREFIX}${family.tokenHash}`);
|
await this.redis.del(`${this.REFRESH_TOKEN_PREFIX}${family.tokenHash}`);
|
||||||
}
|
}
|
||||||
@ -309,15 +320,17 @@ export class AuthTokenService {
|
|||||||
try {
|
try {
|
||||||
const familyData = await this.redis.get(`${this.REFRESH_TOKEN_FAMILY_PREFIX}${familyId}`);
|
const familyData = await this.redis.get(`${this.REFRESH_TOKEN_FAMILY_PREFIX}${familyId}`);
|
||||||
if (familyData) {
|
if (familyData) {
|
||||||
const family = JSON.parse(familyData);
|
const family = this.parseRefreshTokenFamilyRecord(familyData);
|
||||||
|
|
||||||
await this.redis.del(`${this.REFRESH_TOKEN_FAMILY_PREFIX}${familyId}`);
|
await this.redis.del(`${this.REFRESH_TOKEN_FAMILY_PREFIX}${familyId}`);
|
||||||
await this.redis.del(`${this.REFRESH_TOKEN_PREFIX}${family.tokenHash}`);
|
|
||||||
|
|
||||||
this.logger.warn("Invalidated token family due to security concern", {
|
if (family) {
|
||||||
familyId: familyId.slice(0, 8),
|
await this.redis.del(`${this.REFRESH_TOKEN_PREFIX}${family.tokenHash}`);
|
||||||
userId: family.userId,
|
|
||||||
});
|
this.logger.warn("Invalidated token family due to security concern", {
|
||||||
|
familyId: familyId.slice(0, 8),
|
||||||
|
userId: family.userId,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error("Failed to invalidate token family", {
|
this.logger.error("Failed to invalidate token family", {
|
||||||
@ -334,6 +347,55 @@ export class AuthTokenService {
|
|||||||
return createHash("sha256").update(token).digest("hex");
|
return createHash("sha256").update(token).digest("hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private parseRefreshTokenRecord(value: string): StoredRefreshToken | null {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value) as Partial<StoredRefreshToken>;
|
||||||
|
if (
|
||||||
|
parsed &&
|
||||||
|
typeof parsed === "object" &&
|
||||||
|
typeof parsed.familyId === "string" &&
|
||||||
|
typeof parsed.userId === "string" &&
|
||||||
|
typeof parsed.valid === "boolean"
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
familyId: parsed.familyId,
|
||||||
|
userId: parsed.userId,
|
||||||
|
valid: parsed.valid,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn("Failed to parse refresh token record", {
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseRefreshTokenFamilyRecord(value: string): StoredRefreshTokenFamily | null {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value) as Partial<StoredRefreshTokenFamily>;
|
||||||
|
if (
|
||||||
|
parsed &&
|
||||||
|
typeof parsed === "object" &&
|
||||||
|
typeof parsed.userId === "string" &&
|
||||||
|
typeof parsed.tokenHash === "string"
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
userId: parsed.userId,
|
||||||
|
tokenHash: parsed.tokenHash,
|
||||||
|
deviceId: typeof parsed.deviceId === "string" ? parsed.deviceId : undefined,
|
||||||
|
userAgent: typeof parsed.userAgent === "string" ? parsed.userAgent : undefined,
|
||||||
|
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn("Failed to parse refresh token family record", {
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private parseExpiryToMs(expiry: string): number {
|
private parseExpiryToMs(expiry: string): number {
|
||||||
const unit = expiry.slice(-1);
|
const unit = expiry.slice(-1);
|
||||||
const value = parseInt(expiry.slice(0, -1));
|
const value = parseInt(expiry.slice(0, -1));
|
||||||
|
|||||||
@ -2,7 +2,11 @@ import { Injectable, Inject } from "@nestjs/common";
|
|||||||
import { Logger } from "nestjs-pino";
|
import { Logger } from "nestjs-pino";
|
||||||
import { SalesforceConnection } from "@bff/integrations/salesforce/services/salesforce-connection.service";
|
import { SalesforceConnection } from "@bff/integrations/salesforce/services/salesforce-connection.service";
|
||||||
import { getSalesforceFieldMap } from "@bff/core/config/field-map";
|
import { getSalesforceFieldMap } from "@bff/core/config/field-map";
|
||||||
import { assertSalesforceId, sanitizeSoqlLiteral } from "@bff/integrations/salesforce/utils/soql.util";
|
import {
|
||||||
|
assertSalesforceId,
|
||||||
|
sanitizeSoqlLiteral,
|
||||||
|
} from "@bff/integrations/salesforce/utils/soql.util";
|
||||||
|
import { getErrorMessage } from "@bff/core/utils/error.util";
|
||||||
import type {
|
import type {
|
||||||
SalesforceProduct2WithPricebookEntries,
|
SalesforceProduct2WithPricebookEntries,
|
||||||
SalesforceQueryResult,
|
SalesforceQueryResult,
|
||||||
@ -31,8 +35,12 @@ export class BaseCatalogService {
|
|||||||
try {
|
try {
|
||||||
const res = (await this.sf.query(soql)) as SalesforceQueryResult<TRecord>;
|
const res = (await this.sf.query(soql)) as SalesforceQueryResult<TRecord>;
|
||||||
return res.records ?? [];
|
return res.records ?? [];
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
this.logger.error({ error, soql, context }, `Query failed: ${context}`);
|
this.logger.error(`Query failed: ${context}`, {
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
soql,
|
||||||
|
context,
|
||||||
|
});
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,7 +50,6 @@ export class SimCatalogService extends BaseCatalogService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getActivationFees(): Promise<SimActivationFeeCatalogItem[]> {
|
async getActivationFees(): Promise<SimActivationFeeCatalogItem[]> {
|
||||||
const fields = this.getFields();
|
|
||||||
const soql = this.buildProductQuery("SIM", "Activation", []);
|
const soql = this.buildProductQuery("SIM", "Activation", []);
|
||||||
const records = await this.executeQuery<SalesforceProduct2WithPricebookEntries>(
|
const records = await this.executeQuery<SalesforceProduct2WithPricebookEntries>(
|
||||||
soql,
|
soql,
|
||||||
|
|||||||
@ -299,22 +299,24 @@ export class MappingsService {
|
|||||||
|
|
||||||
async getMappingStats(): Promise<MappingStats> {
|
async getMappingStats(): Promise<MappingStats> {
|
||||||
try {
|
try {
|
||||||
const [totalCount, whmcsCount, sfCount, completeCount] = await Promise.all([
|
const [totalCount, whmcsCount, sfCount, completeCount] = await Promise.all([
|
||||||
this.prisma.idMapping.count(),
|
this.prisma.idMapping.count(),
|
||||||
this.prisma.idMapping.count({ where: { whmcsClientId: { not: null } } }),
|
this.prisma.idMapping.count({ where: { whmcsClientId: { not: null } } }),
|
||||||
this.prisma.idMapping.count({ where: { sfAccountId: { not: null } } }),
|
this.prisma.idMapping.count({ where: { sfAccountId: { not: null } } }),
|
||||||
this.prisma.idMapping.count({ where: { whmcsClientId: { not: null }, sfAccountId: { not: null } } }),
|
this.prisma.idMapping.count({
|
||||||
]);
|
where: { whmcsClientId: { not: null }, sfAccountId: { not: null } },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
const orphanedMappings = whmcsCount - completeCount;
|
const orphanedMappings = whmcsCount - completeCount;
|
||||||
|
|
||||||
const stats: MappingStats = {
|
const stats: MappingStats = {
|
||||||
totalMappings: totalCount,
|
totalMappings: totalCount,
|
||||||
whmcsMappings: whmcsCount,
|
whmcsMappings: whmcsCount,
|
||||||
salesforceMappings: sfCount,
|
salesforceMappings: sfCount,
|
||||||
completeMappings: completeCount,
|
completeMappings: completeCount,
|
||||||
orphanedMappings: orphanedMappings < 0 ? 0 : orphanedMappings,
|
orphanedMappings: orphanedMappings < 0 ? 0 : orphanedMappings,
|
||||||
};
|
};
|
||||||
this.logger.debug("Generated mapping statistics", stats);
|
this.logger.debug("Generated mapping statistics", stats);
|
||||||
return stats;
|
return stats;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export interface OrderFulfillmentStep {
|
|||||||
export interface OrderFulfillmentContext {
|
export interface OrderFulfillmentContext {
|
||||||
sfOrderId: string;
|
sfOrderId: string;
|
||||||
idempotencyKey: string;
|
idempotencyKey: string;
|
||||||
validation: OrderFulfillmentValidationResult;
|
validation: OrderFulfillmentValidationResult | null;
|
||||||
orderDetails?: OrderDetailsResponse;
|
orderDetails?: OrderDetailsResponse;
|
||||||
mappingResult?: OrderItemMappingResult;
|
mappingResult?: OrderItemMappingResult;
|
||||||
whmcsResult?: WhmcsOrderResult;
|
whmcsResult?: WhmcsOrderResult;
|
||||||
@ -63,8 +63,10 @@ export class OrderFulfillmentOrchestrator {
|
|||||||
const context: OrderFulfillmentContext = {
|
const context: OrderFulfillmentContext = {
|
||||||
sfOrderId,
|
sfOrderId,
|
||||||
idempotencyKey,
|
idempotencyKey,
|
||||||
validation: {} as OrderFulfillmentValidationResult,
|
validation: null,
|
||||||
steps: this.initializeSteps(payload.orderType as string),
|
steps: this.initializeSteps(
|
||||||
|
typeof payload.orderType === "string" ? payload.orderType : "Unknown"
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.logger.log("Starting fulfillment orchestration", {
|
this.logger.log("Starting fulfillment orchestration", {
|
||||||
@ -81,6 +83,10 @@ export class OrderFulfillmentOrchestrator {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!context.validation) {
|
||||||
|
throw new Error("Fulfillment validation did not complete successfully");
|
||||||
|
}
|
||||||
|
|
||||||
// If already provisioned, return early
|
// If already provisioned, return early
|
||||||
if (context.validation.isAlreadyProvisioned) {
|
if (context.validation.isAlreadyProvisioned) {
|
||||||
this.markStepCompleted(context, "validation");
|
this.markStepCompleted(context, "validation");
|
||||||
@ -136,6 +142,10 @@ export class OrderFulfillmentOrchestrator {
|
|||||||
|
|
||||||
// Step 5: Create order in WHMCS
|
// Step 5: Create order in WHMCS
|
||||||
await this.executeStep(context, "whmcs_create", async () => {
|
await this.executeStep(context, "whmcs_create", async () => {
|
||||||
|
if (!context.validation) {
|
||||||
|
throw new Error("Validation context is missing");
|
||||||
|
}
|
||||||
|
|
||||||
const orderNotes = this.orderWhmcsMapper.createOrderNotes(
|
const orderNotes = this.orderWhmcsMapper.createOrderNotes(
|
||||||
sfOrderId,
|
sfOrderId,
|
||||||
`Provisioned from Salesforce Order ${sfOrderId}`
|
`Provisioned from Salesforce Order ${sfOrderId}`
|
||||||
@ -157,7 +167,7 @@ export class OrderFulfillmentOrchestrator {
|
|||||||
context.whmcsResult = {
|
context.whmcsResult = {
|
||||||
orderId: createResult.orderId,
|
orderId: createResult.orderId,
|
||||||
serviceIds: [],
|
serviceIds: [],
|
||||||
} as unknown as { orderId: number; serviceIds: number[] };
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Step 6: Accept/provision order in WHMCS
|
// Step 6: Accept/provision order in WHMCS
|
||||||
@ -373,7 +383,7 @@ export class OrderFulfillmentOrchestrator {
|
|||||||
const isSuccess = context.steps.every((s: OrderFulfillmentStep) => s.status === "completed");
|
const isSuccess = context.steps.every((s: OrderFulfillmentStep) => s.status === "completed");
|
||||||
const failedStep = context.steps.find((s: OrderFulfillmentStep) => s.status === "failed");
|
const failedStep = context.steps.find((s: OrderFulfillmentStep) => s.status === "failed");
|
||||||
|
|
||||||
if (context.validation.isAlreadyProvisioned) {
|
if (context.validation?.isAlreadyProvisioned) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
status: "Already Fulfilled",
|
status: "Already Fulfilled",
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
getOrderItemProduct2Select,
|
getOrderItemProduct2Select,
|
||||||
} from "@bff/core/config/field-map";
|
} from "@bff/core/config/field-map";
|
||||||
import { assertSalesforceId, buildInClause } from "@bff/integrations/salesforce/utils/soql.util";
|
import { assertSalesforceId, buildInClause } from "@bff/integrations/salesforce/utils/soql.util";
|
||||||
|
import { getErrorMessage } from "@bff/core/utils/error.util";
|
||||||
|
|
||||||
const fieldMap = getSalesforceFieldMap();
|
const fieldMap = getSalesforceFieldMap();
|
||||||
|
|
||||||
@ -262,8 +263,11 @@ export class OrderOrchestrator {
|
|||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
this.logger.error({ error, orderId }, "Failed to fetch order with items");
|
this.logger.error("Failed to fetch order with items", {
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
orderId,
|
||||||
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,6 @@
|
|||||||
import { getErrorMessage } from "@bff/core/utils/error.util";
|
import { getErrorMessage } from "@bff/core/utils/error.util";
|
||||||
import { Injectable, NotFoundException, Inject } from "@nestjs/common";
|
import { Injectable, NotFoundException, Inject } from "@nestjs/common";
|
||||||
import {
|
import { Subscription, SubscriptionList, InvoiceList } from "@customer-portal/domain";
|
||||||
Subscription,
|
|
||||||
SubscriptionList,
|
|
||||||
InvoiceList,
|
|
||||||
invoiceListSchema,
|
|
||||||
} from "@customer-portal/domain";
|
|
||||||
import type { Invoice, InvoiceItem } from "@customer-portal/domain";
|
import type { Invoice, InvoiceItem } from "@customer-portal/domain";
|
||||||
import { WhmcsService } from "@bff/integrations/whmcs/whmcs.service";
|
import { WhmcsService } from "@bff/integrations/whmcs/whmcs.service";
|
||||||
import { MappingsService } from "@bff/modules/id-mappings/mappings.service";
|
import { MappingsService } from "@bff/modules/id-mappings/mappings.service";
|
||||||
@ -15,6 +10,7 @@ import {
|
|||||||
subscriptionSchema,
|
subscriptionSchema,
|
||||||
type SubscriptionSchema,
|
type SubscriptionSchema,
|
||||||
} from "@customer-portal/domain/validation/shared/entities";
|
} from "@customer-portal/domain/validation/shared/entities";
|
||||||
|
import type { WhmcsProduct, WhmcsProductsResponse } from "@bff/integrations/whmcs/types/whmcs-api.types";
|
||||||
|
|
||||||
export interface GetSubscriptionsOptions {
|
export interface GetSubscriptionsOptions {
|
||||||
status?: string;
|
status?: string;
|
||||||
@ -515,20 +511,20 @@ export class SubscriptionsService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const products = await this.whmcsService.getClientsProducts({
|
const productsResponse: WhmcsProductsResponse = await this.whmcsService.getClientsProducts({
|
||||||
clientid: mapping.whmcsClientId,
|
clientid: mapping.whmcsClientId,
|
||||||
});
|
});
|
||||||
const services = products?.products?.product ?? [];
|
const services = productsResponse.products?.product ?? [];
|
||||||
|
|
||||||
return services.some(
|
return services.some((service: WhmcsProduct) => {
|
||||||
service =>
|
const group = typeof service.groupname === "string" ? service.groupname.toLowerCase() : "";
|
||||||
typeof service.groupname === "string" &&
|
const status = typeof service.status === "string" ? service.status.toLowerCase() : "";
|
||||||
service.groupname.toLowerCase().includes("sim") &&
|
return group.includes("sim") && status === "active";
|
||||||
typeof service.status === "string" &&
|
});
|
||||||
service.status.toLowerCase() === "active"
|
} catch (error: unknown) {
|
||||||
);
|
this.logger.warn(`Failed to check existing SIM for user ${userId}`, {
|
||||||
} catch (error) {
|
error: getErrorMessage(error),
|
||||||
this.logger.warn(`Failed to check existing SIM for user ${userId}`, error);
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
import { getErrorMessage } from "@bff/core/utils/error.util";
|
import { getErrorMessage } from "@bff/core/utils/error.util";
|
||||||
import { normalizeAndValidateEmail, validateUuidV4OrThrow } from "@bff/core/utils/validation.util";
|
import { normalizeAndValidateEmail, validateUuidV4OrThrow } from "@bff/core/utils/validation.util";
|
||||||
import {
|
import type { UpdateAddressRequest } from "@customer-portal/domain";
|
||||||
mapPrismaUserToSharedUser,
|
|
||||||
mapPrismaUserToEnhancedBase,
|
|
||||||
} from "@bff/infra/utils/user-mapper.util";
|
|
||||||
import type { UpdateAddressRequest, SalesforceContactRecord } from "@customer-portal/domain";
|
|
||||||
import { Injectable, Inject, NotFoundException, BadRequestException } from "@nestjs/common";
|
import { Injectable, Inject, NotFoundException, BadRequestException } from "@nestjs/common";
|
||||||
import { Logger } from "nestjs-pino";
|
import { Logger } from "nestjs-pino";
|
||||||
import { PrismaService } from "@bff/infra/database/prisma.service";
|
import { PrismaService } from "@bff/infra/database/prisma.service";
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user