/** * Subscriptions Domain - Schemas * * Zod validation schemas for subscription domain types. */ import { z } from "zod"; import { billingCycleSchema } from "../common/schema.js"; // Subscription Status Schema export const subscriptionStatusSchema = z.enum([ "Active", "Inactive", "Pending", "Cancelled", "Suspended", "Terminated", "Completed", ]); // Subscription Cycle Schema — re-exported from common export const subscriptionCycleSchema = billingCycleSchema; // Subscription Schema export const subscriptionSchema = z.object({ id: z.number().int().positive("Subscription id must be positive"), serviceId: z.number().int().positive("Service id must be positive"), productName: z.string().min(1, "Product name is required"), domain: z.string().optional(), cycle: subscriptionCycleSchema, status: subscriptionStatusSchema, nextDue: z.string().optional(), amount: z.number(), currency: z.string().min(1, "Currency is required"), currencySymbol: z.string().optional(), registrationDate: z.string().min(1, "Registration date is required"), notes: z.string().optional(), customFields: z.record(z.string(), z.string()).optional(), orderNumber: z.string().optional(), groupName: z.string().optional(), paymentMethod: z.string().optional(), serverName: z.string().optional(), }); export const subscriptionArraySchema = z.array(subscriptionSchema); // Subscription List Schema export const subscriptionListSchema = z.object({ subscriptions: z.array(subscriptionSchema), totalCount: z.number().int().nonnegative(), }); // ============================================================================ // Route Param Schemas (BFF) // ============================================================================ export const subscriptionIdParamSchema = z.object({ id: z.coerce.number().int().positive("Subscription id must be positive"), }); export type SubscriptionIdParam = z.infer; // ============================================================================ // Query Parameter Schemas // ============================================================================ /** * Schema for subscription query parameters */ export const subscriptionQueryParamsSchema = z.object({ page: z.coerce.number().int().positive().optional(), limit: z.coerce.number().int().positive().max(100).optional(), status: subscriptionStatusSchema.optional(), type: z.string().optional(), }); export type SubscriptionQueryParams = z.infer; export const subscriptionQuerySchema = subscriptionQueryParamsSchema; export type SubscriptionQuery = SubscriptionQueryParams; // ============================================================================ // Response Schemas // ============================================================================ /** * Schema for subscription statistics */ export const subscriptionStatsSchema = z.object({ total: z.number().int().nonnegative(), active: z.number().int().nonnegative(), completed: z.number().int().nonnegative(), cancelled: z.number().int().nonnegative(), }); /** * Schema for SIM action responses (top-up, cancellation, feature updates) */ export const simActionResponseSchema = z.object({ message: z.string(), /** Action-specific payload — varies by SIM/internet operation (top-up, cancellation, etc.) */ data: z.record(z.string(), z.unknown()).optional(), }); /** * Schema for SIM plan change result with IP addresses */ export const simPlanChangeResultSchema = z.object({ message: z.string(), ipv4: z.string().optional(), ipv6: z.string().optional(), scheduledAt: z .string() .regex(/^\d{8}$/, "Scheduled date must be in YYYYMMDD format") .optional(), }); // ============================================================================ // Inferred Types from Schemas (Schema-First Approach) // ============================================================================ export type SubscriptionStatus = z.infer; export type SubscriptionCycle = z.infer; export type Subscription = z.infer; export type SubscriptionArray = z.infer; export type SubscriptionList = z.infer; export type SubscriptionStats = z.infer; export type SimActionResponse = z.infer; export type SimPlanChangeResult = z.infer; // ============================================================================ // Internet Cancellation Schemas // ============================================================================ /** * Available cancellation month for the customer */ export const internetCancellationMonthSchema = z.object({ value: z.string(), // YYYY-MM format label: z.string(), // Display label like "November 2025" }); /** * Internet cancellation preview response (service details + available months) */ export const internetCancellationPreviewSchema = z.object({ productName: z.string(), billingAmount: z.number(), nextDueDate: z.string().optional(), registrationDate: z.string().optional(), availableMonths: z.array(internetCancellationMonthSchema), customerEmail: z.string(), customerName: z.string(), }); /** * Internet cancellation request from customer */ export const internetCancelRequestSchema = z.object({ cancellationMonth: z .string() .regex(/^\d{4}-\d{2}$/, "Cancellation month must be in YYYY-MM format"), confirmRead: z.boolean(), confirmCancel: z.boolean(), comments: z.string().max(1000).optional(), }); export type InternetCancellationMonth = z.infer; export type InternetCancellationPreview = z.infer; export type InternetCancelRequest = z.infer; // ============================================================================ // Unified Cancellation Preview (SIM + Internet) // ============================================================================ /** * Service type for cancellation flows */ export const serviceTypeSchema = z.enum(["sim", "internet"]); export type ServiceType = z.infer; /** * Structured notice/term content for cancellation pages */ export const cancellationNoticeSchema = z.object({ title: z.string().min(1), content: z.string().min(1), }); export type CancellationNotice = z.infer; /** * Cancellation status derived from Salesforce Opportunity (if available) * * Notes: * - We only need a small subset for portal display. * - This is nullable because some services may not have a linked Opportunity. */ export const cancellationStatusSchema = z .object({ stage: z.enum(["Active", "△Cancelling", "〇Cancelled"]), scheduledEndDate: z.string().optional(), // Internet only rentalReturnStatus: z.string().optional(), }) .nullable(); export type CancellationStatus = z.infer; /** * Unified cancellation preview response used by the generic cancellation page. * * Includes: * - Service type + display fields * - Terms and notices (service-type specific) * - Cancellation status (derived from Opportunity when WHMCS isn't already cancelled) */ export const cancellationPreviewSchema = z.object({ serviceType: serviceTypeSchema, serviceName: z.string().min(1), /** * Salesforce Opportunity ID read from WHMCS service custom fields (already stored in WHMCS). * Optional because not all services are guaranteed to have it. */ opportunityId: z.string().min(15).max(18).optional(), serviceInfo: z.array( z.object({ label: z.string().min(1), value: z.string().min(1), mono: z.boolean().optional(), }) ), terms: z.array(cancellationNoticeSchema), warnings: z.array(cancellationNoticeSchema).default([]), step3Notices: z.array(cancellationNoticeSchema).default([]), cancellationStatus: cancellationStatusSchema, availableMonths: z.array(internetCancellationMonthSchema), customerEmail: z.string(), customerName: z.string().min(1), // SIM-specific (optional) isWithinMinimumTerm: z.boolean().optional(), minimumContractEndDate: z.string().optional(), }); export type CancellationPreview = z.infer;