Skip to content

Commit

Permalink
[Infrastructure] Added initial purchase_service.dart
Browse files Browse the repository at this point in the history
  • Loading branch information
Wolfteam committed Feb 27, 2022
1 parent 5f73452 commit 6d5ed56
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 1 deletion.
13 changes: 13 additions & 0 deletions lib/domain/models/donations/package_item_model.dart
@@ -0,0 +1,13 @@
class PackageItemModel {
final String identifier;
final String offeringIdentifier;
final String productIdentifier;
final String priceString;

PackageItemModel({
required this.identifier,
required this.offeringIdentifier,
required this.productIdentifier,
required this.priceString,
});
}
1 change: 1 addition & 0 deletions lib/domain/models/models.dart
Expand Up @@ -41,6 +41,7 @@ export 'db/translations/translation_monster_file.dart';
export 'db/translations/translation_weapon_file.dart';
export 'db/weapons/weapon_file_model.dart';
export 'db/weapons/weapons_file.dart';
export 'donations/package_item_model.dart';
export 'elements/element_card_model.dart';
export 'elements/element_reaction_card_model.dart';
export 'game_codes/game_code_model.dart';
Expand Down
17 changes: 17 additions & 0 deletions lib/domain/services/purchase_service.dart
@@ -0,0 +1,17 @@
import 'package:shiori/domain/models/models.dart';

abstract class PurchaseService {
bool get isInitialized;

Future<bool> init();

Future<bool> isPlatformSupported();

Future<bool> logIn(String userId);

Future<List<PackageItemModel>> getInAppPurchases();

Future<bool> purchase(String userId, String identifier, String offeringIdentifier);

Future<bool> restorePurchases(String userId, {String? entitlementIdentifier});
}
1 change: 1 addition & 0 deletions lib/infrastructure/infrastructure.dart
Expand Up @@ -9,5 +9,6 @@ export 'package:shiori/infrastructure/logging_service.dart';
export 'package:shiori/infrastructure/network_service.dart';
export 'package:shiori/infrastructure/notification_service.dart';
export 'package:shiori/infrastructure/persistence/custom_builds_data_service.dart';
export 'package:shiori/infrastructure/purchase_service.dart';
export 'package:shiori/infrastructure/settings_service.dart';
export 'package:shiori/infrastructure/telemetry/telemetry_service.dart';
144 changes: 144 additions & 0 deletions lib/infrastructure/purchase_service.dart
@@ -0,0 +1,144 @@
import 'dart:io';

import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'package:shiori/domain/models/models.dart';
import 'package:shiori/domain/services/logging_service.dart';
import 'package:shiori/domain/services/purchase_service.dart';
import 'package:shiori/infrastructure/secrets.dart';

class PurchaseServiceImpl implements PurchaseService {
bool _initialized = false;

final LoggingService _loggingService;

@override
bool get isInitialized => _initialized;

PurchaseServiceImpl(this._loggingService);

@override
Future<bool> init() async {
final isSupported = await isPlatformSupported();
if (!isSupported) {
return false;
}

try {
if (_initialized) {
return true;
}

if (!kReleaseMode) {
await Purchases.setDebugLogsEnabled(true);
}

final key = Platform.isAndroid ? Secrets.androidPurchasesKey : throw Exception('Platform not supported');
await Purchases.setup(key);
_initialized = true;
return true;
} catch (e, s) {
_handleError('init', e, s);
return true;
}
}

@override
Future<bool> isPlatformSupported() {
if (kIsWeb) {
return Future.value(false);
}

if (Platform.isAndroid) {
return Future.value(true);
}

return Future.value(false);
}

@override
Future<bool> logIn(String userId) async {
try {
await Purchases.logIn(userId);
return true;
} catch (e, s) {
_handleError('logIn', e, s);
return false;
}
}

@override
Future<List<PackageItemModel>> getInAppPurchases() async {
try {
final offerings = await Purchases.getOfferings();
return offerings.all.values
.expand((o) => o.availablePackages)
.map(
(p) => PackageItemModel(
identifier: p.identifier,
offeringIdentifier: p.offeringIdentifier,
priceString: p.product.priceString,
productIdentifier: p.product.identifier,
),
)
.toList();
} catch (e, s) {
_handleError('getInAppPurchases', e, s);
return [];
}
}

@override
Future<bool> purchase(String userId, String identifier, String offeringIdentifier) async {
final loggedIn = await logIn(userId);
if (!loggedIn) {
return false;
}
try {
//behind the scenes, the purchase method just uses two params...
//that's why I create dummy object to satisfy the constructor
const dummyProduct = Product('', '', '', 0, '0', '');
final package = Package(identifier, PackageType.lifetime, dummyProduct, offeringIdentifier);
await Purchases.purchasePackage(package);
return true;
} catch (e, s) {
_handleError('purchase', e, s);
return false;
}
}

@override
Future<bool> restorePurchases(String userId, {String? entitlementIdentifier}) async {
final loggedIn = await logIn(userId);
if (!loggedIn) {
return false;
}

try {
final transactions = await Purchases.restoreTransactions();
if (entitlementIdentifier == null) {
return transactions.entitlements.active.isNotEmpty;
}

final entitlement = transactions.entitlements.active.values.firstWhereOrNull((el) => el.identifier == entitlementIdentifier);
return entitlement != null;
} catch (e, s) {
_handleError('restorePurchases', e, s);
return false;
}
}

void _handleError(String methodName, dynamic e, StackTrace s) {
if (e is PlatformException) {
final errorCode = PurchasesErrorHelper.getErrorCode(e);
if (errorCode != PurchasesErrorCode.purchaseCancelledError) {
_loggingService.error(runtimeType, '$methodName: Purchase error = $errorCode', e, s);
}
return;
}

_loggingService.error(runtimeType, '$methodName: Unknown error occurred', e, s);
}
}
2 changes: 1 addition & 1 deletion lib/infrastructure/telemetry/telemetry_service.dart
Expand Up @@ -3,8 +3,8 @@ import 'package:shiori/domain/enums/enums.dart';
import 'package:shiori/domain/models/models.dart';
import 'package:shiori/domain/services/device_info_service.dart';
import 'package:shiori/domain/services/telemetry_service.dart';
import 'package:shiori/infrastructure/secrets.dart';
import 'package:shiori/infrastructure/telemetry/flutter_appcenter_bundle.dart';
import 'package:shiori/infrastructure/telemetry/secrets.dart';

class TelemetryServiceImpl implements TelemetryService {
final DeviceInfoService _deviceInfoService;
Expand Down

0 comments on commit 6d5ed56

Please sign in to comment.