import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import 'ad_config.dart'; /// Wraps the non-consumable `remove_ads` purchase. On any verified purchase /// or restore of the product it invokes [onEntitlementGranted]; the caller /// flips the AdsRemovedNotifier. All failures are swallowed. class IapService { IapService({required this.onEntitlementGranted}); final Future Function() onEntitlementGranted; final InAppPurchase _iap = InAppPurchase.instance; StreamSubscription>? _sub; ProductDetails? _product; bool get available => _available; bool _available = false; ProductDetails? get product => _product; Future initialize() async { try { _available = await _iap.isAvailable(); if (!_available) return; _sub = _iap.purchaseStream.listen(_onPurchases, onError: (_) {}, cancelOnError: false); final response = await _iap.queryProductDetails({AdConfig.removeAdsProductId}); if (response.productDetails.isNotEmpty) { _product = response.productDetails.first; } } catch (e) { debugPrint('IAP init failed: $e'); } } Future buyRemoveAds() async { final product = _product; if (product == null) return; try { await _iap.buyNonConsumable( purchaseParam: PurchaseParam(productDetails: product)); } catch (e) { debugPrint('IAP buy failed: $e'); } } Future restorePurchases() async { try { await _iap.restorePurchases(); } catch (e) { debugPrint('IAP restore failed: $e'); } } Future _onPurchases(List purchases) async { for (final p in purchases) { if (p.productID != AdConfig.removeAdsProductId) continue; if (p.status == PurchaseStatus.purchased || p.status == PurchaseStatus.restored) { await onEntitlementGranted(); } if (p.pendingCompletePurchase) { await _iap.completePurchase(p); } } } void dispose() => _sub?.cancel(); }