feat: add MNP support for Physical SIM (PA05-19) and fix eSIM MNP bugs (PA05-41)
Physical SIM: route MNP orders through PA05-19 (semi-black registration) instead of PA02-01. eSIM: fix PA05-41 payload — move identity fields into mnp object (Level 2 nesting per spec), set addKind="M" and aladinOperated="20" for MNP, map Salesforce gender "F" to Freebit "W", and pass simKind="E0". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
df017d520f
commit
891d3aa099
@ -208,14 +208,11 @@ export class FreebitFacade {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a semi-black SIM account (PA05-18)
|
* Register a semi-black SIM account (PA05-19)
|
||||||
*
|
*
|
||||||
* This MUST be called BEFORE PA02-01 for physical SIMs.
|
* For MNP transfers on physical SIMs, this replaces PA02-01.
|
||||||
* Semi-black SIMs are pre-provisioned physical SIMs that need to be registered
|
* Semi-black SIMs are pre-provisioned physical SIMs that need to be registered
|
||||||
* to associate them with a customer account and plan.
|
* to associate them with a customer account and plan.
|
||||||
*
|
|
||||||
* Error 210 "アカウント不在エラー" on PA02-01 indicates that PA05-18
|
|
||||||
* was not called first.
|
|
||||||
*/
|
*/
|
||||||
async registerSemiBlackAccount(params: {
|
async registerSemiBlackAccount(params: {
|
||||||
account: string;
|
account: string;
|
||||||
|
|||||||
@ -319,15 +319,15 @@ export interface FreebitEsimAccountActivationRequest {
|
|||||||
oldEid?: string; // Row 14: 元eSIM識別番号 (Conditional - for exchange)
|
oldEid?: string; // Row 14: 元eSIM識別番号 (Conditional - for exchange)
|
||||||
mnp?: {
|
mnp?: {
|
||||||
// Row 15: MNP情報 (Conditional)
|
// Row 15: MNP情報 (Conditional)
|
||||||
reserveNumber: string; // Row 16: MNP予約番号 (Conditional)
|
reserveNumber: string; // Row 16: MNP予約番号 (Level 2)
|
||||||
reserveExpireDate?: string; // (Conditional) YYYYMMDD
|
reserveExpireDate?: string; // (Level 2) YYYYMMDD
|
||||||
|
lastnameKanji?: string; // Row 17: 名前(漢字) (Level 2)
|
||||||
|
firstnameKanji?: string; // Row 18: 由字(漢字) (Level 2)
|
||||||
|
lastnameZenKana?: string; // Row 19: 名前(全角カタカナ) (Level 2)
|
||||||
|
firstnameZenKana?: string; // Row 20: 由字(全角カタカナ) (Level 2)
|
||||||
|
gender?: string; // Row 21: 性別 ('M'/'W'/'C') (Level 2)
|
||||||
|
birthday?: string; // Row 22: 生年月日 YYYYMMDD (Level 2)
|
||||||
};
|
};
|
||||||
firstnameKanji?: string; // Row 17: 由字(漢字) (Conditional)
|
|
||||||
lastnameKanji?: string; // Row 18: 名前(漢字) (Conditional)
|
|
||||||
firstnameZenKana?: string; // Row 19: 由字(全角カタカナ) (Conditional)
|
|
||||||
lastnameZenKana?: string; // Row 20: 名前(全角カタカナ) (Conditional)
|
|
||||||
gender?: string; // Row 21: 性別 ('M', 'F') (Required for identification)
|
|
||||||
birthday?: string; // Row 22: 生年月日 YYYYMMDD (Conditional)
|
|
||||||
shipDate?: string; // Row 23: 出荷日 YYYYMMDD (Conditional)
|
shipDate?: string; // Row 23: 出荷日 YYYYMMDD (Conditional)
|
||||||
planCode?: string; // Row 24: プランコード (Max 32 chars) (Conditional)
|
planCode?: string; // Row 24: プランコード (Max 32 chars) (Conditional)
|
||||||
deliveryCode?: string; // Row 25: 顧客コード (Max 10 chars) (Conditional - OEM specific)
|
deliveryCode?: string; // Row 25: 顧客コード (Max 10 chars) (Conditional - OEM specific)
|
||||||
|
|||||||
@ -24,13 +24,14 @@ export interface EsimActivationParams {
|
|||||||
repAccount?: string;
|
repAccount?: string;
|
||||||
deliveryCode?: string;
|
deliveryCode?: string;
|
||||||
globalIp?: "10" | "20";
|
globalIp?: "10" | "20";
|
||||||
mnp?: { reserveNumber: string; reserveExpireDate?: string };
|
mnp?: {
|
||||||
identity?: {
|
reserveNumber: string;
|
||||||
firstnameKanji?: string;
|
reserveExpireDate?: string;
|
||||||
lastnameKanji?: string;
|
lastnameKanji?: string;
|
||||||
firstnameZenKana?: string;
|
firstnameKanji?: string;
|
||||||
lastnameZenKana?: string;
|
lastnameZenKana?: string;
|
||||||
gender?: string;
|
firstnameZenKana?: string;
|
||||||
|
gender?: string; // Freebit uses 'M'/'W'/'C' (caller should map 'F' → 'W')
|
||||||
birthday?: string;
|
birthday?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -128,7 +129,6 @@ export class FreebitEsimService {
|
|||||||
deliveryCode,
|
deliveryCode,
|
||||||
globalIp,
|
globalIp,
|
||||||
mnp,
|
mnp,
|
||||||
identity,
|
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
if (!account || !eid) {
|
if (!account || !eid) {
|
||||||
@ -144,6 +144,19 @@ export class FreebitEsimService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.log("PA05-41 preparing eSIM activation payload", {
|
||||||
|
account,
|
||||||
|
addKind: finalAddKind,
|
||||||
|
simKind: simKind || "E0",
|
||||||
|
aladinOperated,
|
||||||
|
hasMnp: !!mnp,
|
||||||
|
hasReserveNumber: !!mnp?.reserveNumber,
|
||||||
|
hasIdentity: !!(mnp?.lastnameKanji || mnp?.firstnameKanji),
|
||||||
|
gender: mnp?.gender,
|
||||||
|
planCode,
|
||||||
|
contractLine,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload: FreebitEsimAccountActivationRequest = {
|
const payload: FreebitEsimAccountActivationRequest = {
|
||||||
authKey: await this.auth.getAuthKey(),
|
authKey: await this.auth.getAuthKey(),
|
||||||
@ -159,10 +172,19 @@ export class FreebitEsimService {
|
|||||||
repAccount,
|
repAccount,
|
||||||
deliveryCode,
|
deliveryCode,
|
||||||
globalIp,
|
globalIp,
|
||||||
|
// MNP object includes both reservation info AND identity fields (all Level 2 per PA05-41 spec)
|
||||||
...(mnp ? { mnp } : {}),
|
...(mnp ? { mnp } : {}),
|
||||||
...(identity ? identity : {}),
|
|
||||||
} as FreebitEsimAccountActivationRequest;
|
} as FreebitEsimAccountActivationRequest;
|
||||||
|
|
||||||
|
this.logger.log("PA05-41 sending request", {
|
||||||
|
account,
|
||||||
|
addKind: finalAddKind,
|
||||||
|
aladinOperated,
|
||||||
|
mnpReserveNumber: mnp?.reserveNumber,
|
||||||
|
mnpHasIdentity: !!(mnp?.lastnameKanji || mnp?.firstnameKanji),
|
||||||
|
mnpGender: mnp?.gender,
|
||||||
|
});
|
||||||
|
|
||||||
await this.client.makeAuthenticatedJsonRequest<
|
await this.client.makeAuthenticatedJsonRequest<
|
||||||
FreebitEsimAccountActivationResponse,
|
FreebitEsimAccountActivationResponse,
|
||||||
FreebitEsimAccountActivationRequest
|
FreebitEsimAccountActivationRequest
|
||||||
@ -172,9 +194,9 @@ export class FreebitEsimService {
|
|||||||
account,
|
account,
|
||||||
planCode,
|
planCode,
|
||||||
contractLine,
|
contractLine,
|
||||||
addKind: addKind || "N",
|
addKind: finalAddKind,
|
||||||
scheduled: !!shipDate,
|
scheduled: !!shipDate,
|
||||||
mnp: !!mnp,
|
isMnp: finalAddKind === "M",
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = extractErrorMessage(error);
|
const message = extractErrorMessage(error);
|
||||||
@ -182,7 +204,9 @@ export class FreebitEsimService {
|
|||||||
account,
|
account,
|
||||||
eid,
|
eid,
|
||||||
planCode,
|
planCode,
|
||||||
addKind,
|
addKind: finalAddKind,
|
||||||
|
aladinOperated,
|
||||||
|
isMnp: finalAddKind === "M",
|
||||||
error: message,
|
error: message,
|
||||||
});
|
});
|
||||||
throw new BadRequestException(`Failed to activate new eSIM account: ${message}`);
|
throw new BadRequestException(`Failed to activate new eSIM account: ${message}`);
|
||||||
|
|||||||
@ -1,15 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Freebit Semi-Black Account Registration Service (PA05-18)
|
* Freebit Semi-Black Account Registration Service (PA05-19)
|
||||||
*
|
*
|
||||||
* Handles MVNO semi-black (半黒) SIM registration via the Freebit PA05-18 API.
|
* Handles MVNO semi-black (半黒) SIM registration via the Freebit PA05-19 API.
|
||||||
* This must be called BEFORE PA02-01 for physical SIMs to create the account
|
* For MNP transfers, this replaces PA02-01. For non-MNP, PA02-01 is used instead.
|
||||||
* in Freebit's system.
|
|
||||||
*
|
*
|
||||||
* Semi-black SIMs are pre-provisioned SIMs that need to be registered
|
* Semi-black SIMs are pre-provisioned SIMs that need to be registered
|
||||||
* to associate them with a customer account and plan.
|
* to associate them with a customer account and plan.
|
||||||
*
|
|
||||||
* Error 210 "アカウント不在エラー" on PA02-01 indicates that PA05-18
|
|
||||||
* was not called first.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable, Inject, BadRequestException } from "@nestjs/common";
|
import { Injectable, Inject, BadRequestException } from "@nestjs/common";
|
||||||
@ -18,7 +14,7 @@ import { extractErrorMessage } from "@bff/core/utils/error.util.js";
|
|||||||
import { FreebitClientService } from "./freebit-client.service.js";
|
import { FreebitClientService } from "./freebit-client.service.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PA05-18 Semi-Black Account Registration parameters
|
* PA05-19 Semi-Black Account Registration parameters
|
||||||
*/
|
*/
|
||||||
export interface SemiBlackRegistrationParams {
|
export interface SemiBlackRegistrationParams {
|
||||||
/** MSISDN (phone number) - 11-14 digits */
|
/** MSISDN (phone number) - 11-14 digits */
|
||||||
@ -40,7 +36,7 @@ export interface SemiBlackRegistrationParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PA05-18 Request payload structure
|
* PA05-19 Request payload structure
|
||||||
*/
|
*/
|
||||||
interface FreebitSemiBlackRequest {
|
interface FreebitSemiBlackRequest {
|
||||||
authKey: string;
|
authKey: string;
|
||||||
@ -58,7 +54,7 @@ interface FreebitSemiBlackRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PA05-18 Response structure
|
* PA05-19 Response structure
|
||||||
*/
|
*/
|
||||||
interface FreebitSemiBlackResponse {
|
interface FreebitSemiBlackResponse {
|
||||||
resultCode: number;
|
resultCode: number;
|
||||||
@ -76,10 +72,10 @@ export class FreebitSemiBlackService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a semi-black SIM account (PA05-18)
|
* Register a semi-black SIM account (PA05-19)
|
||||||
*
|
*
|
||||||
* This registers a pre-provisioned (semi-black) physical SIM in Freebit's
|
* This registers a pre-provisioned (semi-black) physical SIM in Freebit's
|
||||||
* system. Must be called BEFORE PA02-01 account registration.
|
* system. For MNP transfers, this replaces PA02-01.
|
||||||
*
|
*
|
||||||
* @param params - Semi-black registration parameters
|
* @param params - Semi-black registration parameters
|
||||||
* @throws BadRequestException if registration fails
|
* @throws BadRequestException if registration fails
|
||||||
@ -117,7 +113,7 @@ export class FreebitSemiBlackService {
|
|||||||
// Default to today's date if not provided
|
// Default to today's date if not provided
|
||||||
const effectiveShipDate = shipDate ?? this.formatTodayAsYYYYMMDD();
|
const effectiveShipDate = shipDate ?? this.formatTodayAsYYYYMMDD();
|
||||||
|
|
||||||
this.logger.log("Starting semi-black SIM registration (PA05-18)", {
|
this.logger.log("Starting semi-black SIM registration (PA05-19)", {
|
||||||
account,
|
account,
|
||||||
productNumber,
|
productNumber,
|
||||||
planCode,
|
planCode,
|
||||||
@ -146,7 +142,7 @@ export class FreebitSemiBlackService {
|
|||||||
Omit<FreebitSemiBlackRequest, "authKey">
|
Omit<FreebitSemiBlackRequest, "authKey">
|
||||||
>("/mvno/semiblack/addAcnt/", payload);
|
>("/mvno/semiblack/addAcnt/", payload);
|
||||||
|
|
||||||
this.logger.log("Semi-black SIM registration successful (PA05-18)", {
|
this.logger.log("Semi-black SIM registration successful (PA05-19)", {
|
||||||
account,
|
account,
|
||||||
productNumber,
|
productNumber,
|
||||||
planCode,
|
planCode,
|
||||||
@ -158,7 +154,7 @@ export class FreebitSemiBlackService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const message = extractErrorMessage(error);
|
const message = extractErrorMessage(error);
|
||||||
this.logger.error("Semi-black registration failed (PA05-18)", {
|
this.logger.error("Semi-black registration failed (PA05-19)", {
|
||||||
account,
|
account,
|
||||||
productNumber,
|
productNumber,
|
||||||
planCode,
|
planCode,
|
||||||
|
|||||||
@ -64,7 +64,7 @@ export class FulfillmentStepExecutors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SIM fulfillment via Freebit (PA05-18 + PA02-01 + PA05-05)
|
* SIM fulfillment via Freebit (PA05-19/PA02-01 + PA05-05)
|
||||||
*/
|
*/
|
||||||
async executeSimFulfillment(
|
async executeSimFulfillment(
|
||||||
ctx: OrderFulfillmentContext,
|
ctx: OrderFulfillmentContext,
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export class FulfillmentStepFactory {
|
|||||||
* Step order:
|
* Step order:
|
||||||
* 1. sf_status_update (Activating)
|
* 1. sf_status_update (Activating)
|
||||||
* 2. order_details (retain in context)
|
* 2. order_details (retain in context)
|
||||||
* 3. sim_fulfillment (SIM orders only - PA05-18 + PA02-01 + PA05-05)
|
* 3. sim_fulfillment (SIM orders only - PA05-19/PA02-01 + PA05-05)
|
||||||
* 4. sf_activated_update (SIM orders only)
|
* 4. sf_activated_update (SIM orders only)
|
||||||
* 5. mapping (with SIM data for WHMCS)
|
* 5. mapping (with SIM data for WHMCS)
|
||||||
* 6. whmcs_create
|
* 6. whmcs_create
|
||||||
@ -111,7 +111,7 @@ export class FulfillmentStepFactory {
|
|||||||
): DistributedStep {
|
): DistributedStep {
|
||||||
return {
|
return {
|
||||||
id: "sim_fulfillment",
|
id: "sim_fulfillment",
|
||||||
description: "SIM activation via Freebit (PA05-18 + PA02-01 + PA05-05)",
|
description: "SIM activation via Freebit (PA05-19/PA02-01 + PA05-05)",
|
||||||
execute: this.createTrackedStep(ctx, "sim_fulfillment", async () => {
|
execute: this.createTrackedStep(ctx, "sim_fulfillment", async () => {
|
||||||
const result = await this.executors.executeSimFulfillment(ctx, payload);
|
const result = await this.executors.executeSimFulfillment(ctx, payload);
|
||||||
state.simFulfillmentResult = result;
|
state.simFulfillmentResult = result;
|
||||||
|
|||||||
@ -67,6 +67,31 @@ export interface SimFulfillmentResult {
|
|||||||
eid?: string;
|
eid?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MNP configuration extracted from Salesforce order/porting fields
|
||||||
|
*/
|
||||||
|
interface MnpConfig {
|
||||||
|
reserveNumber?: string;
|
||||||
|
reserveExpireDate?: string;
|
||||||
|
account?: string;
|
||||||
|
firstnameKanji?: string;
|
||||||
|
lastnameKanji?: string;
|
||||||
|
firstnameZenKana?: string;
|
||||||
|
lastnameZenKana?: string;
|
||||||
|
gender?: string;
|
||||||
|
birthday?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map Salesforce gender code to Freebit gender code.
|
||||||
|
* Salesforce: 'M' (Male), 'F' (Female)
|
||||||
|
* Freebit: 'M' (Male), 'W' (Weiblich/Female), 'C' (Corporation)
|
||||||
|
*/
|
||||||
|
function mapGenderToFreebit(gender: string): string {
|
||||||
|
if (gender === "F") return "W";
|
||||||
|
return gender;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SimFulfillmentService {
|
export class SimFulfillmentService {
|
||||||
constructor(
|
constructor(
|
||||||
@ -115,6 +140,16 @@ export class SimFulfillmentService {
|
|||||||
const scheduledAt = this.readString(configurations["scheduledAt"]);
|
const scheduledAt = this.readString(configurations["scheduledAt"]);
|
||||||
const phoneNumber = this.readString(configurations["mnpPhone"]);
|
const phoneNumber = this.readString(configurations["mnpPhone"]);
|
||||||
const mnp = this.extractMnpConfig(configurations);
|
const mnp = this.extractMnpConfig(configurations);
|
||||||
|
const isMnp = !!mnp?.reserveNumber;
|
||||||
|
|
||||||
|
this.logger.log("MNP detection result", {
|
||||||
|
orderId: orderDetails.id,
|
||||||
|
isMnp,
|
||||||
|
simType,
|
||||||
|
mnpReserveNumber: mnp?.reserveNumber,
|
||||||
|
mnpHasIdentity: !!(mnp?.lastnameKanji || mnp?.firstnameKanji),
|
||||||
|
mnpGender: mnp?.gender,
|
||||||
|
});
|
||||||
|
|
||||||
const simPlanItem = orderDetails.items.find(
|
const simPlanItem = orderDetails.items.find(
|
||||||
(item: OrderItemDetails) =>
|
(item: OrderItemDetails) =>
|
||||||
@ -174,7 +209,9 @@ export class SimFulfillmentService {
|
|||||||
eid,
|
eid,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Physical SIM activation flow (PA05-18 + PA02-01 + PA05-05)
|
// Physical SIM activation flow:
|
||||||
|
// Non-MNP: PA02-01 + PA05-05
|
||||||
|
// MNP: PA05-19 + PA05-05
|
||||||
if (!assignedPhysicalSimId) {
|
if (!assignedPhysicalSimId) {
|
||||||
throw new SimActivationException(
|
throw new SimActivationException(
|
||||||
"Physical SIM requires an assigned SIM from inventory (Assign_Physical_SIM__c)",
|
"Physical SIM requires an assigned SIM from inventory (Assign_Physical_SIM__c)",
|
||||||
@ -191,6 +228,8 @@ export class SimFulfillmentService {
|
|||||||
callWaitingEnabled,
|
callWaitingEnabled,
|
||||||
contactIdentity,
|
contactIdentity,
|
||||||
assignmentDetails,
|
assignmentDetails,
|
||||||
|
isMnp,
|
||||||
|
...(mnp && { mnp }),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.log("Physical SIM fulfillment completed successfully", {
|
this.logger.log("Physical SIM fulfillment completed successfully", {
|
||||||
@ -222,55 +261,62 @@ export class SimFulfillmentService {
|
|||||||
planSku: string;
|
planSku: string;
|
||||||
activationType: "Immediate" | "Scheduled";
|
activationType: "Immediate" | "Scheduled";
|
||||||
scheduledAt?: string;
|
scheduledAt?: string;
|
||||||
mnp?: {
|
mnp?: MnpConfig;
|
||||||
reserveNumber?: string;
|
|
||||||
reserveExpireDate?: string;
|
|
||||||
account?: string;
|
|
||||||
firstnameKanji?: string;
|
|
||||||
lastnameKanji?: string;
|
|
||||||
firstnameZenKana?: string;
|
|
||||||
lastnameZenKana?: string;
|
|
||||||
gender?: string;
|
|
||||||
birthday?: string;
|
|
||||||
};
|
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { account, eid, planSku, activationType, scheduledAt, mnp } = params;
|
const { account, eid, planSku, activationType, scheduledAt, mnp } = params;
|
||||||
|
const isMnp = !!mnp?.reserveNumber;
|
||||||
|
|
||||||
|
this.logger.log("eSIM activation starting", {
|
||||||
|
account,
|
||||||
|
planSku,
|
||||||
|
isMnp,
|
||||||
|
addKind: isMnp ? "M" : "N",
|
||||||
|
aladinOperated: isMnp ? "20" : "10",
|
||||||
|
mnpReserveNumber: mnp?.reserveNumber,
|
||||||
|
mnpHasIdentity: !!(mnp?.lastnameKanji || mnp?.firstnameKanji),
|
||||||
|
mnpGender: mnp?.gender,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Build unified MNP object with both reservation and identity data (all Level 2 per PA05-41)
|
||||||
|
const mnpPayload =
|
||||||
|
isMnp && mnp?.reserveNumber
|
||||||
|
? {
|
||||||
|
reserveNumber: mnp.reserveNumber,
|
||||||
|
...(mnp.reserveExpireDate && { reserveExpireDate: mnp.reserveExpireDate }),
|
||||||
|
...(mnp.lastnameKanji && { lastnameKanji: mnp.lastnameKanji }),
|
||||||
|
...(mnp.firstnameKanji && { firstnameKanji: mnp.firstnameKanji }),
|
||||||
|
...(mnp.lastnameZenKana && { lastnameZenKana: mnp.lastnameZenKana }),
|
||||||
|
...(mnp.firstnameZenKana && { firstnameZenKana: mnp.firstnameZenKana }),
|
||||||
|
// Map Salesforce gender 'F' → Freebit gender 'W' (Weiblich)
|
||||||
|
...(mnp.gender && { gender: mapGenderToFreebit(mnp.gender) }),
|
||||||
|
...(mnp.birthday && { birthday: mnp.birthday }),
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
await this.freebitFacade.activateEsimAccountNew({
|
await this.freebitFacade.activateEsimAccountNew({
|
||||||
account,
|
account,
|
||||||
eid,
|
eid,
|
||||||
planCode: planSku,
|
planCode: planSku,
|
||||||
contractLine: "5G",
|
contractLine: "5G",
|
||||||
|
simKind: "E0", // Voice eSIM
|
||||||
|
addKind: isMnp ? "M" : "N",
|
||||||
|
aladinOperated: isMnp ? "20" : "10", // '20' = we provide identity for ALADIN
|
||||||
...(activationType === "Scheduled" && scheduledAt && { shipDate: scheduledAt }),
|
...(activationType === "Scheduled" && scheduledAt && { shipDate: scheduledAt }),
|
||||||
...(mnp?.reserveNumber &&
|
...(mnpPayload && { mnp: mnpPayload }),
|
||||||
mnp?.reserveExpireDate && {
|
|
||||||
mnp: {
|
|
||||||
reserveNumber: mnp.reserveNumber,
|
|
||||||
reserveExpireDate: mnp.reserveExpireDate,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
...(mnp && {
|
|
||||||
identity: {
|
|
||||||
...(mnp.firstnameKanji && { firstnameKanji: mnp.firstnameKanji }),
|
|
||||||
...(mnp.lastnameKanji && { lastnameKanji: mnp.lastnameKanji }),
|
|
||||||
...(mnp.firstnameZenKana && { firstnameZenKana: mnp.firstnameZenKana }),
|
|
||||||
...(mnp.lastnameZenKana && { lastnameZenKana: mnp.lastnameZenKana }),
|
|
||||||
...(mnp.gender && { gender: mnp.gender }),
|
|
||||||
...(mnp.birthday && { birthday: mnp.birthday }),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.log("eSIM activated successfully", {
|
this.logger.log("eSIM activated successfully", {
|
||||||
account,
|
account,
|
||||||
planSku,
|
planSku,
|
||||||
|
isMnp,
|
||||||
scheduled: activationType === "Scheduled",
|
scheduled: activationType === "Scheduled",
|
||||||
});
|
});
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
this.logger.error("eSIM activation failed", {
|
this.logger.error("eSIM activation failed", {
|
||||||
account,
|
account,
|
||||||
planSku,
|
planSku,
|
||||||
|
isMnp,
|
||||||
error: extractErrorMessage(error),
|
error: extractErrorMessage(error),
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
@ -278,15 +324,21 @@ export class SimFulfillmentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate Physical SIM (Black SIM) via Freebit PA02-01 + PA05-05 APIs
|
* Activate Physical SIM (Black SIM) via Freebit APIs
|
||||||
*
|
*
|
||||||
* Flow for Physical SIMs (Black SIMs):
|
* Non-MNP flow:
|
||||||
* 1. Fetch SIM Inventory details from Salesforce
|
* 1. Fetch SIM Inventory details from Salesforce
|
||||||
* 2. Validate SIM status is "Available"
|
* 2. Validate SIM status is "Available"
|
||||||
* 3. Map product SKU to Freebit plan code
|
* 3. Map product SKU to Freebit plan code
|
||||||
* 4. Call Freebit PA02-01 (Account Registration) with createType="new"
|
* 4. Call Freebit PA02-01 (Account Registration) with createType="new"
|
||||||
* 5. Call Freebit PA05-05 (Voice Options) to configure voice features
|
* 5. Call Freebit PA05-05 (Voice Options) to configure voice features
|
||||||
* 6. Update SIM Inventory status to "Assigned"
|
* 6. Update SIM Inventory status to "Assigned"
|
||||||
|
*
|
||||||
|
* MNP flow:
|
||||||
|
* 1-3. Same as above
|
||||||
|
* 4. Call Freebit PA05-19 (Semi-Black MNP Registration) — replaces PA02-01
|
||||||
|
* 5. Call Freebit PA05-05 (Voice Options)
|
||||||
|
* 6. Update SIM Inventory status to "Assigned"
|
||||||
*/
|
*/
|
||||||
private async activatePhysicalSim(params: {
|
private async activatePhysicalSim(params: {
|
||||||
orderId: string;
|
orderId: string;
|
||||||
@ -297,6 +349,8 @@ export class SimFulfillmentService {
|
|||||||
callWaitingEnabled: boolean;
|
callWaitingEnabled: boolean;
|
||||||
contactIdentity?: ContactIdentityData | undefined;
|
contactIdentity?: ContactIdentityData | undefined;
|
||||||
assignmentDetails?: SimAssignmentDetails | undefined;
|
assignmentDetails?: SimAssignmentDetails | undefined;
|
||||||
|
isMnp?: boolean;
|
||||||
|
mnp?: MnpConfig;
|
||||||
}): Promise<{ phoneNumber: string; serialNumber: string }> {
|
}): Promise<{ phoneNumber: string; serialNumber: string }> {
|
||||||
const {
|
const {
|
||||||
orderId,
|
orderId,
|
||||||
@ -307,15 +361,20 @@ export class SimFulfillmentService {
|
|||||||
callWaitingEnabled,
|
callWaitingEnabled,
|
||||||
contactIdentity,
|
contactIdentity,
|
||||||
assignmentDetails,
|
assignmentDetails,
|
||||||
|
isMnp = false,
|
||||||
|
mnp,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
this.logger.log("Starting Physical SIM activation (PA02-01 + PA05-05)", {
|
this.logger.log("Starting Physical SIM activation", {
|
||||||
orderId,
|
orderId,
|
||||||
simInventoryId,
|
simInventoryId,
|
||||||
planSku,
|
planSku,
|
||||||
|
isMnp,
|
||||||
|
path: isMnp ? "PA05-19 (MNP)" : "PA02-01 (new)",
|
||||||
voiceMailEnabled,
|
voiceMailEnabled,
|
||||||
callWaitingEnabled,
|
callWaitingEnabled,
|
||||||
hasContactIdentity: !!contactIdentity,
|
hasContactIdentity: !!contactIdentity,
|
||||||
|
mnpReserveNumber: mnp?.reserveNumber,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Step 1 & 2: Fetch and validate SIM Inventory
|
// Step 1 & 2: Fetch and validate SIM Inventory
|
||||||
@ -337,11 +396,36 @@ export class SimFulfillmentService {
|
|||||||
orderId,
|
orderId,
|
||||||
simInventoryId,
|
simInventoryId,
|
||||||
accountPhoneNumber,
|
accountPhoneNumber,
|
||||||
|
ptNumber: simRecord.ptNumber,
|
||||||
planCode,
|
planCode,
|
||||||
|
isMnp,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Step 4: Call Freebit PA02-01 (Account Registration) for Black SIM
|
if (isMnp) {
|
||||||
|
// Step 4 (MNP): Call Freebit PA05-19 (Semi-Black MNP Registration)
|
||||||
|
// PA05-19 replaces PA02-01 for MNP transfers — it registers the account
|
||||||
|
// and initiates the MNP transfer in a single call
|
||||||
|
this.logger.log("Calling PA05-19 Semi-Black MNP Registration", {
|
||||||
|
orderId,
|
||||||
|
account: accountPhoneNumber,
|
||||||
|
productNumber: simRecord.ptNumber,
|
||||||
|
planCode,
|
||||||
|
mnpMethod: "10",
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.freebitFacade.registerSemiBlackAccount({
|
||||||
|
account: accountPhoneNumber,
|
||||||
|
productNumber: simRecord.ptNumber,
|
||||||
|
planCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.log("PA05-19 Semi-Black MNP Registration successful", {
|
||||||
|
orderId,
|
||||||
|
account: accountPhoneNumber,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Step 4 (non-MNP): Call Freebit PA02-01 (Account Registration)
|
||||||
this.logger.log("Calling PA02-01 Account Registration", {
|
this.logger.log("Calling PA02-01 Account Registration", {
|
||||||
orderId,
|
orderId,
|
||||||
account: accountPhoneNumber,
|
account: accountPhoneNumber,
|
||||||
@ -359,6 +443,7 @@ export class SimFulfillmentService {
|
|||||||
orderId,
|
orderId,
|
||||||
account: accountPhoneNumber,
|
account: accountPhoneNumber,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Step 5: Call Freebit PA05-05 (Voice Options Registration)
|
// Step 5: Call Freebit PA05-05 (Voice Options Registration)
|
||||||
// Only call if we have contact identity data
|
// Only call if we have contact identity data
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user