public async Task UpdateVoucherOperationsStatistic(UpdateVoucherOperationsStatistic partnerStatistic)
        {
            var lockValue = $"{partnerStatistic.PartnerId}_{partnerStatistic.OperationType}_{partnerStatistic.Currency}";

            for (var i = 0; i < MaxAttemptsCount; ++i)
            {
                var locked = await _redisLocksService.TryAcquireLockAsync(
                    lockValue,
                    lockValue,
                    _lockTimeOut);

                if (!locked)
                {
                    await Task.Delay(_lockTimeOut);

                    continue;
                }

                await _voucherOperationsStatisticRepository.UpdateByCurrencyAndOperationType(partnerStatistic);

                await _partnerVouchersDailyStatsRepository.UpdateByDateAndCurrencyAndOperationType(partnerStatistic);

                await _redisLocksService.ReleaseLockAsync(lockValue, lockValue);

                return;
            }

            throw new InvalidOperationException("Couldn't acquire a lock in Redis");
        }
        public async Task <VoucherReservationResult> ReserveVoucherAsync(Guid voucherCampaignId, Guid ownerId)
        {
            var campaign = await _campaignsRepository.GetByIdAsync(voucherCampaignId, false);

            if (campaign == null)
            {
                return new VoucherReservationResult {
                           ErrorCode = ProcessingVoucherError.VoucherCampaignNotFound
                }
            }
            ;

            if (campaign.State != CampaignState.Published ||
                DateTime.UtcNow < campaign.FromDate ||
                campaign.ToDate.HasValue && campaign.ToDate.Value < DateTime.UtcNow)
            {
                return new VoucherReservationResult {
                           ErrorCode = ProcessingVoucherError.VoucherCampaignNotActive
                }
            }
            ;

            if (campaign.VouchersTotalCount <= campaign.BoughtVouchersCount)
            {
                return new VoucherReservationResult {
                           ErrorCode = ProcessingVoucherError.NoAvailableVouchers
                }
            }
            ;

            var voucherPriceIsZero   = campaign.VoucherPrice == 0;
            var voucherCampaignIdStr = voucherCampaignId.ToString();

            for (int i = 0; i < MaxAttemptsCount; ++i)
            {
                var locked = await _redisLocksService.TryAcquireLockAsync(
                    voucherCampaignIdStr,
                    ownerId.ToString(),
                    _lockTimeOut);

                if (!locked)
                {
                    await Task.Delay(_lockTimeOut);

                    continue;
                }

                var vouchers = await _vouchersRepository.GetByCampaignIdAndStatusAsync(voucherCampaignId, VoucherStatus.InStock);

                Voucher voucher = null;
                if (vouchers.Any())
                {
                    try
                    {
                        voucher = vouchers.First();
                        if (voucherPriceIsZero)
                        {
                            voucher.Status       = VoucherStatus.Sold;
                            voucher.OwnerId      = ownerId;
                            voucher.PurchaseDate = DateTime.UtcNow;
                            await _vouchersRepository.UpdateAsync(voucher);
                        }
                        else
                        {
                            await _vouchersRepository.ReserveAsync(voucher, ownerId);
                        }
                    }
                    catch (Exception e)
                    {
                        _log.Error(e);
                        await _redisLocksService.ReleaseLockAsync(voucherCampaignIdStr, ownerId.ToString());

                        return(new VoucherReservationResult {
                            ErrorCode = ProcessingVoucherError.NoAvailableVouchers
                        });
                    }
                }
                else
                {
                    var vouchersPage = await _vouchersRepository.GetByCampaignIdAsync(voucherCampaignId, 0, 1);

                    if (vouchersPage.TotalCount >= campaign.VouchersTotalCount)
                    {
                        await _redisLocksService.ReleaseLockAsync(voucherCampaignIdStr, ownerId.ToString());

                        return(new VoucherReservationResult {
                            ErrorCode = ProcessingVoucherError.NoAvailableVouchers
                        });
                    }

                    var(validationCode, hash) = GenerateValidation();
                    voucher = new Voucher
                    {
                        CampaignId         = voucherCampaignId,
                        Status             = voucherPriceIsZero ? VoucherStatus.Sold : VoucherStatus.Reserved,
                        ValidationCodeHash = hash,
                        OwnerId            = ownerId,
                        PurchaseDate       = DateTime.UtcNow,
                    };

                    voucher.Id = await _vouchersRepository.CreateAsync(voucher);

                    voucher.ShortCode = GenerateShortCodeFromId(voucher.Id);

                    await _vouchersRepository.UpdateAsync(voucher, validationCode);
                }

                await _redisLocksService.ReleaseLockAsync(voucherCampaignIdStr, ownerId.ToString());

                if (voucherPriceIsZero)
                {
                    await PublishVoucherSoldEvent(null, campaign, voucher);

                    return(new VoucherReservationResult
                    {
                        ErrorCode = ProcessingVoucherError.None
                    });
                }

                var paymentRequestResult = await _paymentManagementClient.Api.GeneratePaymentAsync(
                    new PaymentGenerationRequest
                {
                    CustomerId = ownerId,
                    Amount     = campaign.VoucherPrice,
                    Currency   = campaign.Currency,
                    PartnerId  = campaign.PartnerId,
                    ExternalPaymentEntityId = voucher.ShortCode,
                });

                if (paymentRequestResult.ErrorCode != PaymentGenerationErrorCode.None)
                {
                    await CancelReservationAsync(voucher.ShortCode);

                    return(new VoucherReservationResult
                    {
                        ErrorCode = ProcessingVoucherError.InvalidPartnerPaymentConfiguration,
                    });
                }

                await _paymentRequestsRepository.CreatePaymentRequestAsync(paymentRequestResult.PaymentRequestId, voucher.ShortCode);

                return(new VoucherReservationResult
                {
                    ErrorCode = ProcessingVoucherError.None,
                    PaymentUrl = paymentRequestResult.PaymentPageUrl,
                });
            }

            _log.Warning($"Couldn't get a lock for voucher campaign {voucherCampaignId}");

            return(new VoucherReservationResult {
                ErrorCode = ProcessingVoucherError.NoAvailableVouchers
            });
        }