import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import '../config/api_config.dart'; import '../models/subscription_model.dart'; import 'in_app_purchase_service.dart'; /// 구독 관련 서비스 class SubscriptionService extends ChangeNotifier { final String _baseUrl = ApiConfig.baseUrl; final String _token; Subscription? _currentSubscription; List _availablePlans = []; bool _isLoading = false; String? _error; // 인앱 결제 서비스 final InAppPurchaseService _purchaseService = InAppPurchaseService(); SubscriptionService(this._token) { _loadAvailablePlans(); _initializeInAppPurchase(); } // 게터 Subscription? get currentSubscription => _currentSubscription; List get availablePlans => _availablePlans; bool get isLoading => _isLoading || _purchaseService.isLoading; String? get error => _error ?? _purchaseService.error; bool get hasSubscription => _currentSubscription != null; bool get isActive => _currentSubscription?.isActive ?? false; // 인앱 결제 관련 게터 InAppPurchaseService get purchaseService => _purchaseService; List get products => _purchaseService.products; /// 현재 구독 정보 로드 Future loadCurrentSubscription() async { _isLoading = true; _error = null; notifyListeners(); try { final response = await http.post( Uri.parse('$_baseUrl${ApiConfig.currentSubscription}'), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer $_token', }, body: jsonEncode({ 'clubId': _clubId, }), ); if (response.statusCode == 200) { final data = jsonDecode(response.body); _currentSubscription = Subscription.fromJson(data); } else if (response.statusCode == 404) { // 구독 정보가 없는 경우 (Free 플랜으로 간주) _currentSubscription = Subscription( id: 'free', userId: _userId, planType: SubscriptionPlanType.free, status: SubscriptionStatus.active, startDate: DateTime.now(), endDate: DateTime.now().add(const Duration(days: 36500)), // 100년 autoRenew: false, lastPaymentDate: null, nextPaymentDate: null, ); } else { _error = '구독 정보를 불러오는데 실패했습니다. (${response.statusCode})'; } } catch (e) { _error = '구독 정보를 불러오는데 실패했습니다: $e'; } finally { _isLoading = false; notifyListeners(); } } /// 사용 가능한 구독 플랜 목록 로드 Future _loadAvailablePlans() async { _isLoading = true; _error = null; notifyListeners(); try { final response = await http.get( Uri.parse('$_baseUrl${ApiConfig.subscriptionPlans}'), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer $_token', }, ); if (response.statusCode == 200) { final List data = jsonDecode(response.body); _availablePlans = data.map((plan) => SubscriptionPlan.fromJson(plan)).toList(); } else { _error = '구독 플랜 정보를 불러오는데 실패했습니다. (${response.statusCode})'; // 기본 플랜 정보 사용 _availablePlans = SubscriptionPlan.getDefaultPlans(); } } catch (e) { _error = '구독 플랜 정보를 불러오는데 실패했습니다: $e'; // 기본 플랜 정보 사용 _availablePlans = SubscriptionPlan.getDefaultPlans(); } finally { _isLoading = false; notifyListeners(); } } /// 인앱 결제 서비스 초기화 Future _initializeInAppPurchase() async { // 인앱 결제 서비스는 자체적으로 초기화됨 // 필요한 경우 추가 설정 } /// 구독 복원 Future restoreSubscriptions() async { _isLoading = true; _error = null; notifyListeners(); try { // 1. 인앱 결제 복원 요청 final restoreSuccess = await _purchaseService.restorePurchases(); if (!restoreSuccess) { _error = '구독 복원에 실패했습니다.'; return false; } // 2. 서버에서 최신 구독 정보 조회 await fetchCurrentSubscription(); return true; } catch (e) { _error = '구독 복원에 실패했습니다: $e'; return false; } finally { _isLoading = false; notifyListeners(); } } /// 구독 상태 검증 Future verifySubscription() async { _isLoading = true; _error = null; notifyListeners(); try { final response = await http.get( Uri.parse('$_baseUrl/api/subscriptions/verify'), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer $_token', }, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); _currentSubscription = Subscription.fromJson(data); notifyListeners(); return true; } else { _error = '구독 검증에 실패했습니다. (${response.statusCode})'; return false; } } catch (e) { _error = '구독 검증에 실패했습니다: $e'; return false; } finally { _isLoading = false; notifyListeners(); } } /// 새 구독 생성 (인앱 결제 통합) Future createSubscription(SubscriptionPlanType planType, bool isYearly) async { _isLoading = true; _error = null; notifyListeners(); try { // 인앱 결제 시작 final success = await _purchaseService.purchaseSubscription(planType); if (!success) { _error = '구독 구매 요청에 실패했습니다.'; _isLoading = false; notifyListeners(); return false; } // 구매 검증 결과 대기 bool verified = false; int attempts = 0; const maxAttempts = 10; // 구매 검증이 완료될 때까지 최대 10회 확인 while (!verified && attempts < maxAttempts) { await Future.delayed(const Duration(seconds: 1)); verified = _purchaseService._purchaseVerified; attempts++; // 오류가 발생한 경우 if (_purchaseService.error != null) { _error = _purchaseService.error; _isLoading = false; notifyListeners(); return false; } } if (!verified) { _error = '구독 검증 시간이 초과되었습니다.'; _isLoading = false; notifyListeners(); return false; } // 검증된 구매 정보 가져오기 final verifiedPurchase = _purchaseService._verifiedPurchase; if (verifiedPurchase == null) { _error = '검증된 구매 정보를 찾을 수 없습니다.'; _isLoading = false; notifyListeners(); return false; } // 서버에 구독 생성 요청 final response = await http.post( Uri.parse('$_baseUrl${ApiConfig.subscribeClub}'), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer $_token', }, body: jsonEncode({ 'planType': planType.toString().split('.').last, 'isYearly': isYearly, 'clubId': _clubId, 'transactionId': verifiedPurchase['transactionId'], 'productId': verifiedPurchase['productId'], 'verificationData': verifiedPurchase['verificationData'], }), ); if (response.statusCode == 200) { final data = jsonDecode(response.body); _currentSubscription = Subscription.fromJson(data); // 구매 검증 상태 초기화 _purchaseService._purchaseVerified = false; _purchaseService._verifiedPurchase = null; notifyListeners(); return true; } else { _error = '구독 생성에 실패했습니다. (${response.statusCode})'; return false; } } catch (e) { _error = '구독 생성 중 오류가 발생했습니다: $e'; return false; } finally { _isLoading = false; notifyListeners(); } } /// 구독 취소 Future cancelSubscription() async { if (_currentSubscription == null) { _error = '취소할 구독이 없습니다.'; notifyListeners(); return false; } _isLoading = true; _error = null; notifyListeners(); try { final response = await http.post( Uri.parse('$_baseUrl/api/subscriptions/${_currentSubscription!.id}/cancel'), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer $_token', }, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); _currentSubscription = Subscription.fromJson(data); notifyListeners(); return true; } else { _error = '구독 취소에 실패했습니다. (${response.statusCode})'; return false; } } catch (e) { _error = '구독 취소에 실패했습니다: $e'; return false; } finally { _isLoading = false; notifyListeners(); } } /// 구독 플랜 변경 Future changePlan(SubscriptionPlanType newPlanType, bool isYearly) async { if (_currentSubscription == null) { _error = '변경할 구독이 없습니다.'; notifyListeners(); return false; } _isLoading = true; _error = null; notifyListeners(); try { final response = await http.post( Uri.parse('$_baseUrl${ApiConfig.changePlan}'), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer $_token', }, body: jsonEncode({ 'subscriptionId': _currentSubscription!.id, 'planType': newPlanType.toString().split('.').last, 'isYearly': isYearly, 'clubId': _clubId, }), ); if (response.statusCode == 200) { final data = jsonDecode(response.body); _currentSubscription = Subscription.fromJson(data); notifyListeners(); return true; } else { _error = '구독 플랜 변경에 실패했습니다. (${response.statusCode})'; return false; } } catch (e) { _error = '구독 플랜 변경에 실패했습니다: $e'; return false; } finally { _isLoading = false; notifyListeners(); } } /// 자동 갱신 설정 변경 Future toggleAutoRenew() async { if (_currentSubscription == null) { _error = '변경할 구독이 없습니다.'; notifyListeners(); return false; } _isLoading = true; _error = null; notifyListeners(); try { final response = await http.post( Uri.parse('$_baseUrl${ApiConfig.subscriptions}/autorenew'), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer $_token', }, body: jsonEncode({ 'subscriptionId': _currentSubscription!.id, 'autoRenew': !_currentSubscription!.autoRenew, 'clubId': _clubId, }), ); if (response.statusCode == 200) { final data = jsonDecode(response.body); _currentSubscription = Subscription.fromJson(data); notifyListeners(); return true; } else { _error = '자동 갱신 설정 변경에 실패했습니다. (${response.statusCode})'; return false; } } catch (e) { _error = '자동 갱신 설정 변경에 실패했습니다: $e'; return false; } finally { _isLoading = false; notifyListeners(); } } /// 특정 기능이 현재 구독에서 사용 가능한지 확인 bool canUseFeature(String featureName) { if (_currentSubscription == null || !_currentSubscription!.isActive) { return false; } // 기능별 접근 권한 확인 로직 switch (featureName) { case 'advancedStats': return _getSubscriptionPlan(_currentSubscription!.planType).hasAdvancedStats; case 'customization': return _getSubscriptionPlan(_currentSubscription!.planType).hasCustomization; case 'prioritySupport': return _getSubscriptionPlan(_currentSubscription!.planType).hasPriority; default: return false; } } /// 현재 구독으로 추가 가능한 최대 회원 수 확인 int getMaxMembers() { if (_currentSubscription == null || !_currentSubscription!.isActive) { return SubscriptionPlan.getDefaultPlans().first.maxMembers; // 무료 플랜 } return _getSubscriptionPlan(_currentSubscription!.planType).maxMembers; } /// 현재 구독으로 추가 가능한 최대 이벤트 수 확인 int getMaxEvents() { if (_currentSubscription == null || !_currentSubscription!.isActive) { return SubscriptionPlan.getDefaultPlans().first.maxEvents; // 무료 플랜 } return _getSubscriptionPlan(_currentSubscription!.planType).maxEvents; } /// 구독 플랜 타입에 해당하는 플랜 정보 가져오기 SubscriptionPlan _getSubscriptionPlan(SubscriptionPlanType planType) { return _availablePlans.firstWhere( (plan) => plan.type == planType, orElse: () => SubscriptionPlan.getDefaultPlans().first, ); } }