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; final String _clubId; final String _userId; Subscription? _currentSubscription; List _availablePlans = []; bool _isLoading = false; String? _error; // 인앱 결제 서비스 final InAppPurchaseService _purchaseService; final http.Client _httpClient; // 기본 생성자 SubscriptionService(this._token, [this._clubId = '', this._userId = '']) : _purchaseService = InAppPurchaseService(), _httpClient = http.Client() { _loadAvailablePlans(); _initializeInAppPurchase(); } // 테스트용 생성자 factory SubscriptionService.forTest({ required http.Client client, required InAppPurchaseService purchaseService, required String userId, required String token, required String clubId, }) { return SubscriptionService._internal( token, clubId, userId, client, purchaseService, ); } // 내부 생성자 SubscriptionService._internal( this._token, this._clubId, this._userId, this._httpClient, this._purchaseService, ) { _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 _httpClient.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년 price: 0, currency: 'KRW', autoRenew: false, createdAt: DateTime.now(), updatedAt: DateTime.now(), ); } else { _error = '구독 정보를 불러오는데 실패했습니다. (${response.statusCode})'; } } catch (e) { _error = '구독 정보를 불러오는데 실패했습니다: $e'; } finally { _isLoading = false; notifyListeners(); } } /// 사용 가능한 구독 플랜 목록 로드 Future _loadAvailablePlans() async { _isLoading = true; _error = null; notifyListeners(); try { final response = await _httpClient.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 { // 인앱 구매 복원 시도 final restored = await _purchaseService.restorePurchases(); if (restored) { // 서버에서 현재 구독 정보 가져오기 await loadCurrentSubscription(); return true; } else { _error = '구독을 복원할 수 없습니다.'; return false; } } catch (e) { _error = '구독 복원 중 오류가 발생했습니다: $e'; return false; } finally { _isLoading = false; notifyListeners(); } } /// 구독 상태 검증 Future verifySubscription() async { _isLoading = true; _error = null; notifyListeners(); try { final response = await _httpClient.get( Uri.parse('$_baseUrl${ApiConfig.currentSubscription}'), 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.isPurchaseVerified; attempts++; // 오류가 발생한 경우 if (_purchaseService.error != null) { _error = _purchaseService.error; _isLoading = false; notifyListeners(); return false; } } if (!verified) { _error = '구독 검증 시간이 초과되었습니다.'; _isLoading = false; notifyListeners(); return false; } // 검증된 구매 정보 가져오기 final verifiedPurchase = _purchaseService.lastVerifiedPurchase; if (verifiedPurchase == null) { _error = '검증된 구매 정보를 찾을 수 없습니다.'; _isLoading = false; notifyListeners(); return false; } // 서버에 구독 생성 요청 final response = await _httpClient.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.purchaseID, 'productId': verifiedPurchase.productID, 'verificationData': verifiedPurchase.verificationData.serverVerificationData, }), ); if (response.statusCode == 200) { final data = jsonDecode(response.body); _currentSubscription = Subscription.fromJson(data); // 구매 검증 상태 초기화 _purchaseService.isPurchaseVerified = false; _purchaseService.lastVerifiedPurchase = 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 _httpClient.post( Uri.parse('$_baseUrl${ApiConfig.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 _httpClient.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 _httpClient.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, ); } }