Ejemplo n.º 1
0
        internal static List <LimitationType> MapOperationType(CurrencyOperationType currencyOperationType)
        {
            var result = new List <LimitationType>();

            switch (currencyOperationType)
            {
            case CurrencyOperationType.CardCashIn:
                result.Add(LimitationType.CardAndSwiftCashIn);
                result.Add(LimitationType.CardCashIn);
                break;

            case CurrencyOperationType.SwiftTransfer:
                result.Add(LimitationType.CardAndSwiftCashIn);
                break;

            case CurrencyOperationType.CryptoCashOut:
                result.Add(LimitationType.CryptoCashOut);
                break;

            case CurrencyOperationType.CardCashOut:
            case CurrencyOperationType.CryptoCashIn:
                break;

            case CurrencyOperationType.SwiftTransferOut:
                break;

            default:
                throw new NotSupportedException($"Currency operation type {currencyOperationType} is not supported!");
            }

            return(result);
        }
Ejemplo n.º 2
0
        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);
        }
Ejemplo n.º 3
0
        public async Task RemoveOperationAsync(
            string clientId,
            string asset,
            double amount,
            CurrencyOperationType operationType)
        {
            var keys = await GetClientAttemptKeysAsync(clientId);

            if (keys.Length == 0)
            {
                return;
            }

            var attemptJsons = await _db.StringGetAsync(keys);

            var attemptToDelete = attemptJsons
                                  .Where(a => a.HasValue)
                                  .Select(a => a.ToString().DeserializeJson <CurrencyOperationAttempt>())
                                  .Where(i => i.OperationType == operationType && i.Asset == asset && Math.Abs(amount - i.Amount) < _minDiff)
                                  .OrderBy(i => i.DateTime)
                                  .FirstOrDefault();

            if (attemptToDelete == null)
            {
                return;
            }

            string clientKey     = string.Format(_clientSetKeyPattern, _instanceName, clientId);
            string timeStr       = attemptToDelete.DateTime.ToString(_dataFormat);
            string attemptSuffix = string.Format(_attemptKeySuffixPattern, operationType, timeStr);
            var    attemptKey    = $"{clientKey}:{attemptSuffix}";
            var    tx            = _db.CreateTransaction();
            var    tasks         = new List <Task>
            {
                tx.SortedSetRemoveAsync(clientKey, attemptSuffix),
                tx.KeyDeleteAsync(attemptKey),
            };

            if (await tx.ExecuteAsync())
            {
                await Task.WhenAll(tasks);
            }
        }
Ejemplo n.º 4
0
        public async Task AddDataAsync(
            string clientId,
            string asset,
            double amount,
            int ttlInMinutes,
            CurrencyOperationType operationType)
        {
            amount = Math.Abs(amount);
            var now     = DateTime.UtcNow;
            var attempt = new CurrencyOperationAttempt
            {
                ClientId      = clientId,
                Asset         = asset,
                Amount        = amount,
                OperationType = operationType,
                DateTime      = now,
            };

            string clientKey     = string.Format(_clientSetKeyPattern, _instanceName, clientId);
            string attemptSuffix = string.Format(_attemptKeySuffixPattern, operationType, now.ToString(_dataFormat));
            var    attemptKey    = $"{clientKey}:{attemptSuffix}";

            var tx    = _db.CreateTransaction();
            var tasks = new List <Task>
            {
                tx.SortedSetAddAsync(clientKey, attemptSuffix, DateTime.UtcNow.Ticks)
            };
            var setKeyTask = tx.StringSetAsync(attemptKey, attempt.ToJson(), TimeSpan.FromMinutes(ttlInMinutes));

            tasks.Add(setKeyTask);
            if (!await tx.ExecuteAsync())
            {
                throw new InvalidOperationException($"Error during attempt adding for client {clientId}");
            }
            await Task.WhenAll(tasks);

            if (!setKeyTask.Result)
            {
                throw new InvalidOperationException($"Error during attempt adding for client {clientId}");
            }
        }
Ejemplo n.º 5
0
 public Task CacheClientDataAsync(string clientId, CurrencyOperationType operationType)
 {
     return(_data.CacheClientDataIfRequiredAsync(clientId, operationType));
 }
        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);
        }
