285 lines
7.9 KiB
TypeScript
285 lines
7.9 KiB
TypeScript
|
|
import type { AccessModeValue } from "./contract";
|
||
|
|
import type { OrderSelections } from "./schema";
|
||
|
|
import { normalizeOrderSelections } from "./helpers";
|
||
|
|
import type { ActivationType, MnpData, SimCardType } from "../sim";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Draft representation of Internet checkout configuration.
|
||
|
|
* Only includes business-relevant fields that should be transformed into selections.
|
||
|
|
*/
|
||
|
|
export interface InternetCheckoutDraft {
|
||
|
|
planSku?: string | null | undefined;
|
||
|
|
accessMode?: AccessModeValue | null | undefined;
|
||
|
|
installationSku?: string | null | undefined;
|
||
|
|
addonSkus?: readonly string[] | null | undefined;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Patch representation of Internet checkout state derived from persisted selections.
|
||
|
|
* Consumers can merge this object into their local UI state.
|
||
|
|
*/
|
||
|
|
export interface InternetCheckoutStatePatch {
|
||
|
|
planSku?: string | null;
|
||
|
|
accessMode?: AccessModeValue | null;
|
||
|
|
installationSku?: string | null;
|
||
|
|
addonSkus?: string[];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Draft representation of SIM checkout configuration.
|
||
|
|
*/
|
||
|
|
export interface SimCheckoutDraft {
|
||
|
|
planSku?: string | null | undefined;
|
||
|
|
simType?: SimCardType | null | undefined;
|
||
|
|
activationType?: ActivationType | null | undefined;
|
||
|
|
eid?: string | null | undefined;
|
||
|
|
scheduledActivationDate?: string | null | undefined;
|
||
|
|
wantsMnp?: boolean | null | undefined;
|
||
|
|
mnpData?: Partial<MnpData> | null | undefined;
|
||
|
|
addonSkus?: readonly string[] | null | undefined;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Patch representation of SIM checkout state derived from persisted selections.
|
||
|
|
*/
|
||
|
|
export interface SimCheckoutStatePatch {
|
||
|
|
planSku?: string | null;
|
||
|
|
simType?: SimCardType | null;
|
||
|
|
activationType?: ActivationType | null;
|
||
|
|
eid?: string;
|
||
|
|
scheduledActivationDate?: string;
|
||
|
|
wantsMnp?: boolean;
|
||
|
|
selectedAddons?: string[];
|
||
|
|
mnpData?: Partial<MnpData>;
|
||
|
|
}
|
||
|
|
|
||
|
|
const normalizeString = (value: unknown): string | undefined => {
|
||
|
|
if (typeof value !== "string") return undefined;
|
||
|
|
const trimmed = value.trim();
|
||
|
|
return trimmed.length > 0 ? trimmed : undefined;
|
||
|
|
};
|
||
|
|
|
||
|
|
const normalizeSkuList = (values: readonly string[] | null | undefined): string | undefined => {
|
||
|
|
if (!Array.isArray(values)) return undefined;
|
||
|
|
const sanitized = values
|
||
|
|
.map(normalizeString)
|
||
|
|
.filter((entry): entry is string => Boolean(entry));
|
||
|
|
if (sanitized.length === 0) {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
return sanitized.join(",");
|
||
|
|
};
|
||
|
|
|
||
|
|
const parseAddonList = (value: string | undefined): string[] => {
|
||
|
|
if (!value) return [];
|
||
|
|
return value
|
||
|
|
.split(",")
|
||
|
|
.map(entry => entry.trim())
|
||
|
|
.filter(Boolean);
|
||
|
|
};
|
||
|
|
|
||
|
|
const coalescePlanSku = (selections: OrderSelections): string | null => {
|
||
|
|
const planCandidates = [
|
||
|
|
selections.planSku,
|
||
|
|
selections.planIdSku,
|
||
|
|
selections.plan,
|
||
|
|
selections.planId,
|
||
|
|
];
|
||
|
|
|
||
|
|
for (const candidate of planCandidates) {
|
||
|
|
const normalized = normalizeString(candidate);
|
||
|
|
if (normalized) {
|
||
|
|
return normalized;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return null;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Build normalized order selections for Internet checkout from a UI draft.
|
||
|
|
* Ensures only business-relevant data is emitted.
|
||
|
|
*/
|
||
|
|
export function buildInternetCheckoutSelections(
|
||
|
|
draft: InternetCheckoutDraft
|
||
|
|
): OrderSelections {
|
||
|
|
const raw: Record<string, string> = {};
|
||
|
|
|
||
|
|
const planSku = normalizeString(draft.planSku);
|
||
|
|
if (planSku) {
|
||
|
|
raw.plan = planSku;
|
||
|
|
raw.planSku = planSku;
|
||
|
|
}
|
||
|
|
|
||
|
|
const accessMode = draft.accessMode ?? null;
|
||
|
|
if (accessMode) {
|
||
|
|
raw.accessMode = accessMode;
|
||
|
|
}
|
||
|
|
|
||
|
|
const installationSku = normalizeString(draft.installationSku);
|
||
|
|
if (installationSku) {
|
||
|
|
raw.installationSku = installationSku;
|
||
|
|
}
|
||
|
|
|
||
|
|
const addons = normalizeSkuList(draft.addonSkus);
|
||
|
|
if (addons) {
|
||
|
|
raw.addons = addons;
|
||
|
|
}
|
||
|
|
|
||
|
|
return normalizeOrderSelections(raw);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Derive Internet checkout UI state from normalized selections.
|
||
|
|
*/
|
||
|
|
export function deriveInternetCheckoutState(
|
||
|
|
selections: OrderSelections
|
||
|
|
): InternetCheckoutStatePatch {
|
||
|
|
const patch: InternetCheckoutStatePatch = {
|
||
|
|
addonSkus: parseAddonList(selections.addons),
|
||
|
|
};
|
||
|
|
|
||
|
|
const planSku = coalescePlanSku(selections);
|
||
|
|
if (planSku) {
|
||
|
|
patch.planSku = planSku;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (selections.accessMode) {
|
||
|
|
patch.accessMode = selections.accessMode;
|
||
|
|
}
|
||
|
|
|
||
|
|
const installationSku = normalizeString(selections.installationSku);
|
||
|
|
if (installationSku) {
|
||
|
|
patch.installationSku = installationSku;
|
||
|
|
}
|
||
|
|
|
||
|
|
return patch;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Build normalized order selections for SIM checkout from a UI draft.
|
||
|
|
*/
|
||
|
|
export function buildSimCheckoutSelections(draft: SimCheckoutDraft): OrderSelections {
|
||
|
|
const raw: Record<string, string> = {};
|
||
|
|
|
||
|
|
const planSku = normalizeString(draft.planSku);
|
||
|
|
if (planSku) {
|
||
|
|
raw.plan = planSku;
|
||
|
|
raw.planSku = planSku;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (draft.simType) {
|
||
|
|
raw.simType = draft.simType;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (draft.activationType) {
|
||
|
|
raw.activationType = draft.activationType;
|
||
|
|
}
|
||
|
|
|
||
|
|
const eid = normalizeString(draft.eid);
|
||
|
|
if (draft.simType === "eSIM" && eid) {
|
||
|
|
raw.eid = eid;
|
||
|
|
}
|
||
|
|
|
||
|
|
const scheduledAt = normalizeString(draft.scheduledActivationDate);
|
||
|
|
if (draft.activationType === "Scheduled" && scheduledAt) {
|
||
|
|
raw.scheduledAt = scheduledAt;
|
||
|
|
}
|
||
|
|
|
||
|
|
const addons = normalizeSkuList(draft.addonSkus);
|
||
|
|
if (addons) {
|
||
|
|
raw.addons = addons;
|
||
|
|
}
|
||
|
|
|
||
|
|
const wantsMnp = Boolean(draft.wantsMnp);
|
||
|
|
if (wantsMnp) {
|
||
|
|
raw.isMnp = "true";
|
||
|
|
const mnpData = draft.mnpData ?? {};
|
||
|
|
const assignIfPresent = (key: keyof MnpData, targetKey: keyof typeof raw) => {
|
||
|
|
const normalized = normalizeString(mnpData[key]);
|
||
|
|
if (normalized) {
|
||
|
|
raw[targetKey] = normalized;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
assignIfPresent("reservationNumber", "mnpNumber");
|
||
|
|
assignIfPresent("expiryDate", "mnpExpiry");
|
||
|
|
assignIfPresent("phoneNumber", "mnpPhone");
|
||
|
|
assignIfPresent("mvnoAccountNumber", "mvnoAccountNumber");
|
||
|
|
assignIfPresent("portingLastName", "portingLastName");
|
||
|
|
assignIfPresent("portingFirstName", "portingFirstName");
|
||
|
|
assignIfPresent("portingLastNameKatakana", "portingLastNameKatakana");
|
||
|
|
assignIfPresent("portingFirstNameKatakana", "portingFirstNameKatakana");
|
||
|
|
assignIfPresent("portingGender", "portingGender");
|
||
|
|
assignIfPresent("portingDateOfBirth", "portingDateOfBirth");
|
||
|
|
}
|
||
|
|
|
||
|
|
return normalizeOrderSelections(raw);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Derive SIM checkout UI state from normalized selections.
|
||
|
|
*/
|
||
|
|
export function deriveSimCheckoutState(selections: OrderSelections): SimCheckoutStatePatch {
|
||
|
|
const planSku = coalescePlanSku(selections);
|
||
|
|
const simType = selections.simType ?? null;
|
||
|
|
const activationType = selections.activationType ?? null;
|
||
|
|
const eid = normalizeString(selections.eid);
|
||
|
|
const scheduledActivationDate = normalizeString(selections.scheduledAt);
|
||
|
|
const addonSkus = parseAddonList(selections.addons);
|
||
|
|
|
||
|
|
const wantsMnp = Boolean(
|
||
|
|
selections.isMnp &&
|
||
|
|
typeof selections.isMnp === "string" &&
|
||
|
|
selections.isMnp.toLowerCase() === "true"
|
||
|
|
);
|
||
|
|
|
||
|
|
const mnpFields: Partial<MnpData> = {};
|
||
|
|
|
||
|
|
const assignField = (source: keyof OrderSelections, target: keyof MnpData) => {
|
||
|
|
const normalized = normalizeString(selections[source]);
|
||
|
|
if (normalized) {
|
||
|
|
mnpFields[target] = normalized;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
if (wantsMnp) {
|
||
|
|
assignField("mnpNumber", "reservationNumber");
|
||
|
|
assignField("mnpExpiry", "expiryDate");
|
||
|
|
assignField("mnpPhone", "phoneNumber");
|
||
|
|
assignField("mvnoAccountNumber", "mvnoAccountNumber");
|
||
|
|
assignField("portingLastName", "portingLastName");
|
||
|
|
assignField("portingFirstName", "portingFirstName");
|
||
|
|
assignField("portingLastNameKatakana", "portingLastNameKatakana");
|
||
|
|
assignField("portingFirstNameKatakana", "portingFirstNameKatakana");
|
||
|
|
assignField("portingGender", "portingGender");
|
||
|
|
assignField("portingDateOfBirth", "portingDateOfBirth");
|
||
|
|
}
|
||
|
|
|
||
|
|
const patch: SimCheckoutStatePatch = {
|
||
|
|
selectedAddons: addonSkus,
|
||
|
|
wantsMnp,
|
||
|
|
};
|
||
|
|
|
||
|
|
if (planSku) {
|
||
|
|
patch.planSku = planSku;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (simType) {
|
||
|
|
patch.simType = simType;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (activationType) {
|
||
|
|
patch.activationType = activationType;
|
||
|
|
}
|
||
|
|
|
||
|
|
patch.eid = eid ?? "";
|
||
|
|
patch.scheduledActivationDate = scheduledActivationDate ?? "";
|
||
|
|
|
||
|
|
if (wantsMnp && Object.keys(mnpFields).length > 0) {
|
||
|
|
patch.mnpData = mnpFields;
|
||
|
|
}
|
||
|
|
|
||
|
|
return patch;
|
||
|
|
}
|