"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import { logger } from "@/lib/logger"; import { ordersService } from "@/features/orders/services/orders.service"; import { checkoutService } from "@/features/checkout/services/checkout.service"; import { ACTIVE_INTERNET_SUBSCRIPTION_WARNING } from "@/features/checkout/constants"; import { usePaymentMethods } from "@/features/billing/hooks/useBilling"; import { usePaymentRefresh } from "@/features/billing/hooks/usePaymentRefresh"; import { createLoadingState, createSuccessState, createErrorState, } from "@customer-portal/domain/toolkit"; import type { AsyncState } from "@customer-portal/domain/toolkit"; import { useActiveSubscriptions } from "@/features/subscriptions/hooks/useSubscriptions"; import { ORDER_TYPE, orderWithSkuValidationSchema, prepareOrderFromCart, type CheckoutCart, } from "@customer-portal/domain/orders"; import { CheckoutParamsService } from "@/features/checkout/services/checkout-params.service"; import { useAuthSession, useAuthStore } from "@/features/auth/services/auth.store"; import { ZodError } from "zod"; // Use domain Address type import type { Address } from "@customer-portal/domain/customer"; export function useCheckout() { const params = useSearchParams(); const router = useRouter(); const { isAuthenticated } = useAuthSession(); const [submitting, setSubmitting] = useState(false); const [addressConfirmed, setAddressConfirmed] = useState(false); const [checkoutState, setCheckoutState] = useState>({ status: "loading", }); // Load active subscriptions to enforce business rules client-side before submission const { data: activeSubs } = useActiveSubscriptions(); const hasActiveInternetSubscription = useMemo(() => { if (!Array.isArray(activeSubs)) return false; return activeSubs.some( subscription => String(subscription.groupName || subscription.productName || "") .toLowerCase() .includes("internet") && String(subscription.status || "").toLowerCase() === "active" ); }, [activeSubs]); const [activeInternetWarning, setActiveInternetWarning] = useState(null); const { data: paymentMethods, isLoading: paymentMethodsLoading, error: paymentMethodsError, refetch: refetchPaymentMethods, } = usePaymentMethods(); const paymentRefresh = usePaymentRefresh({ refetch: refetchPaymentMethods, attachFocusListeners: true, }); const paramsKey = params.toString(); const checkoutSnapshot = CheckoutParamsService.buildSnapshot(new URLSearchParams(paramsKey)); const { orderType, warnings } = checkoutSnapshot; const lastWarningSignature = useRef(null); useEffect(() => { if (warnings.length === 0) { return; } const signature = warnings.join("|"); if (signature === lastWarningSignature.current) { return; } lastWarningSignature.current = signature; warnings.forEach(message => { logger.warn("Checkout parameter warning", { message }); }); }, [warnings]); useEffect(() => { if (orderType !== ORDER_TYPE.INTERNET || !hasActiveInternetSubscription) { setActiveInternetWarning(null); return; } setActiveInternetWarning(ACTIVE_INTERNET_SUBSCRIPTION_WARNING); }, [orderType, hasActiveInternetSubscription]); useEffect(() => { // Wait for authentication before building cart if (!isAuthenticated) { return; } let mounted = true; void (async () => { const snapshot = CheckoutParamsService.buildSnapshot(new URLSearchParams(paramsKey)); const { orderType: snapshotOrderType, selections, configuration, planReference: snapshotPlan, } = snapshot; try { setCheckoutState(createLoadingState()); if (!snapshotPlan) { throw new Error("No plan selected. Please go back and select a plan."); } // Build cart using BFF service const cart = await checkoutService.buildCart(snapshotOrderType, selections, configuration); if (!mounted) return; setCheckoutState(createSuccessState(cart)); } catch (error) { if (mounted) { const reason = error instanceof Error ? error.message : "Failed to load checkout data"; setCheckoutState(createErrorState(new Error(reason))); } } })(); return () => { mounted = false; }; }, [isAuthenticated, paramsKey]); const handleSubmitOrder = useCallback(async () => { try { setSubmitting(true); if (checkoutState.status !== "success") { throw new Error("Checkout data not loaded"); } const cart = checkoutState.data; // Debug logging to check cart contents console.log("[DEBUG] Cart data:", cart); console.log("[DEBUG] Cart items:", cart.items); // Validate cart before submission await checkoutService.validateCart(cart); // Use domain helper to prepare order data // This encapsulates SKU extraction and payload formatting const orderData = prepareOrderFromCart(cart, orderType); console.log("[DEBUG] Extracted SKUs from cart:", orderData.skus); const currentUserId = useAuthStore.getState().user?.id; if (currentUserId) { try { orderWithSkuValidationSchema.parse({ ...orderData, userId: currentUserId, }); } catch (validationError) { if (validationError instanceof ZodError) { const firstIssue = validationError.issues.at(0); throw new Error(firstIssue?.message || "Order contains invalid data"); } throw validationError; } } const response = await ordersService.createOrder(orderData); router.push(`/orders/${response.sfOrderId}?status=success`); } catch (error) { let errorMessage = "Order submission failed"; if (error instanceof Error) errorMessage = error.message; setCheckoutState(createErrorState(new Error(errorMessage))); } finally { setSubmitting(false); } }, [checkoutState, orderType, router]); const confirmAddress = useCallback((address?: Address) => { setAddressConfirmed(true); void address; }, []); const markAddressIncomplete = useCallback(() => { setAddressConfirmed(false); }, []); const navigateBackToConfigure = useCallback(() => { // State is already persisted in Zustand store // Just need to restore params and navigate const urlParams = new URLSearchParams(paramsKey); urlParams.delete("type"); // Remove type param as it's not needed const configureUrl = orderType === ORDER_TYPE.INTERNET ? `/shop/internet/configure?${urlParams.toString()}` : `/shop/sim/configure?${urlParams.toString()}`; router.push(configureUrl); }, [orderType, paramsKey, router]); return { checkoutState, submitting, orderType, addressConfirmed, paymentMethods, paymentMethodsLoading, paymentMethodsError, paymentRefresh, confirmAddress, markAddressIncomplete, handleSubmitOrder, navigateBackToConfigure, activeInternetWarning, } as const; }