private async Task <string> CheckLimitAsync( double cashPeriodValue, double transferPeriodValue, CashOperationLimitation limit, LimitationPeriod period, string clientId, string asset, double amount) { double currentValue = cashPeriodValue; if (limit.LimitationType == LimitationType.CardAndSwiftCashIn) { currentValue += transferPeriodValue; } double limitValue = (await _currencyConverter.ConvertAsync(limit.Asset, _currencyConverter.DefaultAsset, limit.Limit)).Item2; if (limitValue < currentValue + amount) { return(GetPeriodLimitFailMessage(period)); } double antiFraudValue = await _antiFraudCollector.GetAttemptsValueAsync( clientId, asset, limit.LimitationType); if (limitValue < currentValue + amount + antiFraudValue) { var forbidDuration = limit.LimitationType == LimitationType.CardCashIn ? CashOperationsTimeoutInMinutes : _attemptRetainInMinutes; return($"Please wait {forbidDuration} minute(s) after previous payment attempt"); } return(null); }
public async Task <(double, bool)> GetCurrentAmountAsync( string clientId, string asset, LimitationPeriod period, CurrencyOperationType operationType) { (var items, bool notCached) = await _data.GetClientDataAsync(clientId, operationType); double result = 0; DateTime now = DateTime.UtcNow; foreach (var item in items) { if (item.Asset != asset) { continue; } if (period == LimitationPeriod.Day && now.Subtract(item.DateTime).TotalHours >= 24) { continue; } result += item.Volume; } return(result, notCached); }
private string GetPeriodLimitFailMessage(LimitationPeriod period) { switch (period) { case LimitationPeriod.Day: return("Operation is not allowed because of Daily limitation."); case LimitationPeriod.Month: return("Operation is not allowed because of Monthly limitation."); default: throw new NotSupportedException($"Limitation period {period} is not supported"); } }
private async Task AddRemainingLimitsAsync(string clientId, LimitationPeriod period, ClientData clientData) { var result = new List <RemainingLimitation>(); var periodLimits = _limits.Where(l => l.Period == period); foreach (var periodLimit in periodLimits) { if (!string.IsNullOrWhiteSpace(periodLimit.ClientId) && clientId != periodLimit.ClientId) { continue; } if (periodLimit.LimitationType == LimitationType.CardCashIn) { result.Add( await CalculateRemainingAsync( clientData.CashOperations, clientData.OperationAttempts, new List <CurrencyOperationType> { CurrencyOperationType.CardCashIn }, periodLimit)); } else if (periodLimit.LimitationType == LimitationType.CryptoCashOut) { result.Add( await CalculateRemainingAsync( clientData.CashOperations, clientData.OperationAttempts, new List <CurrencyOperationType> { CurrencyOperationType.CryptoCashOut }, periodLimit)); } else if (periodLimit.LimitationType == LimitationType.CardAndSwiftCashIn) { result.Add( await CalculateRemainingAsync( clientData.CashOperations.Concat(clientData.CashTransferOperations), clientData.OperationAttempts, new List <CurrencyOperationType> { CurrencyOperationType.CardCashIn, CurrencyOperationType.SwiftTransfer }, periodLimit)); } } clientData.RemainingLimits = result; }
public async Task <ClientData> GetClientDataAsync(string clientId, LimitationPeriod period) { var result = new ClientData { CashOperations = await _cashOperationsCollector.GetClientDataAsync(clientId, period), CashTransferOperations = (await _cashTransfersCollector.GetClientDataAsync(clientId, period)) .Select(i => new CashOperation { Id = i.Id, ClientId = i.ClientId, Asset = i.Asset, Volume = i.Volume, DateTime = i.DateTime, OperationType = i.OperationType, }) .ToList(), OperationAttempts = await _antiFraudCollector.GetClientDataAsync(clientId), }; await AddRemainingLimitsAsync(clientId, period, result); return(result); }
public async Task <(double, bool)> GetCurrentAmountAsync( string clientId, string asset, LimitationPeriod period, CurrencyOperationType operationType, bool checkAllCrypto = false) { int sign; switch (operationType) { case CurrencyOperationType.CardCashIn: case CurrencyOperationType.CryptoCashIn: case CurrencyOperationType.SwiftTransfer: sign = 1; break; case CurrencyOperationType.CardCashOut: case CurrencyOperationType.CryptoCashOut: case CurrencyOperationType.SwiftTransferOut: sign = -1; break; default: throw new NotSupportedException($"Operation type {operationType} can't be mapped to CashFlowDirection!"); } (var items, bool notCached) = await _data.GetClientDataAsync(clientId, operationType); DateTime now = DateTime.UtcNow; double result = 0; foreach (var item in items) { if (period == LimitationPeriod.Day && now.Subtract(item.DateTime).TotalHours >= 24) { continue; } if (Math.Sign(item.Volume) != Math.Sign(sign)) { continue; } double amount; if (checkAllCrypto) { if (!_currencyConverter.IsNotConvertible(item.Asset)) { continue; } var(_, convertedAmount) = await _currencyConverter.ConvertAsync( item.Asset, _currencyConverter.DefaultAsset, item.Volume, true); amount = convertedAmount; } else { if (item.Asset != asset) { continue; } amount = item.Volume; } result += amount; } return(Math.Abs(result), notCached); }
public async Task <List <CashOperation> > GetClientDataAsync(string clientId, LimitationPeriod period) { var(result, _) = await _data.GetClientDataAsync(clientId); if (period == LimitationPeriod.Day) { var now = DateTime.UtcNow; result.RemoveAll(i => now.Subtract(i.DateTime).TotalHours >= 24); } foreach (var item in result) { if (item.OperationType.HasValue) { continue; } item.OperationType = GetOperationType(item); } return(result); }
private async Task <string> DoPeriodCheckAsync( IEnumerable <CashOperationLimitation> limits, LimitationPeriod period, string clientId, string asset, double amount, CurrencyOperationType currencyOperationType, bool checkAllCrypto) { var periodLimits = limits.Where(l => l.Period == period).ToList(); if (!periodLimits.Any()) { return(null); } (double cashPeriodValue, bool cashOperationsNotCached) = await _cashOperationsCollector.GetCurrentAmountAsync( clientId, asset, period, currencyOperationType, checkAllCrypto); (double transferPeriodValue, bool cashTransfersNotCached) = await _cashTransfersCollector.GetCurrentAmountAsync( clientId, asset, period, currencyOperationType); if (cashOperationsNotCached || cashTransfersNotCached) { try { await _limitOperationsApi.CacheClientDataAsync(clientId, currencyOperationType); } catch (Exception ex) { _log.Error(ex, context: new { Type = "CachOp", clientId, currencyOperationType }); } } var clientLimit = periodLimits.FirstOrDefault(l => l.ClientId == clientId); if (clientLimit != null) { string checkMessage = await CheckLimitAsync( cashPeriodValue, transferPeriodValue, clientLimit, period, clientId, asset, amount); if (!string.IsNullOrWhiteSpace(checkMessage)) { return(checkMessage); } } else { foreach (var periodLimit in periodLimits) { if (periodLimit.ClientId != null) { continue; } string checkMessage = await CheckLimitAsync( cashPeriodValue, transferPeriodValue, periodLimit, period, clientId, asset, amount); if (!string.IsNullOrWhiteSpace(checkMessage)) { return(checkMessage); } } } return(null); }