import type { PaymentPlan } from "@/data/schemas/payment"; import type { CoinsOfferPlanView } from "./components/subscription-coins-offer-section"; import type { VipOfferPlanView } from "./components/subscription-vip-offer-section"; export type SubscriptionType = "vip" | "topup"; export interface FirstRechargeOfferView { badgeText: string; title: string; subtitle: string; renewalNotice: string | null; } export function isVipPlan(plan: PaymentPlan): boolean { return plan.vipDays !== null; } export function isCreditPlan(plan: PaymentPlan): boolean { return plan.dolAmount !== null; } function formatAmount(amountCents: number | null): string | null { if (amountCents === null) return null; if (!Number.isFinite(amountCents)) return null; return (amountCents / 100).toFixed(2).replace(/\.?0+$/, ""); } function formatOfferAmount(amountCents: number | null): string { return formatAmount(amountCents) ?? ""; } function formatOfferCurrency(currency: string): string { const normalized = currency.toUpperCase(); if (normalized === "USD") return "usd"; return currency.toLowerCase(); } function formatCoinCurrency(currency: string): string { const normalized = currency.toUpperCase(); if (normalized === "USD") return "US$"; if (normalized === "CNY") return "¥"; return `${currency} `; } function vipOfferTitle(plan: PaymentPlan): string { const key = `${plan.planId} ${plan.orderType} ${plan.planName}`.toLowerCase(); if (key.includes("year")) return "Annual"; if (key.includes("quarter")) return "Quarterly"; if (key.includes("month")) return "Monthly"; if (plan.vipDays !== null && plan.vipDays >= 300) return "Annual"; if (plan.vipDays !== null && plan.vipDays >= 80) return "Quarterly"; return plan.planName; } export function toVipOfferPlanView(plan: PaymentPlan): VipOfferPlanView { return { id: plan.planId, title: vipOfferTitle(plan), price: formatOfferAmount(plan.amountCents), currency: formatOfferCurrency(plan.currency), originalPrice: formatOfferAmount(plan.originalAmountCents), isFirstRechargeOffer: plan.isFirstRechargeOffer, mostPopular: plan.mostPopular, firstRechargeDiscountPercent: plan.firstRechargeDiscountPercent, promotionType: plan.promotionType, }; } export function toCoinsOfferPlanView(plan: PaymentPlan): CoinsOfferPlanView { return { id: plan.planId, coins: plan.dolAmount ?? 0, price: formatOfferAmount(plan.amountCents), currency: formatCoinCurrency(plan.currency), originalPrice: formatOfferAmount(plan.originalAmountCents), isFirstRechargeOffer: plan.isFirstRechargeOffer, mostPopular: plan.mostPopular, firstRechargeDiscountPercent: plan.firstRechargeDiscountPercent, promotionType: plan.promotionType, }; } export function toVipOfferPlanViews( plans: readonly PaymentPlan[], ): VipOfferPlanView[] { return plans.filter(isVipPlan).slice(0, 3).map(toVipOfferPlanView); } export function toCoinsOfferPlanViews( plans: readonly PaymentPlan[], ): CoinsOfferPlanView[] { return plans.filter(isCreditPlan).map(toCoinsOfferPlanView); } export function findSelectedSubscriptionPlan(input: { canSubscribeVip: boolean; selectedPlanId: string; vipPlans: readonly VipOfferPlanView[]; coinPlans: readonly CoinsOfferPlanView[]; }): VipOfferPlanView | CoinsOfferPlanView | null { const plans = input.canSubscribeVip ? [...input.vipPlans, ...input.coinPlans] : input.coinPlans; return plans.find((plan) => plan.id === input.selectedPlanId) ?? null; } export function requiresVipPlanConfirmation(input: { planId: string; vipPlans: readonly VipOfferPlanView[]; confirmedVipPlanIds: ReadonlySet; }): boolean { return ( input.vipPlans.some((plan) => plan.id === input.planId) && !input.confirmedVipPlanIds.has(input.planId) ); } export function canCheckoutSubscriptionPlan(input: { selectedPlanId: string; vipPlans: readonly VipOfferPlanView[]; confirmedVipPlanIds: ReadonlySet; }): boolean { if (!input.selectedPlanId) return false; return !requiresVipPlanConfirmation({ planId: input.selectedPlanId, vipPlans: input.vipPlans, confirmedVipPlanIds: input.confirmedVipPlanIds, }); } export function getDefaultSubscriptionPlanId(input: { canSubscribeVip: boolean; selectedPlanId: string; status: string; isLoadingPlans: boolean; selectedPlan: VipOfferPlanView | CoinsOfferPlanView | null; vipPlans: readonly VipOfferPlanView[]; coinPlans: readonly CoinsOfferPlanView[]; }): string | null { if (input.canSubscribeVip) { const firstVipPlanId = input.vipPlans[0]?.id ?? ""; const hasSelectedVipPlan = input.vipPlans.some( (plan) => plan.id === input.selectedPlanId, ); const hasSelectedCoinsPlan = input.coinPlans.some( (plan) => plan.id === input.selectedPlanId, ); const canSelectPlan = firstVipPlanId.length > 0 && (input.status === "ready" || input.status === "paid" || input.status === "failed"); if (!canSelectPlan || hasSelectedVipPlan || hasSelectedCoinsPlan) { return null; } return firstVipPlanId; } if (input.isLoadingPlans || input.coinPlans.length === 0) return null; if (input.selectedPlan !== null) return null; return input.coinPlans[0]?.id ?? null; } export function getFirstRechargeOfferView(input: { isFirstRecharge: boolean; subscriptionType: SubscriptionType; vipPlans: readonly VipOfferPlanView[]; coinPlans: readonly CoinsOfferPlanView[]; }): FirstRechargeOfferView | null { if (!input.isFirstRecharge) return null; const promotedPlan = [...input.vipPlans, ...input.coinPlans].find( (plan) => plan.isFirstRechargeOffer, ); const discountPercent = promotedPlan?.firstRechargeDiscountPercent ?? 50; const badgeText = `${discountPercent}% OFF`; return { badgeText, title: "First Recharge Offer", subtitle: "Your first recharge price is already applied. No code needed.", renewalNotice: input.subscriptionType === "vip" ? "First month 50% off. Renews at the regular price from the second month." : null, }; }