Handle subscription expired errors

This commit is contained in:
Vishnu Mohandas 2021-02-02 22:05:38 +05:30
parent 0ab2fbd755
commit a10ad8c279
11 changed files with 73 additions and 56 deletions

View file

@ -0,0 +1,3 @@
import 'package:photos/events/event.dart';
class SubscriptionPurchasedEvent extends Event {}

View file

@ -1 +0,0 @@
class UserAuthenticatedEvent {}

View file

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
// import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:logging/logging.dart';
@ -29,10 +30,10 @@ class BillingService {
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
InAppPurchaseConnection.enablePendingPurchases();
// if (Platform.isIOS && kDebugMode) {
// await FlutterInappPurchase.instance.initConnection;
// FlutterInappPurchase.instance.clearTransactionIOS();
// }
if (Platform.isIOS && kDebugMode) {
await FlutterInappPurchase.instance.initConnection;
FlutterInappPurchase.instance.clearTransactionIOS();
}
InAppPurchaseConnection.instance.purchaseUpdatedStream.listen((purchases) {
if (_isOnSubscriptionPage) {
return;
@ -51,9 +52,6 @@ class BillingService {
}
}
});
if (_config.hasConfiguredAccount() && !hasActiveSubscription()) {
fetchSubscription();
}
}
Future<List<BillingPlan>> getBillingPlans() {

View file

@ -11,7 +11,7 @@ import 'package:photos/core/network.dart';
import 'package:photos/db/files_db.dart';
import 'package:photos/events/collection_updated_event.dart';
import 'package:photos/events/sync_status_update_event.dart';
import 'package:photos/events/user_authenticated_event.dart';
import 'package:photos/events/subscription_purchased_event.dart';
import 'package:photos/models/file_type.dart';
import 'package:photos/services/billing_service.dart';
import 'package:photos/services/collections_service.dart';
@ -44,7 +44,7 @@ class SyncService {
static final _diffLimit = 200;
SyncService._privateConstructor() {
Bus.instance.on<UserAuthenticatedEvent>().listen((event) {
Bus.instance.on<SubscriptionPurchasedEvent>().listen((event) {
sync();
});
@ -94,6 +94,9 @@ class SyncService {
_syncStopRequested = false;
Bus.instance
.fire(SyncStatusUpdate(SyncStatus.completed, wasStopped: true));
} on NoActiveSubscriptionError {
Bus.instance.fire(SyncStatusUpdate(SyncStatus.error,
reason: "your subscription has expired"));
} catch (e, s) {
_logger.severe(e, s);
Bus.instance.fire(SyncStatusUpdate(SyncStatus.error));
@ -230,6 +233,12 @@ class SyncService {
}
Future<bool> _uploadDiff() async {
if (!BillingService.instance.hasActiveSubscription()) {
await BillingService.instance.fetchSubscription();
if (!BillingService.instance.hasActiveSubscription()) {
throw NoActiveSubscriptionError();
}
}
final foldersToBackUp = Configuration.instance.getPathsToBackUp();
final filesToBeUploaded =
await _db.getFilesToBeUploadedWithinFolders(foldersToBackUp);
@ -277,13 +286,15 @@ class SyncService {
futures.add(future);
}
try {
await Future.wait(futures);
await Future.wait(futures, eagerError: true);
} on InvalidFileError {
// Do nothing
} on WiFiUnavailableError {
throw WiFiUnavailableError();
} on SyncStopRequestedError {
throw SyncStopRequestedError();
} on NoActiveSubscriptionError {
throw NoActiveSubscriptionError();
} catch (e, s) {
_isSyncInProgress = false;
Bus.instance.fire(SyncStatusUpdate(SyncStatus.error));

View file

@ -6,7 +6,7 @@ import 'package:logging/logging.dart';
import 'package:page_transition/page_transition.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/events/user_authenticated_event.dart';
import 'package:photos/events/subscription_purchased_event.dart';
import 'package:photos/models/collection.dart';
import 'package:photos/models/selected_files.dart';
import 'package:photos/services/collections_service.dart';
@ -60,7 +60,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
};
widget.selectedFiles.addListener(_selectedFilesListener);
_userAuthEventSubscription =
Bus.instance.on<UserAuthenticatedEvent>().listen((event) {
Bus.instance.on<SubscriptionPurchasedEvent>().listen((event) {
setState(() {});
});
super.initState();

View file

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/events/user_authenticated_event.dart';
import 'package:photos/events/subscription_purchased_event.dart';
import 'package:photos/services/billing_service.dart';
import 'package:photos/ui/email_entry_page.dart';
import 'package:photos/ui/password_entry_page.dart';
@ -25,7 +25,7 @@ class _SignInHeaderState extends State<SignInHeader> {
@override
void initState() {
_userAuthEventSubscription =
Bus.instance.on<UserAuthenticatedEvent>().listen((event) {
Bus.instance.on<SubscriptionPurchasedEvent>().listen((event) {
setState(() {});
});
super.initState();
@ -43,22 +43,8 @@ class _SignInHeaderState extends State<SignInHeader> {
var hasSubscription = BillingService.instance.getSubscription() != null;
if (hasConfiguredAccount && hasSubscription) {
return Container();
} else if (!hasConfiguredAccount) {
return _getBody(context);
} else {
return FutureBuilder(
future: BillingService.instance.fetchSubscription(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData || snapshot.hasError) {
if (BillingService.instance.hasActiveSubscription()) {
return Container();
}
return _getBody(context);
} else {
return Container();
}
},
);
return _getBody(context);
}
}

View file

@ -8,7 +8,7 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/events/user_authenticated_event.dart';
import 'package:photos/events/subscription_purchased_event.dart';
import 'package:photos/models/billing_plan.dart';
import 'package:photos/models/subscription.dart';
import 'package:photos/services/billing_service.dart';
@ -41,7 +41,7 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
_currentSubscription = _billingService.getSubscription();
_hasActiveSubscription =
_currentSubscription != null && _currentSubscription.isValid();
if (_hasActiveSubscription) {
if (_currentSubscription != null) {
_usageFuture = _billingService.fetchUsage();
}
@ -62,7 +62,7 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
purchase.verificationData.serverVerificationData,
);
await InAppPurchaseConnection.instance.completePurchase(purchase);
Bus.instance.fire(UserAuthenticatedEvent());
Bus.instance.fire(SubscriptionPurchasedEvent());
final isUpgrade = _hasActiveSubscription &&
newSubscription.storage > _currentSubscription.storage;
final isDowngrade = _hasActiveSubscription &&
@ -170,9 +170,10 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
showGenericErrorDialog(context);
return;
}
if (Platform.isAndroid &&
final isCrossGradingOnAndroid = Platform.isAndroid &&
_hasActiveSubscription &&
_currentSubscription.productID != plan.androidID) {
_currentSubscription.productID != plan.androidID;
if (isCrossGradingOnAndroid) {
final existingProductDetailsResponse =
await InAppPurchaseConnection.instance.queryProductDetails(
[_currentSubscription.productID].toSet());

View file

@ -126,6 +126,6 @@ class _SyncIndicatorState extends State<SyncIndicator> {
}
}
// _event.status == SyncStatus.error)
return "upload failed";
return _event.reason ?? "upload failed";
}
}

View file

@ -108,14 +108,15 @@ class FileUploader {
void _pollQueue() {
if (SyncService.instance.shouldStopSync()) {
final uploadsToBeRemoved = List<int>();
_queue.entries
.where((entry) => entry.value.status == UploadStatus.not_started)
.forEach((pendingUpload) {
_queue
.remove(pendingUpload.key)
.completer
.completeError(SyncStopRequestedError());
uploadsToBeRemoved.add(pendingUpload.key);
});
for (final id in uploadsToBeRemoved) {
_queue.remove(id).completer.completeError(SyncStopRequestedError());
}
}
if (_queue.length > 0 && _currentlyUploading < _maximumConcurrentUploads) {
final firstPendingEntry = _queue.entries
@ -262,8 +263,10 @@ class FileUploader {
return uploadedFile;
}
} catch (e, s) {
_logger.severe(
"File upload failed for " + file.generatedID.toString(), e, s);
if (!(e is NoActiveSubscriptionError)) {
_logger.severe(
"File upload failed for " + file.generatedID.toString(), e, s);
}
throw e;
} finally {
if (io.Platform.isIOS && sourceFile != null) {
@ -379,24 +382,31 @@ class FileUploader {
Future<void> _uploadURLFetchInProgress;
Future<void> _fetchUploadURLs() {
Future<void> _fetchUploadURLs() async {
if (_uploadURLFetchInProgress == null) {
_uploadURLFetchInProgress = _dio
.get(
Configuration.instance.getHttpEndpoint() + "/files/upload-urls",
queryParameters: {
"count": 42, // m4gic number
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
)
.then((response) {
_uploadURLFetchInProgress = null;
final completer = Completer<void>();
_uploadURLFetchInProgress = completer.future;
try {
final response = await _dio.get(
Configuration.instance.getHttpEndpoint() + "/files/upload-urls",
queryParameters: {
"count": 42, // m4gic number
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
);
final urls = (response.data["urls"] as List)
.map((e) => UploadURL.fromMap(e))
.toList();
_uploadURLs.addAll(urls);
});
} on DioError catch (e) {
if (e.response.statusCode == 402) {
throw NoActiveSubscriptionError();
}
throw e;
}
_uploadURLFetchInProgress = null;
completer.complete();
}
return _uploadURLFetchInProgress;
}
@ -444,3 +454,5 @@ class InvalidFileError extends Error {}
class WiFiUnavailableError extends Error {}
class SyncStopRequestedError extends Error {}
class NoActiveSubscriptionError extends Error {}

View file

@ -251,6 +251,13 @@ packages:
relative: true
source: path
version: "0.7.0"
flutter_inapp_purchase:
dependency: "direct main"
description:
name: flutter_inapp_purchase
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
flutter_inappwebview:
dependency: "direct main"
description:

View file

@ -73,7 +73,7 @@ dependencies:
flutter_password_strength: ^0.1.4
flutter_inappwebview: ^4.0.0+4
background_fetch: ^0.5.1
# flutter_inapp_purchase: ^3.0.1
flutter_inapp_purchase: ^3.0.1
dev_dependencies:
flutter_test: