private async Task PurchaseSubscription(string subCode) { if (_userCache.GetLoggedInAccount() == null) { throw new InvalidOperationException("User must be logged in to purchase a subscription."); } var sub = await _purchaseService.PurchaseItem(subCode, ItemType.Subscription, "payload"); if (sub == null) { throw new InvalidOperationException($"Something went wrong when attempting to purchase. Please try again."); } var model = new Models.SubscriptionModel() { PurchaseId = sub.Id, PurchaseToken = sub.PurchaseToken, SubscriptionType = SubscriptionUtility.GetTypeFromProductId(sub.ProductId), StartDateTime = DateTimeOffset.UtcNow, PurchasedDateTime = DateTimeOffset.UtcNow, EndDateTime = DateTimeOffset.UtcNow.AddMonths(1), PurchaseSource = Device.RuntimePlatform == Device.Android ? Models.PurchaseSource.GooglePlay : Models.PurchaseSource.AppStore, UserId = _userCache.GetLoggedInAccount().UserId, Email = _userCache.GetLoggedInAccount().Email }; try { _subCache.Put(model.PurchaseId, model); } catch { } _subService.AddSubscription(model); }
private string GetLegalJargon(ValidationModel validation) { string platformText = _runtimePlatform == Device.Android ? "Play Store" : "iTunes"; var costDesc = $"${SubscriptionUtility.IndvReportNoSubPrice.ToString()}"; if (validation.Subscription != null) { var info = SubscriptionUtility.GetInfoFromSubType(validation.Subscription.SubscriptionType); switch (validation.Subscription.SubscriptionType) { case SubscriptionType.Premium: costDesc = $"${SubscriptionUtility.IndvReportPremiumPrice.ToString()}"; break; case SubscriptionType.Enterprise: costDesc = $"${SubscriptionUtility.IndvReportEnterprisePrice.ToString()}"; break; default: costDesc = $"${SubscriptionUtility.IndvReportBasicPrice.ToString()}"; break; } } return($@"A {costDesc} purchase will be applied to your {platformText} account upon confirmation. " + $@"For more information, "); }
private async Task SetVisualStateForValidation() { try { var user = _userService.GetLoggedInAccount(); if (user == null) { MainLayoutVisible = true; CannotSubmitLayoutVisible = false; return; } var validation = await _orderValidator.ValidateOrderRequest(user); var activeSub = SubscriptionUtility.SubscriptionActive(validation.Subscription); if (validation.RemainingOrders > 0) { MainLayoutVisible = true; CannotSubmitLayoutVisible = false; return; } switch (validation.State) { case ValidationState.NoReportsLeftInPeriod: MainLayoutVisible = false; CannotSubmitHeaderText = "You've been busy!"; CannotSubmitLabelText = $"Sorry, you have used all of your reports for this month."; CannotSubmitLayoutVisible = true; PurchaseOptionsText = $"Additional reports can be purchased at a reduced price of " + $"${SubscriptionUtility.GetSingleReportInfo(validation).Price} per report."; PurchaseOptionsVisible = true; break; case ValidationState.NoSubscriptionAndTrialValid: MainLayoutVisible = false; CannotSubmitHeaderText = "Thanks for trying Fair Squares!"; CannotSubmitLabelText = $"Please claim your free one month subscription trial, or click below to view other options."; CannotSubmitLayoutVisible = true; PurchaseOptionsVisible = false; break; case ValidationState.NoSubscriptionAndTrialAlreadyUsed: MainLayoutVisible = false; CannotSubmitHeaderText = "Thanks for trying Fair Squares!"; CannotSubmitLabelText = $"Click below to view options for getting more reports."; CannotSubmitLayoutVisible = true; PurchaseOptionsVisible = false; break; default: MainLayoutVisible = true; CannotSubmitLayoutVisible = false; break; } } catch (Exception ex) { _logger.LogError("Failed to set visual state on load.", ex); } }
public async Task WhenPurchasingSubscription_PurchasesCorrect(SubscriptionType type) { var svc = GetService(); var name = SubscriptionUtility.GetInfoFromSubType(type).SubscriptionCode; var purchase = await svc.PurchaseItem(name, ItemType.Subscription, ""); _billing.Verify(x => x.ConnectAsync(ItemType.Subscription), Times.Once); _billing.Verify(x => x.DisconnectAsync(), Times.Once); _billing.Verify(x => x.PurchaseAsync(name, ItemType.Subscription, "", null), Times.Once); _logger.Verify(x => x.LogError(It.IsAny <string>(), It.IsAny <Exception>()), Times.Never); }
private void SetViewState(ValidationModel validation) { PurchaseButtonText = $"Purchase Report"; LegalText = GetLegalJargon(validation); ReportsDescVisible = true; CostDescVisible = true; PurchaseButtonEnabled = true; MarketingDescVisible = true; MarketingDescText = $"Purchase one additional report."; ReportsDescText = $"Adds one available report to your account."; CostDescText = $"${SubscriptionUtility.GetSingleReportInfo(validation).Price}"; }
public OrderViewModel(IOrderValidationService validator, ICurrentUserService userCache, IOrderService orderService, IToastService toast, IPageFactory pageFactory, MainThreadNavigator nav, IMessagingSubscriber topicSubscriber, ILogger <OrderViewModel> logger, string deviceType, AlertUtility alertUtility, Action <BaseNavPageType> baseNavigationAction, ICache <Models.Order> orderCache) { _orderValidator = validator; _userService = userCache; _toast = toast; _nav = nav; _orderService = orderService; _pageFactory = pageFactory; _orderCache = orderCache; _alertUtility = alertUtility; _topicSubscriber = topicSubscriber; _baseNavigationAction = baseNavigationAction; _deviceType = deviceType; _logger = logger; PurchaseOptionsCommand = new Command(async() => { var val = await _orderValidator.ValidateOrderRequest(_userService.GetLoggedInAccount()); if (SubscriptionUtility.SubscriptionActive(val.Subscription)) { _nav.Push(_pageFactory.GetPage(PageType.SingleReportPurchase, val)); } else { _nav.Push(_pageFactory.GetPage(PageType.PurchaseOptions, val)); } }); OptionsInfoCommand = new Command(async() => await alertUtility.Display("Roof Option Selection", $"Selecting a roof option allows Fair Squares to determine what roofs you would like measured at the submitted address.{Environment.NewLine}" + $"{Environment.NewLine}Primary Only- Fair Squares will measure the primary structure, including attached garage.{Environment.NewLine}" + $"Detached Garage- Fair Squares will also measure the detached garage on the property.{Environment.NewLine}" + $"Shed/Barn- Fair Squares will also measure a shed or barn on the property.{Environment.NewLine}" + $"{Environment.NewLine}NOTE: Fair Squares only supports measuring one primary structure per report (with a detached garage or shed/barn if needed).", "Ok")); ErrorMessageRowHeight = 0; SelectedOptionIndex = -1; SelectedStateIndex = -1; #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed SetVisualStateForValidation(); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed }
public async Task WhenActiveSubscriptionAndAllOrdersUsed_ShouldNotValidate(SubscriptionType type) { _subCache.Put("1", new SubscriptionModel() { EndDateTime = DateTimeOffset.Now.AddDays(2), SubscriptionType = type, StartDateTime = DateTimeOffset.Now.AddDays(-20) }); _orderCache.Put(Enumerable.Range(0, SubscriptionUtility.GetInfoFromSubType(type).OrderCount + 1) .ToDictionary(x => x.ToString(), x => new Order() { DateReceived = DateTimeOffset.Now.AddDays(-2) })); var valSvc = new OrderValidationService(_refresher.Object, _orderCache, _subCache, _prCache, _logger.Object); var result = await valSvc.ValidateOrderRequest(new MobileClient.Authentication.AccountModel() { UserId = "234" }); Assert.AreEqual(ValidationState.NoReportsLeftInPeriod, result.State); }
public async Task WhenActiveSubscriptionAndRemainingOrders_ShouldHaveOrdersLeft(SubscriptionType type, int orderCount) { _subCache.Put("1", new SubscriptionModel() { EndDateTime = DateTimeOffset.Now.AddDays(2), SubscriptionType = type, StartDateTime = DateTimeOffset.Now.AddDays(-20) }); _orderCache.Put(Enumerable.Range(0, orderCount + 1) .ToDictionary(x => x.ToString(), x => new Order() { DateReceived = DateTimeOffset.Now.AddDays(-2) })); var valSvc = new OrderValidationService(_refresher.Object, _orderCache, _subCache, _prCache, _logger.Object); var result = await valSvc.ValidateOrderRequest(new AccountModel() { UserId = "12345" }); Assert.AreEqual(ValidationState.SubscriptionReportValid, result.State); Assert.AreEqual(SubscriptionUtility.GetInfoFromSubType(type).OrderCount - orderCount, result.RemainingOrders); }
private async void PurchaseButtonClicked() { LoadAnimationRunning = true; LoadAnimationVisible = true; PurchaseButtonEnabled = false; try { SubscriptionType selected = SubscriptionType.Basic; var subCode = SubscriptionUtility.GetInfoFromSubType(selected).SubscriptionCode; try { await PurchaseSubscription(subCode); await _alertUtility.Display("Purchase Complete", "Thank you for your purchase!", "Ok"); _nav.PopToRoot(); } catch (Exception ex) { _toastService.LongToast($"Failed to purchase subscription. {ex.Message}"); } } catch (Exception ex) { _toastService.LongToast($"Something went wrong when trying to purchase subscription. {ex.Message}"); } finally { try { PurchaseButtonEnabled = true; LoadAnimationRunning = false; LoadAnimationVisible = false; } catch { } } }
private async Task SetSubState(AccountModel user) { try { if (user == null) { SubscriptionLabel = $"Please log in to view purchasing options."; _changeSubStyleClass("btn-success"); SubscriptionButtonText = "View Options"; SubscriptionButtonEnabled = false; return; } if (_cacheRefresher.Invalidated) { await(_cacheRefresher.RefreshTask ?? Task.CompletedTask); } var validity = await _orderValidator.ValidateOrderRequest(user); var activeSub = SubscriptionUtility.SubscriptionActive(validity.Subscription); _changeSubStyleClass(activeSub ? "btn-primary" : "btn-success"); SubscriptionButtonText = activeSub ? "Manage" : "View Options"; SubscriptionButtonEnabled = true; if (validity.RemainingOrders > 0) { SubscriptionLabel = $"Reports remaining: {validity.RemainingOrders.ToString()}"; } else { SubscriptionLabel = $"No reports remaining. " + $"{(activeSub ? "Additional reports can be purchased at a reduced price. Click below to find out more." : "Click below to view purchase options.")}"; } } catch (Exception ex) { _logger.LogError("Failed to set subscription state.", ex); } }
private async Task PurchaseButtonClicked() { LoadAnimationRunning = true; LoadAnimationVisible = true; PurchaseButtonEnabled = false; try { var code = SubscriptionUtility.GetSingleReportInfo(_validation).Code; try { await PurchaseItem(code); await _alertUtility.Display("Purchase Complete", "Thank you for your purchase!", "Ok"); _nav.PopToRoot(); } catch (Exception ex) { _toastService.LongToast($"Failed to purchase report. {ex.Message}"); } } catch (Exception ex) { _toastService.LongToast($"Something went wrong when trying to purchase report. {ex.Message}"); } finally { try { PurchaseButtonEnabled = true; LoadAnimationRunning = false; LoadAnimationVisible = false; } catch { } } }
public async Task <ValidationModel> ValidateOrderRequest(AccountModel user, bool useCached = true) { try { List <SubscriptionModel> subs = null; List <PurchasedReportModel> purchased = null; IEnumerable <Models.Order> orders = null; SubscriptionModel lastSub = null; if (!useCached) { await _cacheRefresher.RefreshCaches(user); } subs = _subCache.GetAll().Select(x => x.Value).OrderBy(x => x.StartDateTime).ToList(); lastSub = subs.LastOrDefault(); purchased = _prCache.GetAll().Select(x => x.Value).ToList(); orders = _orderCache.GetAll().Select(x => x.Value); var totalRemainingOrders = subs .Select(x => SubscriptionUtility.GetInfoFromSubType(x.SubscriptionType).OrderCount).Sum() + 1 + purchased.Count() - orders.Count(); var model = new ValidationModel() { PurchasedReports = purchased, RemainingOrders = totalRemainingOrders }; if (!subs.Any() && !orders.Any()) { model.State = ValidationState.FreeReportValid; model.Message = "User can use their free report."; return(model); } // Handle case of no sub but purchased reports if (!subs.Any() && purchased.Any()) { if (orders.Count() < (1 + purchased.Count())) { model.State = ValidationState.NoSubscriptionAndReportValid; model.Message = "User can use their purchased report."; return(model); } } if (!subs.Any() && orders.Any()) { // This case means they've never had a subscription before, and are eligible for a trial month. model.State = ValidationState.NoSubscriptionAndTrialValid; model.Message = "User has used their free report, but is eligible for a free trial period."; return(model); } if (subs.Any() && !SubscriptionUtility.SubscriptionActive(lastSub) && orders.Any()) { model.State = ValidationState.NoSubscriptionAndTrialAlreadyUsed; model.Message = "User has used their free trial and free report."; return(model); } if (totalRemainingOrders <= 0) { model.State = ValidationState.NoReportsLeftInPeriod; model.RemainingOrders = 0; model.Subscription = lastSub; model.Message = "User has used all of their orders for this subscription period."; return(model); } else { model.State = ValidationState.SubscriptionReportValid; model.Subscription = lastSub; model.RemainingOrders = totalRemainingOrders; return(model); } } catch (Exception ex) { _logger.LogError($"Failed to validate user's order.", ex); throw ex; } }
private async Task SetInitialState(AccountModel user) { ToolbarInfoCommand = new Command(() => _navigation.Push(_pageFactory.GetPage(PageType.Instruction, false))); LogOutCommand = new Command(async() => { if (_userCache.GetLoggedInAccount() == null) { _navigation.Push(_pageFactory.GetPage(PageType.Landing)); } else { _userCache.LogOut(); SetAccountState(null); await SetSubState(null); } }); SubscriptionCommand = new Command(async() => { if (_userCache.GetLoggedInAccount() == null) { return; } var valid = await _orderValidator.ValidateOrderRequest(_userCache.GetLoggedInAccount()); try { SubscriptionButtonEnabled = false; if (SubscriptionUtility.SubscriptionActive(valid.Subscription)) { _navigation.Push(_pageFactory.GetPage(PageType.ManageSubscription, valid)); } else { _navigation.Push(_pageFactory.GetPage(PageType.PurchaseOptions, valid)); } } catch (Exception ex) { _logger.LogError("Failed to handle subscription click command.", ex); } finally { SubscriptionButtonEnabled = true; } }); FeedbackCommand = new Command(() => { try { FeedbackButtonEnabled = false; _navigation.Push(_pageFactory.GetPage(PageType.Feedback, _navigation)); } catch (Exception ex) { _logger.LogError("Failed to handle feedback click command.", ex); } finally { FeedbackButtonEnabled = true; } }); SetAccountState(user); await SetSubState(user); }
public ManageSubscriptionViewModel(ValidationModel model, string runtimePlatform, Action <Uri> openUri, MainThreadNavigator nav, ILogger <ManageSubscriptionViewModel> logger, IPageFactory pageFactory) { _model = model; _runtimePlatform = runtimePlatform; _openUri = openUri; _nav = nav; _logger = logger; _pageFactory = pageFactory; try { SubscriptionTypeLabel = " " + _model.Subscription.SubscriptionType.ToString(); RemainingOrdersLabel = " " + _model.RemainingOrders.ToString(); PurchasedOrdersLabel = " " + _model.PurchasedReports?.Count.ToString(); EndDateLabel = " " + _model.Subscription.EndDateTime.ToString("dddd, dd MMMM yyyy"); GetMoreReportsLabel = $"Purchase additional reports at a reduced price of ${SubscriptionUtility.GetSingleReportInfo(_model).Price} per report."; GetMoreReportsCommand = new Command(() => _nav.Push(_pageFactory.GetPage(PageType.SingleReportPurchase, _model))); var compName = _runtimePlatform == Device.Android ? "Google" : "Apple"; var supportUri = _runtimePlatform == Device.Android ? "https://support.google.com/googleplay/answer/7018481" : "https://support.apple.com/en-us/HT202039#subscriptions"; DisclaimerLabel = $"NOTE: {compName} does not allow subscriptions to be cancelled through the app. This button will open a web browser with instructions on how to cancel from your device."; CancelSubCommand = new Command(() => _openUri(new Uri(supportUri))); } catch (Exception ex) { _logger.LogError($"Failed to load manage subscription page.", ex); } }
public async Task WhenNotAllOrdersUsedFromOneSub_RollsOverToNextMonth(int totalOrders, ValidationState expected) { var subs = new List <SubscriptionModel>() { new SubscriptionModel() { PurchaseId = "1", EndDateTime = DateTimeOffset.Now.AddDays(2), SubscriptionType = SubscriptionType.Basic, StartDateTime = DateTimeOffset.Now.AddDays(-20) }, new SubscriptionModel() { PurchaseId = "2", EndDateTime = DateTimeOffset.Now.AddDays(-20), SubscriptionType = SubscriptionType.Premium, StartDateTime = DateTimeOffset.Now.AddDays(-40) }, new SubscriptionModel() { PurchaseId = "3", EndDateTime = DateTimeOffset.Now.AddDays(-40), SubscriptionType = SubscriptionType.Enterprise, StartDateTime = DateTimeOffset.Now.AddDays(-60) }, new SubscriptionModel() { PurchaseId = "4", EndDateTime = DateTimeOffset.Now.AddDays(-60), SubscriptionType = SubscriptionType.Basic, StartDateTime = DateTimeOffset.Now.AddDays(-80) } }; var prs = new List <PurchasedReportModel>() { new PurchasedReportModel() { PurchaseId = "1" }, new PurchasedReportModel() { PurchaseId = "2" } }; var purchasedOrderCount = subs.Select(x => SubscriptionUtility.GetInfoFromSubType(x.SubscriptionType).OrderCount).Sum() + prs.Count(); _subCache.Put(subs.ToDictionary(x => x.PurchaseId, x => x)); _prCache.Put(prs.ToDictionary(x => x.PurchaseId, x => x)); _orderCache.Put(Enumerable.Range(0, totalOrders + 1) .ToDictionary(x => x.ToString(), x => new Order() { Fulfilled = true, DateReceived = DateTimeOffset.Now.AddDays(-2) })); var valSvc = new OrderValidationService(_refresher.Object, _orderCache, _subCache, _prCache, _logger.Object); var result = await valSvc.ValidateOrderRequest(new MobileClient.Authentication.AccountModel() { UserId = "234" }); var expectedCount = purchasedOrderCount - totalOrders > 0 ? purchasedOrderCount - totalOrders : 0; Assert.AreEqual(expectedCount, result.RemainingOrders); Assert.AreEqual(expected, result.State); }
public async Task <InAppBillingPurchase> PurchaseItem(string name, ItemType iapType, string payload) { if (!SubscriptionUtility.ValidatePurchaseType(name, iapType)) { throw new InvalidOperationException($"The item that is attempting to be purchased does not have the proper IAP type."); } try { if (_isPurchasing) { throw new InvalidOperationException("A different purchase is already in progress. Please try again."); } _isPurchasing = true; bool connected = false; try { connected = await _billing.ConnectAsync(iapType); } catch (Exception ex) { var message = $"Error occurred while try to connect to billing service."; _logger.LogError(message, ex, name, payload); throw new InvalidOperationException(message); } if (!connected) { // we are offline or can't connect, don't try to purchase _logger.LogError($"Can't connect to billing service.", null, name, payload); throw new InvalidOperationException($"Can't connect to billing service. Check connection to internet."); } InAppBillingPurchase purchase = null; try { //check purchases purchase = await _billing.PurchaseAsync(name, iapType, payload); } catch (InAppBillingPurchaseException purchaseEx) { var errorMsg = ""; switch (purchaseEx.PurchaseError) { case PurchaseError.AlreadyOwned: errorMsg = $"Current user already owns this item."; break; case PurchaseError.AppStoreUnavailable: errorMsg = "The App Store is unavailable. Please check your internet connection and try again."; break; case PurchaseError.BillingUnavailable: errorMsg = "The billing service is unavailable. Please check your internet connection and try again."; break; case PurchaseError.DeveloperError: errorMsg = "A configuration error occurred during billing. Please contact Fair Squares support."; break; case PurchaseError.InvalidProduct: errorMsg = "This product was not found. Please contact Fair Squares support."; break; case PurchaseError.PaymentInvalid: errorMsg = "A payment configuration error occurred during billing. Please contact Fair Squares support."; break; case PurchaseError.PaymentNotAllowed: errorMsg = "Current user is not allowed to authorize payments."; break; case PurchaseError.ProductRequestFailed: errorMsg = "The product request failed. Please try again."; break; case PurchaseError.ServiceUnavailable: errorMsg = "The network connection is not available. Please check your internet connection."; break; case PurchaseError.UserCancelled: errorMsg = "The transaction was cancelled by the user."; break; default: errorMsg = "An error occurred while purchasing subscription. Please contact Fair Squares support."; break; } try { if (purchaseEx.PurchaseError != PurchaseError.UserCancelled) { _logger.LogError($"Error type: '{purchaseEx.PurchaseError.ToString()}'. Error Message displayed to user: '******'.", purchaseEx, name, payload); } } catch { } throw new InvalidOperationException(errorMsg, purchaseEx); } catch (Exception ex) { // Something else has gone wrong, log it _logger.LogError("Failed to purchase subscription.", ex, name); throw new InvalidOperationException(ex.Message, ex); } finally { await _billing.DisconnectAsync(); } if (purchase == null) { // did not purchase _logger.LogError($"Failed to purchase {name}.", null, name); throw new InvalidOperationException("Failed to complete purchase. Please contact support."); } return(purchase); } catch { throw; } finally { _isPurchasing = false; } }
static App() { try { Container = new Container(); var analyticsSvc = DependencyService.Get <IAnalyticsService>(); var userService = new CurrentUserService(); var orderService = new AzureOrderService(new HttpClient(), _orderEndpoint, _apiKey); var propertyService = new PropertyService(new HttpClient(), _propertyEndpoint, new AnalyticsLogger <PropertyService>(analyticsSvc, userService)); var imageService = new BlobImageService(new HttpClient(), _blobEndpoint, new AnalyticsLogger <BlobImageService>(analyticsSvc, userService)); var subService = new SubscriptionService(new HttpClient(), _subEndpoint, new AnalyticsLogger <SubscriptionService>(analyticsSvc, userService)); var prService = new PurchasedReportService(new HttpClient(), _purchasedReportsEndpoint, new AnalyticsLogger <PurchasedReportService>(analyticsSvc, userService)); var authenticator = new OAuth2Authenticator(Configuration.ClientId, null, Configuration.Scope, new Uri(GoogleAuthorizeUrl), new Uri(Configuration.RedirectNoPath + ":" + Configuration.RedirectPath), new Uri(GoogleAccessTokenUrl), null, true); var notifyService = new NotificationService(new HttpClient(), _notifyEndpoint, _apiKey); var purchaseEmailLogger = new EmailLogger <PurchasingService>(notifyService, userService); var purchaseService = new PurchasingService(CrossInAppBilling.Current, purchaseEmailLogger); authenticator.Completed += (s, e) => { if (e.IsAuthenticated) { userService.LogIn(e.Account); } }; // Setup caches and begin process of filling them. var dbBasePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); var propertyCache = new LocalSqlCache <PropertyModel>(Path.Combine(dbBasePath, "property.db3"), new AnalyticsLogger <LocalSqlCache <PropertyModel> >(analyticsSvc, userService)); var orderCache = new LocalSqlCache <Models.Order>(Path.Combine(dbBasePath, "order.db3"), new AnalyticsLogger <LocalSqlCache <Models.Order> >(analyticsSvc, userService)); var imageCache = new LocalSqlCache <ImageModel>(Path.Combine(dbBasePath, "images.db3"), new AnalyticsLogger <LocalSqlCache <ImageModel> >(analyticsSvc, userService)); var subCache = new LocalSqlCache <SubscriptionModel>(Path.Combine(dbBasePath, "subs.db3"), new AnalyticsLogger <LocalSqlCache <SubscriptionModel> >(analyticsSvc, userService)); var settingsCache = new LocalSqlCache <SettingsModel>(Path.Combine(dbBasePath, "sets.db3"), new AnalyticsLogger <LocalSqlCache <SettingsModel> >(analyticsSvc, userService)); var prCache = new LocalSqlCache <PurchasedReportModel>(Path.Combine(dbBasePath, "purchasedreports.db3"), new AnalyticsLogger <LocalSqlCache <PurchasedReportModel> >(analyticsSvc, userService)); var startUpLogger = new AnalyticsLogger <App>(analyticsSvc, userService); Action ClearCaches = () => { try { orderCache.Clear(); propertyCache.Clear(); imageCache.Clear(); subCache.Clear(); prCache.Clear(); } catch { } }; Func <AccountModel, Task> RefreshCaches = user => { var userId = user.UserId; var prTask = Task.Run(() => { try { var prs = prService.GetPurchasedReports(userId); prCache.Put(prs.ToDictionary(x => x.PurchaseId, x => x)); } catch (Exception ex) { startUpLogger.LogError("Failed to get purchased reports on refresh.", ex); } }); var orderTask = Task.Run(async() => { try { var orders = await orderService.GetMemberOrders(userId); var unCached = orders.Except(orderCache.GetAll().Select(x => x.Value).ToList(), new OrderEqualityComparer()).ToList(); orderCache.Put(orders.ToDictionary(x => x.OrderId, x => x)); var subTask = Task.Run(() => { try { DependencyService.Get <IMessagingSubscriber>().Subscribe(orders.Select(x => $"{(Device.RuntimePlatform == Device.Android ? App.TopicPrefix : "")}{x.OrderId}").ToList()); } catch { } }); var propTask = Task.Run(async() => { if (!orders.Any()) { propertyCache.Clear(); return; } var properties = await propertyService.GetProperties(unCached.Select(x => x.OrderId).ToList()); propertyCache.Update(properties); }); var imgTask = Task.Run(() => { if (!orders.Any()) { imageCache.Clear(); return; } var images = imageService.GetImages(unCached.Select(x => x.OrderId).ToList()); imageCache.Update(images); }); var subscriptionTask = Task.Run(async() => { try { // TODO: Refactor this so it can be tested. var allSubs = subService.GetSubscriptions(userId).OrderBy(x => x.StartDateTime).ToList(); var recentSub = allSubs.LastOrDefault(); var purchases = new List <InAppBillingPurchase>(); SubscriptionModel newSub = null; // Check app store purchases to see if they auto-renewed if (recentSub != null && !SubscriptionUtility.SubscriptionActive(recentSub)) { try { purchases = (await purchaseService.GetPurchases(ItemType.Subscription)).ToList(); } catch (Exception ex) { purchaseEmailLogger.LogError($"Error occurred while getting purchases.", ex); } var mostRecent = purchases.OrderBy(x => x.TransactionDateUtc)?.LastOrDefault(); if (mostRecent != null) { newSub = SubscriptionUtility.GetModelFromIAP(mostRecent, user, recentSub); if (newSub != null) { allSubs.Add(newSub); subService.AddSubscription(newSub); } } } if (!allSubs.Any()) { subCache.Clear(); } else { subCache.Put(allSubs.ToDictionary(x => x.PurchaseId, x => x)); } } catch (Exception ex) { startUpLogger.LogError("Failed to get subscriptions on refresh.", ex); } }); await Task.WhenAll(new[] { propTask, imgTask, subTask, subscriptionTask }); } catch (Exception ex) { startUpLogger.LogError($"Failed to fill caches.\n{ex.ToString()}", ex); } }); return(Task.WhenAll(new[] { prTask, orderTask })); }; var refresher = new CacheRefresher(new AnalyticsLogger <CacheRefresher>(analyticsSvc, userService), RefreshCaches); refresher.Invalidate(); #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed refresher.RefreshCaches(userService.GetLoggedInAccount()); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed userService.OnLoggedIn += async(s, e) => { ClearCaches(); await refresher.RefreshCaches(e.Account); }; userService.OnLoggedOut += (s, e) => ClearCaches(); // Register services Container.Register <IOrderService>(() => orderService, Lifestyle.Singleton); Container.Register <IPropertyService>(() => propertyService, Lifestyle.Singleton); Container.Register <IImageService>(() => imageService, Lifestyle.Singleton); Container.Register <INotificationService>(() => notifyService, Lifestyle.Singleton); Container.Register <ILogger <SingleReportPurchaseViewModel> >(() => new EmailLogger <SingleReportPurchaseViewModel>(notifyService, userService), Lifestyle.Singleton); Container.RegisterConditional(typeof(ILogger <>), typeof(AnalyticsLogger <>), c => !c.Handled); Container.Register <OAuth2Authenticator>(() => authenticator, Lifestyle.Singleton); Container.Register <AccountStore>(() => AccountStore.Create(), Lifestyle.Singleton); Container.Register <ICurrentUserService>(() => userService, Lifestyle.Singleton); Container.Register <IPurchasingService>(() => purchaseService, Lifestyle.Singleton); Container.Register <ICacheRefresher>(() => refresher, Lifestyle.Singleton); Container.Register <ISubscriptionService>(() => subService, Lifestyle.Singleton); Container.Register <IOrderValidationService, OrderValidationService>(); Container.Register <IPageFactory, PageFactory>(Lifestyle.Singleton); Container.Register <IToastService>(() => DependencyService.Get <IToastService>(), Lifestyle.Singleton); Container.Register <IMessagingSubscriber>(() => DependencyService.Get <IMessagingSubscriber>(), Lifestyle.Singleton); Container.Register <IMessagingCenter>(() => MessagingCenter.Instance, Lifestyle.Singleton); Container.Register <IPurchasedReportService>(() => prService, Lifestyle.Singleton); Container.Register <IAnalyticsService>(() => analyticsSvc, Lifestyle.Singleton); Container.Register <LaunchedFromPushModel>(() => App.PushModel ?? new LaunchedFromPushModel(), Lifestyle.Singleton); // Finish registering created caches Container.Register <ICache <PropertyModel> >(() => propertyCache, Lifestyle.Singleton); Container.Register <ICache <Models.Order> >(() => orderCache, Lifestyle.Singleton); Container.Register <ICache <ImageModel> >(() => imageCache, Lifestyle.Singleton); Container.Register <ICache <SubscriptionModel> >(() => subCache, Lifestyle.Singleton); Container.Register <ICache <SettingsModel> >(() => settingsCache, Lifestyle.Singleton); Container.Register <ICache <PurchasedReportModel> >(() => prCache, Lifestyle.Singleton); Container.RegisterConditional(typeof(ICache <>), typeof(MemoryCache <>), c => !c.Handled); } catch (Exception ex) { Debug.WriteLine(ex.ToString()); throw; } }