/// 결제 주기에 따른 비용 변환 유틸리티 class BillingCostUtil { /// 결제 주기별 비용을 월 비용으로 변환 /// /// [amount]: 입력된 비용 /// [billingCycle]: 결제 주기 ('monthly', 'yearly', 'quarterly', 'half-yearly' 등) /// /// Returns: 월 환산 비용 static double convertToMonthlyCost(double amount, String billingCycle) { final normalizedCycle = _normalizeBillingCycle(billingCycle); switch (normalizedCycle) { case 'monthly': return amount; case 'yearly': return amount / 12; case 'quarterly': return amount / 3; case 'half-yearly': return amount / 6; case 'weekly': return amount * 4.33; // 평균 주당 4.33주 default: return amount; // 알 수 없는 주기는 그대로 반환 } } /// 월 비용을 결제 주기별 비용으로 역변환 /// /// [monthlyCost]: 월 비용 /// [billingCycle]: 결제 주기 /// /// Returns: 해당 주기의 실제 결제 금액 static double convertFromMonthlyCost(double monthlyCost, String billingCycle) { final normalizedCycle = _normalizeBillingCycle(billingCycle); switch (normalizedCycle) { case 'monthly': return monthlyCost; case 'yearly': return monthlyCost * 12; case 'quarterly': return monthlyCost * 3; case 'half-yearly': return monthlyCost * 6; case 'weekly': return monthlyCost / 4.33; default: return monthlyCost; } } /// 결제 주기를 정규화된 영어 키값으로 변환 static String _normalizeBillingCycle(String cycle) { switch (cycle.toLowerCase()) { case 'monthly': case '월간': case '매월': case '月間': case '月付': case '每月': case '毎月': return 'monthly'; case 'yearly': case 'annual': case 'annually': case '연간': case '매년': case '年間': case '年付': case '每年': return 'yearly'; case 'quarterly': case 'quarter': case '분기별': case '분기': case '季付': case '季度付': case '四半期': case '每季度': return 'quarterly'; case 'half-yearly': case 'half yearly': case 'semiannual': case 'semi-annual': case '반기별': case '半年付': case '半年払い': case '半年ごと': case '每半年': return 'half-yearly'; case 'weekly': case '주간': case '週間': case '周付': case '每周': return 'weekly'; default: return 'monthly'; } } /// 결제 주기의 배수 반환 (월 기준) /// /// 예: yearly = 12, quarterly = 3 static double getBillingCycleMultiplier(String billingCycle) { final normalizedCycle = _normalizeBillingCycle(billingCycle); switch (normalizedCycle) { case 'monthly': return 1.0; case 'yearly': return 12.0; case 'quarterly': return 3.0; case 'half-yearly': return 6.0; case 'weekly': return 1 / 4.33; default: return 1.0; } } /// 다음 결제일에서 이전 결제일 계산 /// /// [nextBillingDate]: 다음 결제 예정일 /// [billingCycle]: 결제 주기 /// /// Returns: 이전 결제일 (마지막으로 결제가 발생한 날짜) static DateTime getLastBillingDate( DateTime nextBillingDate, String billingCycle) { final normalizedCycle = _normalizeBillingCycle(billingCycle); switch (normalizedCycle) { case 'yearly': return DateTime( nextBillingDate.year - 1, nextBillingDate.month, nextBillingDate.day, ); case 'half-yearly': return DateTime( nextBillingDate.month <= 6 ? nextBillingDate.year - 1 : nextBillingDate.year, nextBillingDate.month <= 6 ? nextBillingDate.month + 6 : nextBillingDate.month - 6, nextBillingDate.day, ); case 'quarterly': return DateTime( nextBillingDate.month <= 3 ? nextBillingDate.year - 1 : nextBillingDate.year, nextBillingDate.month <= 3 ? nextBillingDate.month + 9 : nextBillingDate.month - 3, nextBillingDate.day, ); case 'monthly': return DateTime( nextBillingDate.month == 1 ? nextBillingDate.year - 1 : nextBillingDate.year, nextBillingDate.month == 1 ? 12 : nextBillingDate.month - 1, nextBillingDate.day, ); case 'weekly': return nextBillingDate.subtract(const Duration(days: 7)); default: return DateTime( nextBillingDate.month == 1 ? nextBillingDate.year - 1 : nextBillingDate.year, nextBillingDate.month == 1 ? 12 : nextBillingDate.month - 1, nextBillingDate.day, ); } } /// 특정 월에 결제가 발생하는지 확인 /// /// [nextBillingDate]: 다음 결제 예정일 /// [billingCycle]: 결제 주기 /// [targetYear]: 확인할 연도 /// [targetMonth]: 확인할 월 (1-12) /// /// Returns: 해당 월에 결제가 발생하면 true static bool hasBillingInMonth( DateTime nextBillingDate, String billingCycle, int targetYear, int targetMonth, ) { final normalizedCycle = _normalizeBillingCycle(billingCycle); // 주간 결제는 매주 발생하므로 항상 true if (normalizedCycle == 'weekly') { return true; } // 월간 결제는 매월 발생하므로 항상 true if (normalizedCycle == 'monthly') { return true; } // 결제 주기에 따른 개월 수 final cycleMonths = _getCycleMonths(normalizedCycle); // 연도+월을 포함한 개월 차이 계산 // nextBillingDate와 target 월 사이의 차이가 cycleMonths의 배수인지 확인 final targetStart = DateTime(targetYear, targetMonth, 1); final billingStart = DateTime(nextBillingDate.year, nextBillingDate.month, 1); final monthDiff = (billingStart.year - targetStart.year) * 12 + (billingStart.month - targetStart.month); // monthDiff가 cycleMonths의 배수이면 해당 월에 결제 발생 // 예: 연간 결제(2027-01), target=2026-01 → monthDiff=12, 12%12=0 → true (이전 결제) // 예: 연간 결제(2027-01), target=2026-02 → monthDiff=11, 11%12≠0 → false // 예: 연간 결제(2027-01), target=2027-01 → monthDiff=0, 0%12=0 → true return monthDiff % cycleMonths == 0; } /// 결제 주기별 개월 수 반환 static int _getCycleMonths(String normalizedCycle) { switch (normalizedCycle) { case 'yearly': return 12; case 'half-yearly': return 6; case 'quarterly': return 3; case 'monthly': return 1; default: return 1; } } }