/** * SIM Domain - Freebit Provider Request Schemas * * Zod schemas for all Freebit API request payloads. */ import { z } from "zod"; // ============================================================================ // Validation Constants // ============================================================================ const MSG_ACCOUNT_REQUIRED = "Account is required"; const YYYYMMDD_REGEX = /^\d{8}$/; const MSG_SHIP_DATE_FORMAT = "Ship date must be in YYYYMMDD format"; // ============================================================================ // Account Details & Traffic Info // ============================================================================ export const freebitAccountDetailsRequestSchema = z.object({ version: z.string().optional(), requestDatas: z .array( z.object({ kind: z.enum(["MASTER", "MVNO"]), account: z.union([z.string(), z.number()]).optional(), }) ) .min(1, "At least one request data entry is required"), }); export const freebitTrafficInfoRequestSchema = z.object({ account: z.string().min(1, MSG_ACCOUNT_REQUIRED), }); // ============================================================================ // Top-Up // ============================================================================ export const freebitTopUpOptionsSchema = z.object({ campaignCode: z.string().optional(), expiryDate: z.string().optional(), scheduledAt: z.string().optional(), }); export const freebitTopUpRequestPayloadSchema = z.object({ account: z.string().min(1, MSG_ACCOUNT_REQUIRED), quotaMb: z.number().positive("Quota must be positive"), options: freebitTopUpOptionsSchema.optional(), }); export const freebitTopUpApiRequestSchema = z.object({ account: z.string().min(1, MSG_ACCOUNT_REQUIRED), quota: z.number().positive("Quota must be positive"), quotaCode: z.string().optional(), expire: z.string().optional(), runTime: z.string().optional(), }); // ============================================================================ // Plan Change & Cancellation // ============================================================================ export const freebitPlanChangeRequestSchema = z.object({ account: z.string().min(1, MSG_ACCOUNT_REQUIRED), newPlanCode: z.string().min(1, "New plan code is required"), assignGlobalIp: z.boolean().optional(), scheduledAt: z.string().optional(), }); export const freebitPlanChangeApiRequestSchema = z.object({ account: z.string().min(1, MSG_ACCOUNT_REQUIRED), plancode: z.string().min(1, "Plan code is required"), globalip: z.enum(["0", "1"]).optional(), runTime: z.string().optional(), }); export const freebitAddSpecRequestSchema = z.object({ account: z.string().min(1, MSG_ACCOUNT_REQUIRED), specCode: z.string().min(1, "Spec code is required"), enabled: z.boolean().optional(), networkType: z.enum(["4G", "5G"]).optional(), }); export const freebitRemoveSpecRequestSchema = z.object({ account: z.string().min(1, MSG_ACCOUNT_REQUIRED), specCode: z.string().min(1, "Spec code is required"), }); export const freebitCancelPlanRequestSchema = z.object({ account: z.string().min(1, MSG_ACCOUNT_REQUIRED), runDate: z.string().optional(), }); export const freebitCancelPlanApiRequestSchema = z.object({ account: z.string().min(1, MSG_ACCOUNT_REQUIRED), runTime: z.string().optional(), }); export const freebitQuotaHistoryRequestSchema = z.object({ account: z.string().min(1, MSG_ACCOUNT_REQUIRED), fromDate: z.string().regex(/^\d{8}$/, "From date must be in YYYYMMDD format"), toDate: z.string().regex(/^\d{8}$/, "To date must be in YYYYMMDD format"), }); export const freebitQuotaHistoryResponseSchema = z.object({ resultCode: z.string(), status: z .object({ message: z.string(), statusCode: z.union([z.string(), z.number()]), }) .optional(), total: z.union([z.string(), z.number()]), count: z.union([z.string(), z.number()]), quotaHistory: z.array( z.object({ addQuotaKb: z.union([z.string(), z.number()]), addDate: z.string(), expireDate: z.string(), campaignCode: z.string().optional(), }) ), }); export const freebitEsimMnpSchema = z.object({ reserveNumber: z.string().min(1, "Reserve number is required"), reserveExpireDate: z .string() .regex(/^\d{8}$/, "Reserve expire date must be in YYYYMMDD format") .optional(), // Identity fields (Level 2 per PA05-41 spec — nested inside mnp) lastnameKanji: z.string().optional(), firstnameKanji: z.string().optional(), lastnameZenKana: z.string().optional(), firstnameZenKana: z.string().optional(), gender: z.enum(["M", "W", "C"]).optional(), // M: Male, W: Female (Weiblich), C: Corporate birthday: z .string() .regex(/^\d{8}$/, "Birthday must be in YYYYMMDD format") .optional(), }); export const freebitEsimReissueRequestSchema = z.object({ account: z.string().min(1, MSG_ACCOUNT_REQUIRED), newEid: z.string().min(1, "New EID is required"), oldEid: z.string().optional(), planCode: z.string().optional(), oldProductNumber: z.string().optional(), }); export const freebitEsimAddAccountRequestSchema = z.object({ authKey: z.string().min(1).optional(), aladinOperated: z.enum(["10", "20"]).default("10"), account: z.string().min(1, MSG_ACCOUNT_REQUIRED), eid: z.string().min(1, "EID is required"), addKind: z.enum(["N", "R", "M"]).default("N"), // N: New, R: Reissue, M: MNP shipDate: z.string().regex(YYYYMMDD_REGEX, MSG_SHIP_DATE_FORMAT).optional(), planCode: z.string().optional(), contractLine: z.enum(["4G", "5G"]).optional(), mnp: freebitEsimMnpSchema.optional(), }); // ========================================================================= // SIM Features // ========================================================================= export const freebitSimFeaturesRequestSchema = z.object({ account: z.string().min(1, MSG_ACCOUNT_REQUIRED), voiceMailEnabled: z.boolean().optional(), callWaitingEnabled: z.boolean().optional(), callForwardingEnabled: z.boolean().optional(), callerIdEnabled: z.boolean().optional(), }); export const freebitGlobalIpRequestSchema = z.object({ account: z.string().min(1, MSG_ACCOUNT_REQUIRED), assign: z.boolean(), // true to assign, false to remove }); // ========================================================================= // eSIM Activation // ========================================================================= export const freebitAuthRequestSchema = z.object({ oemId: z.string().min(1), oemKey: z.string().min(1), }); export const freebitCancelAccountRequestSchema = z.object({ account: z.string().min(1), runDate: z.string().optional(), }); export const freebitEsimIdentitySchema = z.object({ firstnameKanji: z.string().optional(), lastnameKanji: z.string().optional(), firstnameZenKana: z.string().optional(), lastnameZenKana: z.string().optional(), gender: z.enum(["M", "W", "C"]).optional(), // Freebit: M=Male, W=Female, C=Corporate birthday: z .string() .regex(/^\d{8}$/, "Birthday must be in YYYYMMDD format") .optional(), }); /** * Freebit eSIM Account Activation Request Schema * PA05-41 (addAcct) API endpoint */ export const freebitEsimActivationRequestSchema = z.object({ authKey: z.string().min(1, "Auth key is required"), aladinOperated: z.enum(["10", "20"]).default("10"), // 10: issue profile, 20: no-issue createType: z.enum(["new", "reissue", "exchange"]).optional(), // Not required for addKind='R' account: z.string().min(1, "Account (MSISDN) is required"), eid: z.string().min(1, "EID is required for eSIM"), simkind: z.enum(["E0", "E2", "E3"]).default("E0"), // E0: voice, E2: data-only, E3: SMS planCode: z.string().optional(), contractLine: z.enum(["4G", "5G"]).optional(), shipDate: z.string().regex(YYYYMMDD_REGEX, MSG_SHIP_DATE_FORMAT).optional(), mnp: freebitEsimMnpSchema.optional(), // MNP + identity fields (Level 2 per PA05-41) // Additional fields for reissue/exchange masterAccount: z.string().optional(), masterPassword: z.string().optional(), repAccount: z.string().optional(), size: z.string().optional(), addKind: z.enum(["N", "M", "R"]).optional(), // N: New, M: MNP, R: Reissue oldEid: z.string().optional(), oldProductNumber: z.string().optional(), deliveryCode: z.string().optional(), globalIp: z.enum(["10", "20"]).optional(), // 10: none, 20: with global IP }); /** * Freebit eSIM Activation Response Schema * Note: The 'data' field type varies by API version and is not used in production. * Using z.unknown() for type safety while allowing any shape if present. */ export const freebitEsimActivationResponseSchema = z.object({ resultCode: z.string(), resultMessage: z.string().optional(), data: z.unknown().optional(), status: z .object({ statusCode: z.union([z.string(), z.number()]), message: z.string(), }) .optional(), message: z.string().optional(), }); /** * Higher-level eSIM activation parameters schema * Used for business logic layer before mapping to API request */ export const freebitEsimActivationParamsSchema = z.object({ account: z.string().min(1, MSG_ACCOUNT_REQUIRED), eid: z.string().min(1, "EID is required"), planCode: z.string().optional(), contractLine: z.enum(["4G", "5G"]).optional(), aladinOperated: z.enum(["10", "20"]).default("10"), shipDate: z.string().regex(YYYYMMDD_REGEX, MSG_SHIP_DATE_FORMAT).optional(), addKind: z.enum(["N", "M", "R"]).optional(), simKind: z.enum(["E0", "E2", "E3"]).optional(), mnp: freebitEsimMnpSchema.optional(), // MNP reservation + identity (Level 2) }); // ============================================================================ // Type Exports // ============================================================================ export type FreebitAccountDetailsRequest = z.infer; export type FreebitTrafficInfoRequest = z.infer; export type FreebitTopUpRequest = z.infer; export type FreebitTopUpApiRequest = z.infer; export type FreebitPlanChangeRequest = z.infer; export type FreebitPlanChangeApiRequest = z.infer; export type FreebitAddSpecRequest = z.infer; export type FreebitRemoveSpecRequest = z.infer; export type FreebitCancelPlanRequest = z.infer; export type FreebitCancelPlanApiRequest = z.infer; export type FreebitSimFeaturesRequest = z.infer; export type FreebitGlobalIpRequest = z.infer; export type FreebitEsimActivationRequest = z.infer; export type FreebitEsimActivationResponse = z.infer; export type FreebitEsimActivationParams = z.infer; export type FreebitEsimReissueRequest = z.infer; export type FreebitQuotaHistoryRequest = z.infer; export type FreebitQuotaHistoryResponse = z.infer; export type FreebitEsimAddAccountRequest = z.infer; export type FreebitAuthRequest = z.infer; export type FreebitCancelAccountRequest = z.infer; // ============================================================================ // Physical SIM OTA Activation (PA05-33) // ============================================================================ /** * Freebit OTA Account Activation Request Schema * PA05-33 API endpoint: /mvno/ota/addAcnt/ * Used for Physical SIM activation via OTA (Over-The-Air) */ export const freebitOtaActivationRequestSchema = z.object({ authKey: z.string().min(1, "Auth key is required"), aladinOperated: z.enum(["10", "20"]).default("10"), // 10: issue profile, 20: no-issue createType: z.enum(["new", "reissue"]).default("new"), account: z.string().min(1, "Account (MSISDN) is required"), productNumber: z.string().min(1, "Product number (PT) is required"), simkind: z.string().optional(), // Physical SIM kind (e.g., '3MS', '3MR') planCode: z.string().optional(), contractLine: z.enum(["4G", "5G"]).optional(), size: z.enum(["standard", "micro", "nano"]).default("nano"), shipDate: z .string() .regex(/^\d{8}$/, "Ship date must be in YYYYMMDD format") .optional(), deliveryCode: z.string().optional(), // OEM ID code addKind: z.enum(["N", "M"]).default("N"), // N: New, M: MNP mnp: z .object({ reserveNumber: z.string().min(1, "MNP reserve number is required"), reserveExpireDate: z.string().regex(/^\d{8}$/, "Reserve expire date must be YYYYMMDD"), account: z.string().optional(), firstnameKanji: z.string().optional(), lastnameKanji: z.string().optional(), firstnameZenKana: z.string().optional(), lastnameZenKana: z.string().optional(), gender: z.enum(["M", "F"]).optional(), birthday: z.string().optional(), }) .optional(), }); /** * Freebit OTA Account Activation Response Schema */ export const freebitOtaActivationResponseSchema = z.object({ resultCode: z.string(), resultMessage: z.string().optional(), status: z .object({ statusCode: z.union([z.string(), z.number()]), message: z.string(), }) .optional(), message: z.string().optional(), }); export type FreebitOtaActivationRequest = z.infer; export type FreebitOtaActivationResponse = z.infer;