private async Task PerformOrderSynchronization(IIndexDocumentChangeFeed ordersFeed, Action <AvaTaxOrdersSynchronizationProgress> progressCallback,
                                                IJobCancellationToken cancellationToken)
 {
     var cancellationTokenWrapper = new JobCancellationTokenWrapper(cancellationToken);
     await _ordersSynchronizationService.SynchronizeOrdersAsync(ordersFeed, progressCallback, cancellationTokenWrapper);
 }
        public async Task SynchronizeOrdersAsync(IIndexDocumentChangeFeed ordersFeed, Action <AvaTaxOrdersSynchronizationProgress> progressCallback, ICancellationToken cancellationToken)
        {
            // TODO: how to find order count when ordersFeed.TotalsCount is null?
            var totalCount = (long)ordersFeed.TotalCount;

            var progressInfo = new AvaTaxOrdersSynchronizationProgress
            {
                Message        = "Reading orders...",
                TotalCount     = totalCount,
                ProcessedCount = 0
            };

            progressCallback(progressInfo);

            cancellationToken?.ThrowIfCancellationRequested();

            for (long i = 0; i < totalCount; i += BatchSize)
            {
                var searchResult = await ordersFeed.GetNextBatch();

                var orderIds = searchResult.Select(x => x.DocumentId).ToArray();
                var orders   = await _orderService.GetByIdsAsync(orderIds);

                foreach (var order in orders)
                {
                    var avaTaxSettings = await GetAvataxSettingsForOrder(order);

                    if (avaTaxSettings != null)
                    {
                        _orderTaxTypeResolver.ResolveTaxTypeForOrderAsync(order);

                        var avaTaxClient = _avaTaxClientFactory(avaTaxSettings);
                        try
                        {
                            await SendOrderToAvaTax(order, avaTaxSettings.CompanyCode, avaTaxSettings.SourceAddress, avaTaxClient);
                        }
                        catch (AvaTaxError e)
                        {
                            var errorDetails   = e.error.error;
                            var joinedMessages = string.Join(Environment.NewLine,
                                                             errorDetails.details.Select(x => $"{x.severity}: {x.message} {x.description}"));

                            var errorMessage = $"Order #{order.Number}: {errorDetails.message}{Environment.NewLine}{joinedMessages}";
                            progressInfo.Errors.Add(errorMessage);
                        }
                    }
                    else
                    {
                        var errorMessage = $"Order #{order.Number} was not sent to Avalara, because the order store does not use AvaTax as tax provider.";
                        progressInfo.Errors.Add(errorMessage);
                    }

                    cancellationToken?.ThrowIfCancellationRequested();
                }

                var processedCount = Math.Min(i + orderIds.Length, totalCount);
                progressInfo.ProcessedCount = processedCount;
                progressInfo.Message        = $"Processed {processedCount} of {totalCount} orders";
                progressCallback(progressInfo);
            }

            progressInfo.Message = "Orders synchronization completed.";
            progressCallback(progressInfo);
        }