import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'entitlement_state.dart'; /// Contract defining persistence operations for cached entitlement state. /// This abstraction isolates the billing coordinator and domain from physical storage. abstract class EntitlementStorage { /// Reads cached entitlement state, returning [EntitlementState.free] if none exists or invalid. Future read(); /// Persists [state] to cache. Future write(EntitlementState state); /// Clears cached entitlement, reverting cache to default. Future clear(); } /// In-memory implementation of [EntitlementStorage] for unit testing. class InMemoryEntitlementStorage implements EntitlementStorage { EntitlementState _state; InMemoryEntitlementStorage([this._state = const EntitlementState.free()]); @override Future read() async => _state; @override Future write(EntitlementState state) async { _state = state; } @override Future clear() async { _state = const EntitlementState.free(); } } /// Production implementation backed by [FlutterSecureStorage]. /// Stores normalized JSON with versioning. Never stores raw Google Play purchase tokens. class SecureEntitlementStorage implements EntitlementStorage { static const String storageKey = 'motorsignal_entitlement_v1'; final FlutterSecureStorage _storage; const SecureEntitlementStorage({FlutterSecureStorage? storage}) : _storage = storage ?? const FlutterSecureStorage(); @override Future read() async { try { final raw = await _storage.read(key: storageKey); if (raw == null || raw.trim().isEmpty) { return const EntitlementState.free(); } final decoded = jsonDecode(raw); if (decoded is! Map) { debugPrint('[EntitlementStorage] Cache payload not a valid JSON map'); return const EntitlementState.free(); } return EntitlementState.fromJson(decoded); } catch (e, st) { debugPrint( '[EntitlementStorage] Error reading cached entitlement: $e\n$st', ); return const EntitlementState.free(); } } @override Future write(EntitlementState state) async { try { final payload = jsonEncode(state.toJson()); await _storage.write(key: storageKey, value: payload); } catch (e, st) { debugPrint( '[EntitlementStorage] Error writing cached entitlement: $e\n$st', ); } } @override Future clear() async { try { await _storage.delete(key: storageKey); } catch (e, st) { debugPrint( '[EntitlementStorage] Error clearing cached entitlement: $e\n$st', ); } } }