173 lines
5.4 KiB
TypeScript
173 lines
5.4 KiB
TypeScript
|
|
import { Injectable, Inject } from "@nestjs/common";
|
||
|
|
import { Logger } from "nestjs-pino";
|
||
|
|
import { SimDetailsService } from "./sim-details.service";
|
||
|
|
import { SimUsageService } from "./sim-usage.service";
|
||
|
|
import { SimTopUpService } from "./sim-topup.service";
|
||
|
|
import { SimPlanService } from "./sim-plan.service";
|
||
|
|
import { SimCancellationService } from "./sim-cancellation.service";
|
||
|
|
import { EsimManagementService } from "./esim-management.service";
|
||
|
|
import { SimValidationService } from "./sim-validation.service";
|
||
|
|
import { getErrorMessage } from "@bff/core/utils/error.util";
|
||
|
|
import type {
|
||
|
|
SimDetails,
|
||
|
|
SimUsage,
|
||
|
|
SimTopUpHistory,
|
||
|
|
} from "@bff/integrations/freebit/interfaces/freebit.types";
|
||
|
|
import type {
|
||
|
|
SimTopUpRequest,
|
||
|
|
SimPlanChangeRequest,
|
||
|
|
SimCancelRequest,
|
||
|
|
SimTopUpHistoryRequest,
|
||
|
|
SimFeaturesUpdateRequest,
|
||
|
|
} from "../types/sim-requests.types";
|
||
|
|
|
||
|
|
@Injectable()
|
||
|
|
export class SimOrchestratorService {
|
||
|
|
constructor(
|
||
|
|
private readonly simDetails: SimDetailsService,
|
||
|
|
private readonly simUsage: SimUsageService,
|
||
|
|
private readonly simTopUp: SimTopUpService,
|
||
|
|
private readonly simPlan: SimPlanService,
|
||
|
|
private readonly simCancellation: SimCancellationService,
|
||
|
|
private readonly esimManagement: EsimManagementService,
|
||
|
|
private readonly simValidation: SimValidationService,
|
||
|
|
@Inject(Logger) private readonly logger: Logger
|
||
|
|
) {}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get SIM details for a subscription
|
||
|
|
*/
|
||
|
|
async getSimDetails(userId: string, subscriptionId: number): Promise<SimDetails> {
|
||
|
|
return this.simDetails.getSimDetails(userId, subscriptionId);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get SIM data usage for a subscription
|
||
|
|
*/
|
||
|
|
async getSimUsage(userId: string, subscriptionId: number): Promise<SimUsage> {
|
||
|
|
return this.simUsage.getSimUsage(userId, subscriptionId);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Top up SIM data quota with payment processing
|
||
|
|
*/
|
||
|
|
async topUpSim(userId: string, subscriptionId: number, request: SimTopUpRequest): Promise<void> {
|
||
|
|
return this.simTopUp.topUpSim(userId, subscriptionId, request);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get SIM top-up history
|
||
|
|
*/
|
||
|
|
async getSimTopUpHistory(
|
||
|
|
userId: string,
|
||
|
|
subscriptionId: number,
|
||
|
|
request: SimTopUpHistoryRequest
|
||
|
|
): Promise<SimTopUpHistory> {
|
||
|
|
return this.simUsage.getSimTopUpHistory(userId, subscriptionId, request);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Change SIM plan
|
||
|
|
*/
|
||
|
|
async changeSimPlan(
|
||
|
|
userId: string,
|
||
|
|
subscriptionId: number,
|
||
|
|
request: SimPlanChangeRequest
|
||
|
|
): Promise<{ ipv4?: string; ipv6?: string }> {
|
||
|
|
return this.simPlan.changeSimPlan(userId, subscriptionId, request);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update SIM features (voicemail, call waiting, roaming, network type)
|
||
|
|
*/
|
||
|
|
async updateSimFeatures(
|
||
|
|
userId: string,
|
||
|
|
subscriptionId: number,
|
||
|
|
request: SimFeaturesUpdateRequest
|
||
|
|
): Promise<void> {
|
||
|
|
return this.simPlan.updateSimFeatures(userId, subscriptionId, request);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Cancel SIM service
|
||
|
|
*/
|
||
|
|
async cancelSim(
|
||
|
|
userId: string,
|
||
|
|
subscriptionId: number,
|
||
|
|
request: SimCancelRequest = {}
|
||
|
|
): Promise<void> {
|
||
|
|
return this.simCancellation.cancelSim(userId, subscriptionId, request);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Reissue eSIM profile
|
||
|
|
*/
|
||
|
|
async reissueEsimProfile(userId: string, subscriptionId: number, newEid?: string): Promise<void> {
|
||
|
|
return this.esimManagement.reissueEsimProfile(userId, subscriptionId, newEid);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get comprehensive SIM information (details + usage combined)
|
||
|
|
*/
|
||
|
|
async getSimInfo(
|
||
|
|
userId: string,
|
||
|
|
subscriptionId: number
|
||
|
|
): Promise<{
|
||
|
|
details: SimDetails;
|
||
|
|
usage: SimUsage;
|
||
|
|
}> {
|
||
|
|
try {
|
||
|
|
const [details, usage] = await Promise.all([
|
||
|
|
this.getSimDetails(userId, subscriptionId),
|
||
|
|
this.getSimUsage(userId, subscriptionId),
|
||
|
|
]);
|
||
|
|
|
||
|
|
// If Freebit doesn't return remaining quota, derive it from plan code (e.g., PASI_50G)
|
||
|
|
// by subtracting measured usage (today + recentDays) from the plan cap.
|
||
|
|
const normalizeNumber = (n: number) => (isFinite(n) && n > 0 ? n : 0);
|
||
|
|
const usedMb =
|
||
|
|
normalizeNumber(usage.todayUsageMb) +
|
||
|
|
(usage.recentDaysUsage || []).reduce((sum, d) => sum + normalizeNumber(d.usageMb), 0);
|
||
|
|
|
||
|
|
const planCapMatch = (details.planCode || "").match(/(\d+)\s*G/i);
|
||
|
|
if ((details.remainingQuotaMb === 0 || details.remainingQuotaMb == null) && planCapMatch) {
|
||
|
|
const capGb = parseInt(planCapMatch[1], 10);
|
||
|
|
if (!isNaN(capGb) && capGb > 0) {
|
||
|
|
const capMb = capGb * 1000;
|
||
|
|
const remainingMb = Math.max(capMb - usedMb, 0);
|
||
|
|
details.remainingQuotaMb = Math.round(remainingMb * 100) / 100;
|
||
|
|
details.remainingQuotaKb = Math.round(details.remainingQuotaMb * 1000);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return { details, usage };
|
||
|
|
} catch (error) {
|
||
|
|
const sanitizedError = getErrorMessage(error);
|
||
|
|
this.logger.error(`Failed to get comprehensive SIM info for subscription ${subscriptionId}`, {
|
||
|
|
error: sanitizedError,
|
||
|
|
userId,
|
||
|
|
subscriptionId,
|
||
|
|
});
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Debug method to check subscription data for SIM services
|
||
|
|
*/
|
||
|
|
async debugSimSubscription(
|
||
|
|
userId: string,
|
||
|
|
subscriptionId: number
|
||
|
|
): Promise<Record<string, unknown>> {
|
||
|
|
return this.simValidation.debugSimSubscription(userId, subscriptionId);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Debug method to query Freebit directly for any account's details
|
||
|
|
* Bypasses subscription validation and queries Freebit directly
|
||
|
|
*/
|
||
|
|
async getSimDetailsDirectly(account: string): Promise<SimDetails> {
|
||
|
|
return this.simDetails.getSimDetailsDirectly(account);
|
||
|
|
}
|
||
|
|
}
|