MotorSignal-App/lib/services/billing/fake_billing_service.dart

116 lines
3.3 KiB
Dart

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<List<StorePurchase>> _purchaseStreamController =
StreamController<List<StorePurchase>>.broadcast();
final Set<String> _completedPurchaseTokens = <String>{};
bool available = true;
bool initializeSuccess = true;
List<StoreProduct> 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<List<StorePurchase>> get purchaseStream =>
_purchaseStreamController.stream;
@override
Future<bool> initialize() async => initializeSuccess && available;
@override
Future<bool> isAvailable() async => available;
@override
Future<List<StoreProduct>> queryProducts() async {
if (!available) return [];
return mockProducts;
}
@override
Future<bool> launchPurchaseFlow(String productId) async {
launchCount++;
lastPurchasedProductId = productId;
if (!available || !ProductCatalog.isValidProduct(productId)) return false;
return true;
}
@override
Future<void> restorePurchases() async {
restoreCount++;
}
@override
Future<void> 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<StorePurchase> 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();
}
}