208 lines
6.8 KiB
TypeScript
208 lines
6.8 KiB
TypeScript
|
|
import { Injectable, Inject, BadRequestException } from "@nestjs/common";
|
||
|
|
import { Logger } from "nestjs-pino";
|
||
|
|
import { FreebitOrchestratorService } from "@bff/integrations/freebit/services/freebit-orchestrator.service";
|
||
|
|
import { SimValidationService } from "./sim-validation.service";
|
||
|
|
import { SimNotificationService } from "./sim-notification.service";
|
||
|
|
import { getErrorMessage } from "@bff/core/utils/error.util";
|
||
|
|
import type { SimPlanChangeRequest, SimFeaturesUpdateRequest } from "../types/sim-requests.types";
|
||
|
|
|
||
|
|
@Injectable()
|
||
|
|
export class SimPlanService {
|
||
|
|
constructor(
|
||
|
|
private readonly freebitService: FreebitOrchestratorService,
|
||
|
|
private readonly simValidation: SimValidationService,
|
||
|
|
private readonly simNotification: SimNotificationService,
|
||
|
|
@Inject(Logger) private readonly logger: Logger
|
||
|
|
) {}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Change SIM plan
|
||
|
|
*/
|
||
|
|
async changeSimPlan(
|
||
|
|
userId: string,
|
||
|
|
subscriptionId: number,
|
||
|
|
request: SimPlanChangeRequest
|
||
|
|
): Promise<{ ipv4?: string; ipv6?: string }> {
|
||
|
|
try {
|
||
|
|
const { account } = await this.simValidation.validateSimSubscription(userId, subscriptionId);
|
||
|
|
|
||
|
|
// Validate plan code format - simplified for 5GB, 10GB, 25GB, 50GB plans
|
||
|
|
if (!request.newPlanCode || !["5GB", "10GB", "25GB", "50GB"].includes(request.newPlanCode)) {
|
||
|
|
throw new BadRequestException("Invalid plan code. Must be one of: 5GB, 10GB, 25GB, 50GB");
|
||
|
|
}
|
||
|
|
|
||
|
|
// Map simplified plan codes to Freebit plan codes
|
||
|
|
const planCodeMapping: Record<string, string> = {
|
||
|
|
"5GB": "PASI_5G",
|
||
|
|
"10GB": "PASI_10G",
|
||
|
|
"25GB": "PASI_25G",
|
||
|
|
"50GB": "PASI_50G",
|
||
|
|
};
|
||
|
|
|
||
|
|
const freebitPlanCode = planCodeMapping[request.newPlanCode];
|
||
|
|
if (!freebitPlanCode) {
|
||
|
|
throw new BadRequestException(`Unable to map plan code ${request.newPlanCode} to Freebit format`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Automatically set to 1st of next month
|
||
|
|
const nextMonth = new Date();
|
||
|
|
nextMonth.setMonth(nextMonth.getMonth() + 1);
|
||
|
|
nextMonth.setDate(1); // Set to 1st of the month
|
||
|
|
|
||
|
|
// Format as YYYYMMDD for Freebit API
|
||
|
|
const year = nextMonth.getFullYear();
|
||
|
|
const month = String(nextMonth.getMonth() + 1).padStart(2, "0");
|
||
|
|
const day = String(nextMonth.getDate()).padStart(2, "0");
|
||
|
|
const scheduledAt = `${year}${month}${day}`;
|
||
|
|
|
||
|
|
this.logger.log(`Auto-scheduled plan change to 1st of next month: ${scheduledAt}`, {
|
||
|
|
userId,
|
||
|
|
subscriptionId,
|
||
|
|
account,
|
||
|
|
newPlanCode: request.newPlanCode,
|
||
|
|
freebitPlanCode,
|
||
|
|
});
|
||
|
|
|
||
|
|
// First try immediate change, fallback to scheduled if that fails
|
||
|
|
let result: { ipv4?: string; ipv6?: string };
|
||
|
|
|
||
|
|
try {
|
||
|
|
result = await this.freebitService.changeSimPlan(account, freebitPlanCode, {
|
||
|
|
assignGlobalIp: false, // No global IP assignment
|
||
|
|
// Try immediate first
|
||
|
|
});
|
||
|
|
|
||
|
|
this.logger.log(`Immediate plan change successful for account ${account}`, {
|
||
|
|
account,
|
||
|
|
newPlanCode: request.newPlanCode,
|
||
|
|
freebitPlanCode,
|
||
|
|
});
|
||
|
|
} catch (immediateError) {
|
||
|
|
this.logger.warn(`Immediate plan change failed, trying scheduled: ${getErrorMessage(immediateError)}`, {
|
||
|
|
account,
|
||
|
|
newPlanCode: request.newPlanCode,
|
||
|
|
freebitPlanCode,
|
||
|
|
error: getErrorMessage(immediateError),
|
||
|
|
});
|
||
|
|
|
||
|
|
// Fallback to scheduled change
|
||
|
|
result = await this.freebitService.changeSimPlan(account, freebitPlanCode, {
|
||
|
|
assignGlobalIp: false, // No global IP assignment
|
||
|
|
scheduledAt: scheduledAt,
|
||
|
|
});
|
||
|
|
|
||
|
|
this.logger.log(`Scheduled plan change successful for account ${account}`, {
|
||
|
|
account,
|
||
|
|
newPlanCode: request.newPlanCode,
|
||
|
|
freebitPlanCode,
|
||
|
|
scheduledAt,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
this.logger.log(`Successfully changed SIM plan for subscription ${subscriptionId}`, {
|
||
|
|
userId,
|
||
|
|
subscriptionId,
|
||
|
|
account,
|
||
|
|
newPlanCode: request.newPlanCode,
|
||
|
|
freebitPlanCode,
|
||
|
|
scheduledAt: scheduledAt,
|
||
|
|
assignGlobalIp: false,
|
||
|
|
});
|
||
|
|
|
||
|
|
await this.simNotification.notifySimAction("Change Plan", "SUCCESS", {
|
||
|
|
userId,
|
||
|
|
subscriptionId,
|
||
|
|
account,
|
||
|
|
newPlanCode: request.newPlanCode,
|
||
|
|
freebitPlanCode,
|
||
|
|
scheduledAt,
|
||
|
|
});
|
||
|
|
|
||
|
|
return result;
|
||
|
|
} catch (error) {
|
||
|
|
const sanitizedError = getErrorMessage(error);
|
||
|
|
this.logger.error(`Failed to change SIM plan for subscription ${subscriptionId}`, {
|
||
|
|
error: sanitizedError,
|
||
|
|
userId,
|
||
|
|
subscriptionId,
|
||
|
|
newPlanCode: request.newPlanCode,
|
||
|
|
});
|
||
|
|
|
||
|
|
await this.simNotification.notifySimAction("Change Plan", "ERROR", {
|
||
|
|
userId,
|
||
|
|
subscriptionId,
|
||
|
|
account: "unknown",
|
||
|
|
newPlanCode: request.newPlanCode,
|
||
|
|
error: sanitizedError,
|
||
|
|
});
|
||
|
|
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update SIM features (voicemail, call waiting, roaming, network type)
|
||
|
|
*/
|
||
|
|
async updateSimFeatures(
|
||
|
|
userId: string,
|
||
|
|
subscriptionId: number,
|
||
|
|
request: SimFeaturesUpdateRequest
|
||
|
|
): Promise<void> {
|
||
|
|
try {
|
||
|
|
const { account } = await this.simValidation.validateSimSubscription(userId, subscriptionId);
|
||
|
|
|
||
|
|
// Validate network type if provided
|
||
|
|
if (request.networkType && !["4G", "5G"].includes(request.networkType)) {
|
||
|
|
throw new BadRequestException('networkType must be either "4G" or "5G"');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Log the request for debugging
|
||
|
|
this.logger.log(`Updating SIM features for subscription ${subscriptionId}`, {
|
||
|
|
userId,
|
||
|
|
subscriptionId,
|
||
|
|
account,
|
||
|
|
request,
|
||
|
|
});
|
||
|
|
|
||
|
|
// Update all features in one call - the FreebitOperationsService will handle the complexity
|
||
|
|
await this.freebitService.updateSimFeatures(account, {
|
||
|
|
voiceMailEnabled: request.voiceMailEnabled,
|
||
|
|
callWaitingEnabled: request.callWaitingEnabled,
|
||
|
|
internationalRoamingEnabled: request.internationalRoamingEnabled,
|
||
|
|
networkType: request.networkType,
|
||
|
|
});
|
||
|
|
|
||
|
|
this.logger.log(`Successfully updated SIM features for subscription ${subscriptionId}`, {
|
||
|
|
userId,
|
||
|
|
subscriptionId,
|
||
|
|
account,
|
||
|
|
...request,
|
||
|
|
});
|
||
|
|
|
||
|
|
await this.simNotification.notifySimAction("Update Features", "SUCCESS", {
|
||
|
|
userId,
|
||
|
|
subscriptionId,
|
||
|
|
account,
|
||
|
|
...request,
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
const sanitizedError = getErrorMessage(error);
|
||
|
|
this.logger.error(`Failed to update SIM features for subscription ${subscriptionId}`, {
|
||
|
|
error: sanitizedError,
|
||
|
|
userId,
|
||
|
|
subscriptionId,
|
||
|
|
account: "unknown",
|
||
|
|
...request,
|
||
|
|
errorStack: error instanceof Error ? error.stack : undefined,
|
||
|
|
});
|
||
|
|
await this.simNotification.notifySimAction("Update Features", "ERROR", {
|
||
|
|
userId,
|
||
|
|
subscriptionId,
|
||
|
|
...request,
|
||
|
|
error: sanitizedError,
|
||
|
|
});
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|