import 'dart:async'; import 'package:crypto/crypto.dart'; import 'billing_models.dart'; import 'billing_service.dart'; import 'product_catalog.dart'; /// Test/Mock implementation of [BillingService] for automated test suites. /// Never selected in release execution paths. class FakeBillingService implements BillingService { final StreamController> _purchaseStreamController = StreamController>.broadcast(); final Set _completedPurchaseTokens = {}; bool available = true; bool initializeSuccess = true; List mockProducts = [ const StoreProduct( id: ProductCatalog.proMonthly, title: 'MotorSignal Pro Monthly', description: 'Monthly unlimited access to all Pro features', price: '\$4.99', rawPrice: 4.99, currencyCode: 'USD', currencySymbol: '\$', isSubscription: true, ), const StoreProduct( id: ProductCatalog.proLifetime, title: 'MotorSignal Pro Lifetime', description: 'Permanent unlimited access to all Pro features', price: '\$49.99', rawPrice: 49.99, currencyCode: 'USD', currencySymbol: '\$', isSubscription: false, ), ]; int launchCount = 0; int restoreCount = 0; String? lastPurchasedProductId; @override Stream> get purchaseStream => _purchaseStreamController.stream; @override Future initialize() async => initializeSuccess && available; @override Future isAvailable() async => available; @override Future> queryProducts() async { if (!available) return []; return mockProducts; } @override Future launchPurchaseFlow(String productId) async { launchCount++; lastPurchasedProductId = productId; if (!available || !ProductCatalog.isValidProduct(productId)) return false; return true; } @override Future restorePurchases() async { restoreCount++; } @override Future completePurchase(StorePurchase purchase) async { final token = purchase.purchaseToken ?? purchase.purchaseId; _completedPurchaseTokens.add(token); } bool isPurchaseCompleted(String tokenOrId) => _completedPurchaseTokens.contains(tokenOrId); /// Helper for tests to simulate incoming purchase events from the store. void emitPurchases(List purchases) { if (!_purchaseStreamController.isClosed) { _purchaseStreamController.add(purchases); } } /// Helper to create a fake StorePurchase for tests. StorePurchase createFakePurchase({ required String productId, required StorePurchaseStatus status, String? purchaseId, String? token, bool pendingComplete = true, }) { final pToken = token ?? 'fake_token_${DateTime.now().millisecondsSinceEpoch}'; return StorePurchase( purchaseId: purchaseId ?? 'fake_purchase_${DateTime.now().millisecondsSinceEpoch}', productId: productId, status: status, transactionDate: DateTime.now().toIso8601String(), purchaseToken: pToken, purchaseTokenHash: sha256.convert(pToken.codeUnits).toString(), pendingCompletePurchase: pendingComplete, ); } @override void dispose() { _purchaseStreamController.close(); } }