Enhance SIM management and Freebit service with improved scheduling and error handling
- Updated SimManagementService to schedule contract line changes 30 minutes after applying voice options, improving user experience. - Refactored FreebititService to include a new method for authenticated JSON POST requests, enhancing error handling and logging for API responses. - Introduced new interfaces for voice option and contract line change requests and responses, improving type safety and clarity in API interactions. - Enhanced error handling in FreebititService to provide more specific error messages based on API response status codes.
This commit is contained in:
parent
340ff94d07
commit
74e27e3ca2
@ -640,7 +640,52 @@ export class SimManagementService {
|
||||
throw new BadRequestException('networkType must be either "4G" or "5G"');
|
||||
}
|
||||
|
||||
await this.freebititService.updateSimFeatures(account, request);
|
||||
const doVoice =
|
||||
typeof request.voiceMailEnabled === 'boolean' ||
|
||||
typeof request.callWaitingEnabled === 'boolean' ||
|
||||
typeof request.internationalRoamingEnabled === 'boolean';
|
||||
const doContract = typeof request.networkType === 'string';
|
||||
|
||||
if (doVoice && doContract) {
|
||||
// First apply voice options immediately (PA05-06)
|
||||
await this.freebititService.updateSimFeatures(account, {
|
||||
voiceMailEnabled: request.voiceMailEnabled,
|
||||
callWaitingEnabled: request.callWaitingEnabled,
|
||||
internationalRoamingEnabled: request.internationalRoamingEnabled,
|
||||
});
|
||||
|
||||
// Then schedule contract line change after 30 minutes (PA05-38)
|
||||
const delayMs = 30 * 60 * 1000;
|
||||
setTimeout(() => {
|
||||
this.freebititService
|
||||
.updateSimFeatures(account, { networkType: request.networkType })
|
||||
.then(() =>
|
||||
this.logger.log('Deferred contract line change executed after 30 minutes', {
|
||||
userId,
|
||||
subscriptionId,
|
||||
account,
|
||||
networkType: request.networkType,
|
||||
})
|
||||
)
|
||||
.catch(err =>
|
||||
this.logger.error('Deferred contract line change failed', {
|
||||
error: getErrorMessage(err),
|
||||
userId,
|
||||
subscriptionId,
|
||||
account,
|
||||
})
|
||||
);
|
||||
}, delayMs);
|
||||
|
||||
this.logger.log('Scheduled contract line change 30 minutes after voice option change', {
|
||||
userId,
|
||||
subscriptionId,
|
||||
account,
|
||||
networkType: request.networkType,
|
||||
});
|
||||
} else {
|
||||
await this.freebititService.updateSimFeatures(account, request);
|
||||
}
|
||||
|
||||
this.logger.log(`Updated SIM features for subscription ${subscriptionId}`, {
|
||||
userId,
|
||||
|
||||
119
apps/bff/src/vendors/freebit/freebit.service.ts
vendored
119
apps/bff/src/vendors/freebit/freebit.service.ts
vendored
@ -182,10 +182,16 @@ export class FreebititService {
|
||||
|
||||
const responseData = (await response.json()) as T;
|
||||
|
||||
// Check for API-level errors
|
||||
const rc = String(responseData?.resultCode ?? "");
|
||||
if (rc !== "100") {
|
||||
const errorMessage = String(responseData.status?.message ?? "Unknown error");
|
||||
// Check for API-level errors (some endpoints return resultCode '101' with message 'OK')
|
||||
const rc = String((responseData as any)?.resultCode ?? "");
|
||||
const statusObj: any = (responseData as any)?.status ?? {};
|
||||
const errorMessage = String((statusObj?.message ?? (responseData as any)?.message ?? "Unknown error"));
|
||||
const statusCodeStr = String(statusObj?.statusCode ?? (responseData as any)?.statusCode ?? "");
|
||||
const msgUpper = errorMessage.toUpperCase();
|
||||
const isOkByRc = rc === "100" || rc === "101";
|
||||
const isOkByMessage = msgUpper === "OK" || msgUpper === "SUCCESS";
|
||||
const isOkByStatus = statusCodeStr === "200";
|
||||
if (!(isOkByRc || isOkByMessage || isOkByStatus)) {
|
||||
|
||||
// Provide more specific error messages for common cases
|
||||
let userFriendlyMessage = `API Error: ${errorMessage}`;
|
||||
@ -200,7 +206,7 @@ export class FreebititService {
|
||||
this.logger.error("Freebit API error response", {
|
||||
endpoint,
|
||||
resultCode: rc,
|
||||
statusCode: responseData.status?.statusCode,
|
||||
statusCode: statusCodeStr,
|
||||
message: errorMessage,
|
||||
userFriendlyMessage,
|
||||
});
|
||||
@ -208,7 +214,7 @@ export class FreebititService {
|
||||
throw new FreebititErrorImpl(
|
||||
userFriendlyMessage,
|
||||
rc,
|
||||
String(responseData.status?.statusCode ?? ""),
|
||||
statusCodeStr,
|
||||
errorMessage
|
||||
);
|
||||
}
|
||||
@ -230,6 +236,42 @@ export class FreebititService {
|
||||
}
|
||||
}
|
||||
|
||||
// Make authenticated JSON POST request (for endpoints that require JSON body)
|
||||
private async makeAuthenticatedJsonRequest<T>(endpoint: string, body: Record<string, unknown>): Promise<T> {
|
||||
const authKey = await this.getAuthKey();
|
||||
const url = `${this.config.baseUrl}${endpoint}`;
|
||||
const payload = { ...body, authKey };
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text().catch(() => null);
|
||||
this.logger.error('Freebit JSON API non-OK', {
|
||||
endpoint,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body: text?.slice(0, 500),
|
||||
});
|
||||
throw new InternalServerErrorException(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
const data = (await response.json()) as T;
|
||||
const rc = String((data as any)?.resultCode ?? '');
|
||||
if (rc !== '100') {
|
||||
const message = (data as any)?.message || (data as any)?.status?.message || 'Unknown error';
|
||||
this.logger.error('Freebit JSON API error response', {
|
||||
endpoint,
|
||||
resultCode: rc,
|
||||
statusCode: (data as any)?.statusCode || (data as any)?.status?.statusCode,
|
||||
message,
|
||||
});
|
||||
throw new FreebititErrorImpl(`API Error: ${message}`, rc, String((data as any)?.statusCode || ''), message);
|
||||
}
|
||||
this.logger.debug('Freebit JSON API Request Success', { endpoint, resultCode: rc });
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed SIM account information
|
||||
*/
|
||||
@ -602,30 +644,48 @@ export class FreebititService {
|
||||
}
|
||||
): Promise<void> {
|
||||
try {
|
||||
const request: Omit<FreebititAddSpecRequest, "authKey"> = {
|
||||
account,
|
||||
kind: "MVNO",
|
||||
};
|
||||
const doVoice =
|
||||
typeof features.voiceMailEnabled === 'boolean' ||
|
||||
typeof features.callWaitingEnabled === 'boolean' ||
|
||||
typeof features.internationalRoamingEnabled === 'boolean';
|
||||
const doContract = typeof features.networkType === 'string';
|
||||
|
||||
if (typeof features.voiceMailEnabled === "boolean") {
|
||||
request.voiceMail = features.voiceMailEnabled ? ("10" as const) : ("20" as const);
|
||||
request.voicemail = request.voiceMail; // include alternate casing for compatibility
|
||||
}
|
||||
if (typeof features.callWaitingEnabled === "boolean") {
|
||||
request.callWaiting = features.callWaitingEnabled ? ("10" as const) : ("20" as const);
|
||||
request.callwaiting = request.callWaiting;
|
||||
}
|
||||
if (typeof features.internationalRoamingEnabled === "boolean") {
|
||||
request.worldWing = features.internationalRoamingEnabled
|
||||
? ("10" as const)
|
||||
: ("20" as const);
|
||||
request.worldwing = request.worldWing;
|
||||
}
|
||||
if (features.networkType) {
|
||||
request.contractLine = features.networkType;
|
||||
if (doVoice) {
|
||||
const talkOption: any = {};
|
||||
if (typeof features.voiceMailEnabled === 'boolean') {
|
||||
talkOption.voiceMail = features.voiceMailEnabled ? '10' : '20';
|
||||
}
|
||||
if (typeof features.callWaitingEnabled === 'boolean') {
|
||||
talkOption.callWaiting = features.callWaitingEnabled ? '10' : '20';
|
||||
}
|
||||
if (typeof features.internationalRoamingEnabled === 'boolean') {
|
||||
talkOption.worldWing = features.internationalRoamingEnabled ? '10' : '20';
|
||||
}
|
||||
await this.makeAuthenticatedRequest<import('./interfaces/freebit.types').FreebititVoiceOptionChangeResponse>(
|
||||
'/mvno/talkoption/changeOrder/',
|
||||
{
|
||||
account,
|
||||
userConfirmed: '10',
|
||||
aladinOperated: '10',
|
||||
talkOption,
|
||||
}
|
||||
);
|
||||
this.logger.log('Applied voice option change (PA05-06)', { account, talkOption });
|
||||
}
|
||||
|
||||
await this.makeAuthenticatedRequest<FreebititAddSpecResponse>("/master/addSpec/", request);
|
||||
if (doContract && features.networkType) {
|
||||
await this.makeAuthenticatedJsonRequest<import('./interfaces/freebit.types').FreebititContractLineChangeResponse>(
|
||||
'/mvno/contractline/change/',
|
||||
{
|
||||
account,
|
||||
contractLine: features.networkType,
|
||||
}
|
||||
);
|
||||
this.logger.log('Applied contract line change (PA05-38)', {
|
||||
account,
|
||||
contractLine: features.networkType,
|
||||
});
|
||||
}
|
||||
|
||||
this.logger.log(`Updated SIM features for account ${account}`, {
|
||||
account,
|
||||
@ -636,10 +696,7 @@ export class FreebititService {
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
this.logger.error(`Failed to update SIM features for account ${account}`, {
|
||||
error: message,
|
||||
account,
|
||||
});
|
||||
this.logger.error(`Failed to update SIM features for account ${account}`, { error: message, account });
|
||||
throw error as Error;
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,6 +187,48 @@ export interface FreebititPlanChangeResponse {
|
||||
ipv6?: string;
|
||||
}
|
||||
|
||||
// PA05-06: MVNO Voice Option Change
|
||||
export interface FreebititVoiceOptionChangeRequest {
|
||||
authKey: string;
|
||||
account: string;
|
||||
userConfirmed: '10' | '20';
|
||||
aladinOperated: '10' | '20';
|
||||
talkOption: {
|
||||
voiceMail?: '10' | '20';
|
||||
callWaiting?: '10' | '20';
|
||||
worldWing?: '10' | '20';
|
||||
worldCall?: '10' | '20';
|
||||
callTransfer?: '10' | '20';
|
||||
callTransferNoId?: '10' | '20';
|
||||
worldCallCreditLimit?: string;
|
||||
worldWingCreditLimit?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface FreebititVoiceOptionChangeResponse {
|
||||
resultCode: string;
|
||||
status: {
|
||||
message: string;
|
||||
statusCode: string;
|
||||
};
|
||||
}
|
||||
|
||||
// PA05-38: MVNO Contract Change (4G/5G)
|
||||
export interface FreebititContractLineChangeRequest {
|
||||
authKey: string;
|
||||
account: string;
|
||||
contractLine: '4G' | '5G';
|
||||
productNumber?: string;
|
||||
eid?: string;
|
||||
}
|
||||
|
||||
export interface FreebititContractLineChangeResponse {
|
||||
resultCode: string | number;
|
||||
status?: unknown;
|
||||
statusCode?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface FreebititCancelPlanRequest {
|
||||
authKey: string;
|
||||
account: string;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user