import { ExceptionHandler } from "@/core/errors"; import { PaymentPlan, type PaymentPlansResponse } from "@/data/dto/payment"; import type { PaymentState } from "./payment-state"; export const MAX_ORDER_POLLING_MS = 5 * 60 * 1000; export const PAYMENT_TIMEOUT_ERROR_MESSAGE = "Payment timed out. Please try again."; export function toPaymentErrorMessage(error: unknown): string { return ExceptionHandler.message(error); } export function hasOrderPollingTimedOut( startedAt: number | null, now = Date.now(), maxDurationMs = MAX_ORDER_POLLING_MS, ): boolean { return startedAt !== null && now - startedAt >= maxDurationMs; } export function defaultAutoRenewForPlan( planId: string, plans: readonly PaymentPlan[] = [], ): boolean { const plan = plans.find((item) => item.planId === planId); if (plan?.dolAmount !== null && plan?.dolAmount !== undefined) return false; return ( !planId.includes("lifetime") && !planId.startsWith("dol_") && !planId.startsWith("points_") && !planId.startsWith("credit_") ); } export function getDefaultPlanId(plans: readonly PaymentPlan[]): string { return ( plans.find((plan) => plan.planId === "vip_monthly")?.planId ?? plans.find((plan) => plan.orderType.startsWith("vip_"))?.planId ?? plans[0]?.planId ?? "" ); } export function hydratePlansState( plans: PaymentPlan[], selectedPlanId: string, ): Pick< PaymentState, "plans" | "selectedPlanId" | "autoRenew" | "errorMessage" > { const nextSelectedPlanId = selectedPlanId || getDefaultPlanId(plans); return { plans, selectedPlanId: nextSelectedPlanId, autoRenew: defaultAutoRenewForPlan(nextSelectedPlanId, plans), errorMessage: null, }; } export function hydratePlansResponseState( response: PaymentPlansResponse, selectedPlanId: string, ): Pick< PaymentState, | "plans" | "isFirstRecharge" | "selectedPlanId" | "autoRenew" | "errorMessage" > { const plans = normalizeFirstRechargePlans( response.plans, response.isFirstRecharge, ); return { ...hydratePlansState(plans, selectedPlanId), isFirstRecharge: response.isFirstRecharge, }; } export function refreshPlansState( plans: PaymentPlan[], selectedPlanId: string, ): Pick< PaymentState, "plans" | "selectedPlanId" | "autoRenew" | "errorMessage" > { const nextSelectedPlanId = plans.some((plan) => plan.planId === selectedPlanId) ? selectedPlanId : getDefaultPlanId(plans); return { plans, selectedPlanId: nextSelectedPlanId, autoRenew: defaultAutoRenewForPlan(nextSelectedPlanId, plans), errorMessage: null, }; } export function refreshPlansResponseState( response: PaymentPlansResponse, selectedPlanId: string, ): Pick< PaymentState, | "plans" | "isFirstRecharge" | "selectedPlanId" | "autoRenew" | "errorMessage" > { const plans = normalizeFirstRechargePlans( response.plans, response.isFirstRecharge, ); return { ...refreshPlansState(plans, selectedPlanId), isFirstRecharge: response.isFirstRecharge, }; } export function normalizeFirstRechargePlans( plans: readonly PaymentPlan[], isFirstRecharge: boolean, ): PaymentPlan[] { if (isFirstRecharge) return [...plans]; return plans.map(consumeFirstRechargePlan); } export function consumeFirstRechargePlan(plan: PaymentPlan): PaymentPlan { if (!plan.isFirstRechargeOffer) { return PaymentPlan.from({ ...plan.toJson(), isFirstRechargeOffer: false, firstRechargeDiscountPercent: null, promotionType: null, }); } const originalAmountCents = plan.originalAmountCents; return PaymentPlan.from({ ...plan.toJson(), amountCents: originalAmountCents ?? plan.amountCents, originalAmountCents: originalAmountCents ?? plan.originalAmountCents, isFirstRechargeOffer: false, firstRechargeDiscountPercent: null, promotionType: null, }); } export function consumeFirstRechargeState( context: PaymentState, ): Pick< PaymentState, | "plans" | "isFirstRecharge" | "selectedPlanId" | "autoRenew" | "errorMessage" > { const plans = context.plans.map(consumeFirstRechargePlan); return { ...refreshPlansState(plans, context.selectedPlanId), isFirstRecharge: false, }; } export function selectPlanState( planId: string, plans: readonly PaymentPlan[], ): Partial { return { ...resetOrderState(), selectedPlanId: planId, autoRenew: defaultAutoRenewForPlan(planId, plans), }; } export function resetOrderState(): Partial { return { currentOrderId: null, payParams: null, orderStatus: null, orderPollingStartedAt: null, errorMessage: null, }; }