private async Task <CustomerBalanceResultModel> InternalGetAsync(string customerId)
        {
            var value = await GetCachedValue(customerId);

            if (value != null)
            {
                return(value.DeserializeJson <CustomerBalanceResultModel>());
            }

            var walletAddressResult = await _walletsService.GetCustomerWalletAsync(customerId);

            switch (walletAddressResult.Error)
            {
            case CustomerWalletAddressError.CustomerWalletMissing:
                return(CustomerBalanceResultModel.Failed(CustomerBalanceError.CustomerWalletMissing));

            case CustomerWalletAddressError.InvalidCustomerId:
                return(CustomerBalanceResultModel.Failed(CustomerBalanceError.InvalidCustomerId));

            case CustomerWalletAddressError.None:
                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(walletAddressResult.Error));
            }

            var balanceResponse =
                await _quorumOperationExecutorClient.AddressesApi.GetBalanceForAddressAsync(walletAddressResult.WalletAddress);

            var transfersInProgress = await _operationsFetcher.GetTransfersInProgressAsync(walletAddressResult.WalletAddress);

            var transfersInProgressAmount = transfersInProgress
                                            .Select(x => JsonConvert.DeserializeObject <TokensTransferContext>(x.ContextJson))
                                            .Sum(x => x.Amount);

            var seizedAmount = (await _operationsFetcher.GetSeizeOperationsInProgressAsync(customerId))
                               .Select(x => JsonConvert.DeserializeObject <SeizeToInternalContext>(x.ContextJson))
                               .Sum(x => x.Amount);

            var reservedAmount = transfersInProgressAmount + seizedAmount;

            if (balanceResponse.Balance < reservedAmount)
            {
                _log.Warning(
                    $"The reserved amount ({reservedAmount}) is more than actual balance ({balanceResponse.Balance})",
                    context: new { customerId });
            }

            var availableBalance = balanceResponse.Balance - balanceResponse.StakedBalance >= reservedAmount
                ? balanceResponse.Balance - balanceResponse.StakedBalance - reservedAmount
                : 0;

            var result = CustomerBalanceResultModel.Succeeded(availableBalance, balanceResponse.StakedBalance);

            await SetCacheValueAsync(customerId, result);

            return(result);
        }
        public async Task <CustomerBalanceResultModel> GetAsync(string customerId)
        {
            if (string.IsNullOrEmpty(customerId))
            {
                return(CustomerBalanceResultModel.Failed(CustomerBalanceError.InvalidCustomerId));
            }

            return(await InternalGetAsync(customerId));
        }
        public async Task HandleAsync_ErrorWhenGettingTheBalance_WalletOwnersRepoNotCalled()
        {
            _balanceServiceMock.Setup(x => x.GetAsync(FakeCustomerId))
            .ReturnsAsync(CustomerBalanceResultModel.Failed(CustomerBalanceError.CustomerWalletMissing));

            var sut = CreateSutInstance();

            await sut.HandleAsync(FakeCustomerId);

            _walletOwnersRepoMock.Verify(x => x.GetByOwnerIdAsync(FakeCustomerId), Times.Never);
        }
        public async Task TransferAsync_BalanceServiceReturnsError_ReturnsFail(CustomerBalanceError balanceError, TransferError expectedError)
        {
            _walletsServiceMock
            .Setup(x => x.GetCustomerWalletAsync(It.IsAny <string>()))
            .ReturnsAsync(CustomerWalletAddressResultModel.Succeeded(FakeWalletAddress));

            _balanceServiceMock
            .Setup(x => x.GetAsync(It.IsAny <string>()))
            .ReturnsAsync(CustomerBalanceResultModel.Failed(balanceError));

            var sut = CreateSutInstance();

            var result = await sut.P2PTransferAsync(FakeSenderCustomerId, FakeRecipientCustomerId, 1, FakeTransferId);

            Assert.Equal(expectedError, result.Error);
        }
        public async Task <CustomerBalanceResultModel> ForceBalanceUpdateAsync(string customerId, OperationType operationCausedUpdate, Guid operationId)
        {
            if (string.IsNullOrEmpty(customerId))
            {
                return(CustomerBalanceResultModel.Failed(CustomerBalanceError.InvalidCustomerId));
            }

            if (!AllowedOperationTypesToUpdateBalanceMap.ContainsKey(operationCausedUpdate))
            {
                throw new InvalidOperationException(
                          $"Attempt to update balance with wrong operation type, operationId={operationId}, operationType={operationCausedUpdate.ToString()}");
            }

            await _distributedCache.RemoveAsync(BuildCacheKey(customerId));

            var balanceResult = await InternalGetAsync(customerId);

            if (balanceResult.Error != CustomerBalanceError.None)
            {
                _log.Error(message: "Couldn't update balance",
                           context: new { customerId, error = balanceResult.Error.ToString() });
            }
            else
            {
                await _customerBalanceUpdatedPublisher.PublishAsync(new CustomerBalanceUpdatedEvent
                {
                    CustomerId  = customerId,
                    Balance     = balanceResult.Total,
                    Timestamp   = DateTime.UtcNow,
                    Reason      = AllowedOperationTypesToUpdateBalanceMap[operationCausedUpdate],
                    OperationId = operationId
                });
            }

            return(balanceResult);
        }