diff --git a/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/LoginPageViewModelTests.cs b/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/LoginPageViewModelTests.cs index fb224091..8e86e1cc 100644 --- a/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/LoginPageViewModelTests.cs +++ b/TransactionProcessor.Mobile.BusinessLogic.Tests/ViewModelTests/LoginPageViewModelTests.cs @@ -33,6 +33,8 @@ public class LoginPageViewModelTests private readonly Mock DialogService; private readonly Mock UpdateService; + + private readonly Mock BalanceRefresher; public LoginPageViewModelTests() { this.Mediator = new Mock(); this.NavigationService = new Mock(); @@ -43,11 +45,14 @@ public LoginPageViewModelTests() { this.ApplicationUpdateLauncherService = new Mock(); this.DialogService = new Mock(); this.UpdateService = new Mock(); + this.BalanceRefresher = new Mock(); + this.ViewModel = new LoginPageViewModel(this.Mediator.Object, this.NavigationService.Object, this.ApplicationCache.Object, this.DeviceService.Object, this.ApplicationInfoService.Object, this.DialogService.Object, this.NavigationParameterService.Object, - this.UpdateService.Object, this.ApplicationUpdateLauncherService.Object); + this.UpdateService.Object, this.ApplicationUpdateLauncherService.Object, + this.BalanceRefresher.Object); Logger.Initialise(new Logging.NullLogger()); } @@ -58,14 +63,12 @@ public void LoginPageViewModel_LoginCommand_Execute_IsExecuted() this.Mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(TestData.AccessToken)); this.Mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(TestData.PerformLogonResponseModel)); this.Mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(TestData.ContractProductList)); - this.Mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(TestData.MerchantBalance)); - + this.ViewModel.LogonCommand.Execute(null); this.Mediator.Verify(x => x.Send(It.IsAny(), It.IsAny()), Times.Once); this.Mediator.Verify(x => x.Send(It.IsAny(), It.IsAny()), Times.Once); this.Mediator.Verify(x => x.Send(It.IsAny(), It.IsAny()), Times.Once); - this.Mediator.Verify(x => x.Send(It.IsAny(), It.IsAny()), Times.Once); this.NavigationService.Verify(n => n.GoToHome(), Times.Once); } @@ -79,7 +82,6 @@ public void LoginPageViewModel_LoginCommand_Execute_ConfigUrlSet_IsExecuted(Stri this.Mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(TestData.AccessToken)); this.Mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(TestData.PerformLogonResponseModel)); this.Mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(TestData.ContractProductList)); - this.Mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(TestData.MerchantBalance)); this.ViewModel.ConfigHostUrl = configUrl; this.ViewModel.LogonCommand.Execute(null); @@ -87,7 +89,6 @@ public void LoginPageViewModel_LoginCommand_Execute_ConfigUrlSet_IsExecuted(Stri this.Mediator.Verify(x => x.Send(It.IsAny(), It.IsAny()), Times.Once); this.Mediator.Verify(x => x.Send(It.IsAny(), It.IsAny()), Times.Once); this.Mediator.Verify(x => x.Send(It.IsAny(), It.IsAny()), Times.Once); - this.Mediator.Verify(x => x.Send(It.IsAny(), It.IsAny()), Times.Once); this.NavigationService.Verify(n => n.GoToHome(), Times.Once); if (String.IsNullOrEmpty(configUrl) == false){ this.ApplicationCache.Verify(v => v.SetConfigHostUrl(It.IsAny(), It.IsAny()), Times.Once); diff --git a/TransactionProcessor.Mobile.BusinessLogic/Services/ApplicationCache.cs b/TransactionProcessor.Mobile.BusinessLogic/Services/ApplicationCache.cs index bed30b1d..1640bdcf 100644 --- a/TransactionProcessor.Mobile.BusinessLogic/Services/ApplicationCache.cs +++ b/TransactionProcessor.Mobile.BusinessLogic/Services/ApplicationCache.cs @@ -46,6 +46,10 @@ public interface IApplicationCache MerchantDetailsModel GetMerchantDetails(); void SetMerchantDetails(MerchantDetailsModel value, MemoryCacheEntryOptions options = default); + + Decimal GetMerchantBalance(); + + void SetMerchantBalance(Decimal value, MemoryCacheEntryOptions options = default); } [ExcludeFromCodeCoverage] @@ -165,6 +169,15 @@ public void SetMerchantDetails(MerchantDetailsModel value, this.Set("MerchantDetails", value, options); } + public Decimal GetMerchantBalance() { + return this.TryGetValue("MerchantBalance"); + } + + public void SetMerchantBalance(Decimal value, + MemoryCacheEntryOptions options = default) { + this.Set("MerchantBalance", value, options); + } + public void SetUseTrainingMode(Boolean value, MemoryCacheEntryOptions options = default) { diff --git a/TransactionProcessor.Mobile.BusinessLogic/Services/BalanceRefresher.cs b/TransactionProcessor.Mobile.BusinessLogic/Services/BalanceRefresher.cs new file mode 100644 index 00000000..d87e59a1 --- /dev/null +++ b/TransactionProcessor.Mobile.BusinessLogic/Services/BalanceRefresher.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Text; +using TransactionProcessor.Mobile.BusinessLogic.Logging; + +namespace TransactionProcessor.Mobile.BusinessLogic.Services +{ + public interface IBalanceRefresher + { + void StartRefreshing(); + void StopRefreshing(); + } + + public class BalanceRefresher : IBalanceRefresher + { + private readonly IApplicationCache ApplicationCache; + private readonly IMerchantService MerchantService; + private CancellationTokenSource? _cts; + + public BalanceRefresher(IApplicationCache applicationCache, IMerchantService merchantService) { + this.ApplicationCache = applicationCache; + this.MerchantService = merchantService; + } + + public void StartRefreshing() + { + _cts = new CancellationTokenSource(); + _ = RefreshLoopAsync(_cts.Token); + } + + public void StopRefreshing() + { + _cts?.Cancel(); + } + + private async Task RefreshLoopAsync(CancellationToken token) + { + using PeriodicTimer timer = new PeriodicTimer(TimeSpan.FromSeconds(30)); + + try + { + // Do the first refresh + await RefreshBalanceAsync(); + + while (await timer.WaitForNextTickAsync(token)) + { + await RefreshBalanceAsync(); + } + } + catch (OperationCanceledException) + { + } + } + + private async Task RefreshBalanceAsync() + { + decimal balance = await GetBalanceFromApi(); + + this.ApplicationCache.SetMerchantBalance(balance); + } + + private async Task GetBalanceFromApi() + { + var result = await this.MerchantService.GetMerchantBalance(CancellationToken.None); + if (result.IsSuccess) + { + return result.Data; + } + + // Handle the failure case as needed, e.g., log the error or return a default value + Logger.LogWarning($"Failed to refresh merchant balance: {result.Message}"); + return 0.0m; + } + } +} diff --git a/TransactionProcessor.Mobile.BusinessLogic/Services/MerchantService.cs b/TransactionProcessor.Mobile.BusinessLogic/Services/MerchantService.cs index 05b6bd69..0d3b51b7 100644 --- a/TransactionProcessor.Mobile.BusinessLogic/Services/MerchantService.cs +++ b/TransactionProcessor.Mobile.BusinessLogic/Services/MerchantService.cs @@ -117,8 +117,7 @@ public async Task> GetMerchantBalance(CancellationToken cancella TokenResponseModel accessToken = this.ApplicationCache.GetAccessToken(); Guid estateId = this.ApplicationCache.GetEstateId(); Guid merchantId = this.ApplicationCache.GetMerchantId(); - - + Logger.LogInformation("About to request merchant balance"); Logger.LogDebug($"Merchant Balance Request details: Estate Id {estateId} Merchant Id {merchantId} Access Token {accessToken.AccessToken}"); diff --git a/TransactionProcessor.Mobile.BusinessLogic/ViewModels/LoginPageViewModel.cs b/TransactionProcessor.Mobile.BusinessLogic/ViewModels/LoginPageViewModel.cs index fe4f1ad3..ffd56af5 100644 --- a/TransactionProcessor.Mobile.BusinessLogic/ViewModels/LoginPageViewModel.cs +++ b/TransactionProcessor.Mobile.BusinessLogic/ViewModels/LoginPageViewModel.cs @@ -20,6 +20,7 @@ public partial class LoginPageViewModel : ExtendedBaseViewModel { private readonly IApplicationInfoService ApplicationInfoService; private readonly IApplicationUpdateLauncherService ApplicationUpdateLauncherService; + private readonly IBalanceRefresher BalanceRefresher; private readonly IUpdateService UpdateService; private String userName; @@ -37,10 +38,12 @@ public LoginPageViewModel(IMediator mediator, INavigationService navigationServi IDialogService dialogService, INavigationParameterService navigationParameterService, IUpdateService updateService, - IApplicationUpdateLauncherService applicationUpdateLauncherService) : base(applicationCache,dialogService,navigationService, deviceService, navigationParameterService) + IApplicationUpdateLauncherService applicationUpdateLauncherService, + IBalanceRefresher balanceRefresher) : base(applicationCache,dialogService,navigationService, deviceService, navigationParameterService) { this.ApplicationInfoService = applicationInfoService; this.ApplicationUpdateLauncherService = applicationUpdateLauncherService; + this.BalanceRefresher = balanceRefresher; this.Mediator = mediator; this.UpdateService = updateService; } @@ -276,13 +279,15 @@ private async Task Logon(){ Result> getMerchantContractProductsResult = await this.GetMerchantContractProducts(); this.HandleResult(getMerchantContractProductsResult); - await this.WriteTimingTrace(sw, "After GetMerchantContractProducts"); - Result getMerchantBalanceResult = await this.GetMerchantBalance(); - this.HandleResult(getMerchantBalanceResult); + //await this.WriteTimingTrace(sw, "After GetMerchantContractProducts"); + //Result getMerchantBalanceResult = await this.GetMerchantBalance(); + //this.HandleResult(getMerchantBalanceResult); - await this.WriteTimingTrace(sw, "After GetMerchantBalance"); + //await this.WriteTimingTrace(sw, "After GetMerchantBalance"); this.ApplicationCache.SetIsLoggedIn(true); - + + this.BalanceRefresher.StartRefreshing(); + await this.WriteTimingTrace(sw, "After SetIsLoggedIn"); await this.NavigationService.GoToHome(); } diff --git a/TransactionProcessor.Mobile/Extensions/MauiAppBuilderExtensions.cs b/TransactionProcessor.Mobile/Extensions/MauiAppBuilderExtensions.cs index e3e6f94f..bed0614e 100644 --- a/TransactionProcessor.Mobile/Extensions/MauiAppBuilderExtensions.cs +++ b/TransactionProcessor.Mobile/Extensions/MauiAppBuilderExtensions.cs @@ -89,7 +89,7 @@ public static MauiAppBuilder ConfigureAppServices(this MauiAppBuilder builder) { builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - + builder.Services.AddSingleton(); builder.RegisterConfigurationService().RegisterAuthenticationService().RegisterTransactionService().RegisterMerchantService(); builder.Services.RegisterHttpClientX();