/** * Plan service module for handling plan and claim data loading. * Provides functionality to load plans with retry mechanism and error handling. * * @module plan */ import axios from "axios"; import { logger } from "../utils/logger"; /** * Response interface for plan loading operations. * Represents the structure of both successful and error responses. */ interface PlanResponse { /** The response data payload */ data?: unknown; /** HTTP status code of the response */ status?: number; /** Error message in case of failure */ error?: string; /** Response headers */ headers?: unknown; } /** * Loads a plan with automatic retry mechanism. * Attempts to load the plan multiple times in case of failure. * * @param handle - The unique identifier for the plan or claim * @param retries - Number of retry attempts (default: 3) * @returns Promise resolving to PlanResponse * * @remarks * - Implements exponential backoff with 1 second delay between retries * - Provides detailed logging of each attempt and any errors * - Handles both plan and claim flows based on handle content * - Logs comprehensive error information including: * - HTTP status and headers * - Response data * - Request configuration * * @example * ```typescript * const response = await loadPlanWithRetry('plan-123'); * if (response.error) { * console.error(response.error); * } else { * console.log(response.data); * } * ``` */ export const loadPlanWithRetry = async ( handle: string, retries = 3, ): Promise => { try { logger.log(`[Plan Service] Loading plan ${handle}, attempt 1/${retries}`); logger.log( `[Plan Service] Context: Deep link handle=${handle}, isClaimFlow=${handle.includes("claim")}`, ); // Different endpoint if this is a claim flow const response = await loadPlan(handle); logger.log(`[Plan Service] Plan ${handle} loaded successfully:`, { status: response?.status, headers: response?.headers, data: response?.data, }); return response; } catch (error: unknown) { logger.error(`[Plan Service] Error loading plan ${handle}:`, { message: (error as Error).message, status: (error as { response?: { status?: number } })?.response?.status, statusText: (error as { response?: { statusText?: string } })?.response ?.statusText, data: (error as { response?: { data?: unknown } })?.response?.data, headers: (error as { response?: { headers?: unknown } })?.response ?.headers, config: { url: (error as { config?: { url?: string } })?.config?.url, method: (error as { config?: { method?: string } })?.config?.method, baseURL: (error as { config?: { baseURL?: string } })?.config?.baseURL, headers: (error as { config?: { headers?: unknown } })?.config?.headers, }, }); if (retries > 1) { logger.log( `[Plan Service] Retrying plan ${handle}, ${retries - 1} attempts remaining`, ); await new Promise((resolve) => setTimeout(resolve, 1000)); return loadPlanWithRetry(handle, retries - 1); } return { error: `Failed to load plan ${handle} after ${4 - retries} attempts: ${(error as Error).message}`, status: (error as { response?: { status?: number } })?.response?.status, }; } }; /** * Makes a single API request to load a plan or claim. * Determines the appropriate endpoint based on the handle. * * @param handle - The unique identifier for the plan or claim * @returns Promise resolving to PlanResponse * @throws Will throw an error if the API request fails * * @remarks * - Automatically detects claim vs plan endpoints based on handle * - Uses axios for HTTP requests * - Provides detailed error logging * - Different endpoints: * - Claims: /api/claims/{handle} * - Plans: /api/plans/{handle} */ export const loadPlan = async (handle: string): Promise => { logger.log(`[Plan Service] Making API request for plan ${handle}`); const endpoint = handle.includes("claim") ? `/api/claims/${handle}` : `/api/plans/${handle}`; logger.log(`[Plan Service] Using endpoint: ${endpoint}`); try { const response = await axios.get(endpoint); return response; } catch (error: unknown) { logger.error(`[Plan Service] API request failed for ${handle}:`, { endpoint, error: (error as Error).message, response: (error as { response?: { data?: unknown } })?.response?.data, }); throw error; } };