Ejemplo n.º 7
0
        public async Task <LimitationCheckResult> CheckCashOperationLimitAsync(
            string clientId,
            string assetId,
            double amount,
            CurrencyOperationType currencyOperationType)
        {
            var originalAsset  = assetId;
            var originalAmount = amount;

            amount = Math.Abs(amount);

            if (currencyOperationType == CurrencyOperationType.CryptoCashOut)
            {
                var asset = _assets.Get(assetId);

                if (amount < asset.CashoutMinimalAmount)
                {
                    var minimalAmount = asset.CashoutMinimalAmount.GetFixedAsString(asset.Accuracy).TrimEnd('0');

                    return(new LimitationCheckResult {
                        IsValid = false, FailMessage = $"The minimum amount to cash out is {minimalAmount}"
                    });
                }

                if (asset.LowVolumeAmount.HasValue && amount < asset.LowVolumeAmount)
                {
                    var settings = await _limitSettingsRepository.GetAsync();

                    var timeout     = TimeSpan.FromMinutes(settings.LowCashOutTimeoutMins);
                    var callHistory = await _callTimeLimitsRepository.GetCallHistoryAsync("CashOutOperation", clientId, timeout);

                    var cashoutEnabled = !callHistory.Any() || callHistory.IsCallEnabled(timeout, settings.LowCashOutLimit);

                    if (!cashoutEnabled)
                    {
                        return new LimitationCheckResult {
                                   IsValid = false, FailMessage = "You have exceeded cash out operations limit. Please try again later."
                        }
                    }
                    ;
                }
            }

            if (currencyOperationType == CurrencyOperationType.SwiftTransferOut)
            {
                var error = await CheckSwiftWithdrawLimitations(assetId, (decimal)amount);

                if (error != null)
                {
                    return(new LimitationCheckResult {
                        IsValid = false, FailMessage = error
                    });
                }
            }

            if (currencyOperationType != CurrencyOperationType.CryptoCashIn &&
                currencyOperationType != CurrencyOperationType.CryptoCashOut)
            {
                (assetId, amount) = await _currencyConverter.ConvertAsync(
                    assetId,
                    _currencyConverter.DefaultAsset,
                    amount);
            }

            var limitationTypes = LimitMapHelper.MapOperationType(currencyOperationType);
            var typeLimits      = _limits.Where(l => limitationTypes.Contains(l.LimitationType)).ToList();

            if (!typeLimits.Any())
            {
                try
                {
                    await _limitOperationsApi.AddOperationAttemptAsync(
                        clientId,
                        originalAsset,
                        originalAmount,
                        currencyOperationType == CurrencyOperationType.CardCashIn
                        ?CashOperationsTimeoutInMinutes
                        : _attemptRetainInMinutes,
                        currencyOperationType);
                }
                catch (Exception ex)
                {
                    _log.Error(ex, context: new { Type = "Attempt", clientId, originalAmount, originalAsset });
                }
                return(new LimitationCheckResult {
                    IsValid = true
                });
            }

            //To handle parallel request
            await _lock.WaitAsync();

            try
            {
                var    assetLimits = typeLimits.Where(l => l.Asset == assetId).ToList();
                string error       = await DoPeriodCheckAsync(
                    assetLimits,
                    LimitationPeriod.Month,
                    clientId,
                    assetId,
                    amount,
                    currencyOperationType,
                    false);

                if (error != null)
                {
                    return new LimitationCheckResult {
                               IsValid = false, FailMessage = error
                    }
                }
                ;

                error = await DoPeriodCheckAsync(
                    assetLimits,
                    LimitationPeriod.Day,
                    clientId,
                    assetId,
                    amount,
                    currencyOperationType,
                    false);

                if (error != null)
                {
                    return new LimitationCheckResult {
                               IsValid = false, FailMessage = error
                    }
                }
                ;

                if (currencyOperationType == CurrencyOperationType.CryptoCashOut)
                {
                    assetLimits = typeLimits.Where(l => l.Asset == _currencyConverter.DefaultAsset).ToList();

                    var(assetTo, convertedAmount) = await _currencyConverter.ConvertAsync(
                        assetId,
                        _currencyConverter.DefaultAsset,
                        amount,
                        true);

                    error = await DoPeriodCheckAsync(
                        assetLimits,
                        LimitationPeriod.Month,
                        clientId,
                        assetTo,
                        convertedAmount,
                        currencyOperationType,
                        true);

                    if (error != null)
                    {
                        return new LimitationCheckResult {
                                   IsValid = false, FailMessage = error
                        }
                    }
                    ;

                    error = await DoPeriodCheckAsync(
                        assetLimits,
                        LimitationPeriod.Day,
                        clientId,
                        assetTo,
                        convertedAmount,
                        currencyOperationType,
                        true);

                    if (error != null)
                    {
                        return new LimitationCheckResult {
                                   IsValid = false, FailMessage = error
                        }
                    }
                    ;
                }

                try
                {
                    await _limitOperationsApi.AddOperationAttemptAsync(
                        clientId,
                        originalAsset,
                        originalAmount,
                        currencyOperationType == CurrencyOperationType.CardCashIn
                        ?CashOperationsTimeoutInMinutes
                        : _attemptRetainInMinutes,
                        currencyOperationType);
                }
                catch (Exception ex)
                {
                    _log.Error(ex, context: new { Type = "Attempt", clientId, originalAmount, originalAsset });
                }
            }
            finally
            {
                _lock.Release();
            }

            return(new LimitationCheckResult {
                IsValid = true
            });
        }
Ejemplo n.º 8
0
        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);
        }