Exemple #1
0
        public async Task <HSSupplier> UpdateSupplier(string supplierID, PartialSupplier supplier, DecodedToken decodedToken)
        {
            var me = await _oc.Me.GetAsync(accessToken : decodedToken.AccessToken);

            Require.That(decodedToken.CommerceRole == CommerceRole.Seller || supplierID == me.Supplier.ID, new ErrorCode("Unauthorized", $"You are not authorized to update supplier {supplierID}", 401));
            var currentSupplier = await _oc.Suppliers.GetAsync <HSSupplier>(supplierID);

            var updatedSupplier = await _oc.Suppliers.PatchAsync <HSSupplier>(supplierID, supplier);

            // Update supplier products only on a name change
            if (currentSupplier.Name != supplier.Name || currentSupplier.xp.Currency.ToString() != supplier.xp.Currency.Value)
            {
                var productsToUpdate = await _oc.Products.ListAllAsync <HSProduct>(
                    supplierID : supplierID,
                    accessToken : decodedToken.AccessToken
                    );

                ApiClient supplierClient = await _apiClientHelper.GetSupplierApiClient(supplierID, decodedToken.AccessToken);

                if (supplierClient == null)
                {
                    throw new Exception($"Default supplier client not found. SupplierID: {supplierID}");
                }
                var configToUse = new OrderCloudClientConfig
                {
                    ApiUrl       = decodedToken.ApiUrl,
                    AuthUrl      = decodedToken.AuthUrl,
                    ClientId     = supplierClient.ID,
                    ClientSecret = supplierClient.ClientSecret,
                    GrantType    = GrantType.ClientCredentials,
                    Roles        = new[]
                    {
                        ApiRole.SupplierAdmin,
                        ApiRole.ProductAdmin
                    },
                };
                var ocClient = new OrderCloudClient(configToUse);
                await ocClient.AuthenticateAsync();

                var token = ocClient.TokenResponse.AccessToken;
                foreach (var product in productsToUpdate)
                {
                    product.xp.Facets["supplier"] = new List <string>()
                    {
                        supplier.Name
                    };
                    product.xp.Currency = supplier.xp.Currency;
                }
                await Throttler.RunAsync(productsToUpdate, 100, 5, product => ocClient.Products.SaveAsync(product.ID, product, accessToken: token));
            }

            return(updatedSupplier);
        }
Exemple #2
0
        public async Task can_provide_alternative_token()
        {
            // prove that auth credentials are no longer required like in earlier versions
            var cli = new OrderCloudClient(new OrderCloudClientConfig {
                ApiUrl  = "https://fake.com",
                AuthUrl = "https://fake.com"
            });

            using (var httpTest = new HttpTest()) {
                var products = await cli.Me.ListProductsAsync(accessToken : "some-other-token");

                httpTest.ShouldHaveMadeACall().WithHeader("Authorization", "Bearer some-other-token");
            }
        }
        public async Task <Product> FilterOptionOverride(string id, string supplierID, IDictionary <string, object> facets, VerifiedUserContext user)
        {
            ApiClient supplierClient = await _apiClientHelper.GetSupplierApiClient(supplierID, user.AccessToken);

            if (supplierClient == null)
            {
                throw new Exception($"Default supplier client not found. SupplierID: {supplierID}");
            }
            var configToUse = new OrderCloudClientConfig
            {
                ApiUrl       = user.TokenApiUrl,
                AuthUrl      = user.TokenAuthUrl,
                ClientId     = supplierClient.ID,
                ClientSecret = supplierClient.ClientSecret,
                GrantType    = GrantType.ClientCredentials,
                Roles        = new[]
                {
                    ApiRole.SupplierAdmin,
                    ApiRole.ProductAdmin
                },
            };
            var ocClient = new OrderCloudClient(configToUse);
            await ocClient.AuthenticateAsync();

            var token = ocClient.TokenResponse.AccessToken;

            //Format the facet data to change for request body
            var facetDataFormatted           = new ExpandoObject();
            var facetDataFormattedCollection = (ICollection <KeyValuePair <string, object> >)facetDataFormatted;

            foreach (var kvp in facets)
            {
                facetDataFormattedCollection.Add(kvp);
            }
            dynamic facetDataFormattedDynamic = facetDataFormatted;

            //Update the product with a supplier token
            var updatedProduct = await ocClient.Products.PatchAsync(
                id,
                new PartialProduct()
            {
                xp = new { Facets = facetDataFormattedDynamic }
            },
                accessToken : token
                );

            return(updatedProduct);
        }
        private async Task <VerifiedUserContext> ParseJwt(string token, ApiRole[] roles, IOrderCloudClient oc = null)
        {
            var jwt      = new JwtSecurityToken(token);
            var clientId = jwt.Claims.FirstOrDefault(x => x.Type == "cid")?.Value;
            var usrtype  = jwt.Claims.FirstOrDefault(x => x.Type == "usrtype")?.Value;
            var scope    = jwt.Claims.Where(x => x.Type == "role").Select(x => x.Value)?.ToList();
            var usr      = jwt.Claims.FirstOrDefault(x => x.Type == "usr")?.Value;

            // validate scope
            if (!scope.Contains("FullAccess"))
            {
                Require.That(scope.Count(s => roles.Any(role => s == role.ToString())) > 0, new ErrorCode("Authorization.InvalidToken", 401, "Authorization.InvalidToken: Access token is invalid or expired."));
            }

            var cid = new ClaimsIdentity("OrderCloudIntegrations");

            cid.AddClaim(new Claim("clientid", clientId));
            cid.AddClaim(new Claim("accesstoken", token));

            if (oc == null)
            {
                oc = new OrderCloudClient(new OrderCloudClientConfig()
                {
                    AuthUrl  = _settings.OrderCloudSettings.ApiUrl,
                    ApiUrl   = _settings.OrderCloudSettings.ApiUrl,
                    ClientId = clientId
                });
            }
            var user = await oc.Me.GetAsync(token);

            if (!user.Active || user.Username != usr)
            {
                throw new Exception("Invalid User");
            }
            cid.AddClaim(new Claim("userrecordjson", JsonConvert.SerializeObject(user)));
            cid.AddClaim(new Claim("username", user.Username));
            cid.AddClaim(new Claim("access", user.Username));
            cid.AddClaim(new Claim("userid", user.ID));
            cid.AddClaim(new Claim("email", user.Email ?? ""));
            cid.AddClaim(new Claim("buyer", user.Buyer?.ID ?? ""));
            cid.AddClaim(new Claim("supplier", user.Supplier?.ID ?? ""));
            cid.AddClaim(new Claim("seller", user?.Seller?.ID ?? ""));

            cid.AddClaims(user.AvailableRoles.Select(r => new Claim(ClaimTypes.Role, r)));

            //Validate the token
            return(new VerifiedUserContext(new ClaimsPrincipal(cid)));
        }
Exemple #5
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var connectionString = Environment.GetEnvironmentVariable("APP_CONFIG_CONNECTION");
            var _settings        = builder.BuildSettingsFromAzureAppConfig <AppSettings>(connectionString);

            var orderCloudClient = new OrderCloudClient(new OrderCloudClientConfig
            {
                ApiUrl       = _settings.OrderCloudSettings.ApiUrl,
                AuthUrl      = _settings.OrderCloudSettings.ApiUrl,
                ClientId     = _settings.OrderCloudSettings.MiddlewareClientID,
                ClientSecret = _settings.OrderCloudSettings.MiddlewareClientSecret,
                Roles        = new[] { ApiRole.FullAccess }
            });

            builder.Services.AddSingleton(_settings);
            builder.Services.AddSingleton <IOrderCloudClient>(orderCloudClient);
        }
Exemple #6
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var azureConnectionString = Environment.GetEnvironmentVariable("APP_CONFIG_CONNECTION");
            var settings = BuildSettings(azureConnectionString);

            var orderCloudClient = new OrderCloudClient(new OrderCloudClientConfig
            {
                ApiUrl       = settings.OrderCloudSettings.ApiUrl,
                AuthUrl      = settings.OrderCloudSettings.ApiUrl,
                ClientId     = settings.OrderCloudSettings.MiddlewareClientID,
                ClientSecret = settings.OrderCloudSettings.MiddlewareClientSecret,
                Roles        = new[] { ApiRole.FullAccess }
            });

            builder.Services.AddSingleton(settings);
            builder.Services.AddSingleton <IOrderCloudClient>(orderCloudClient);
            builder.Services.AddSingleton <IProductCommand, ProductCommand>();
            builder.Services.AddSingleton <ForwardOrderJob>();
        }
Exemple #7
0
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            var cosmosConfig = new CosmosConfig(
                _settings.CosmosSettings.DatabaseName,
                _settings.CosmosSettings.EndpointUri,
                _settings.CosmosSettings.PrimaryKey,
                _settings.CosmosSettings.RequestTimeoutInSeconds
                );
            var cosmosContainers = new List <ContainerInfo>()
            {
                new ContainerInfo()
                {
                    Name         = "salesorderdetail",
                    PartitionKey = "/PartitionKey"
                },
                new ContainerInfo()
                {
                    Name         = "purchaseorderdetail",
                    PartitionKey = "/PartitionKey"
                },
                new ContainerInfo()
                {
                    Name         = "lineitemdetail",
                    PartitionKey = "/PartitionKey"
                },
                new ContainerInfo()
                {
                    Name         = "rmas",
                    PartitionKey = "/PartitionKey"
                },
                new ContainerInfo()
                {
                    Name         = "shipmentdetail",
                    PartitionKey = "/PartitionKey"
                },
                new ContainerInfo()
                {
                    Name         = "productdetail",
                    PartitionKey = "/PartitionKey"
                }
            };

            var avalaraConfig = new AvalaraConfig()
            {
                BaseApiUrl  = _settings.AvalaraSettings.BaseApiUrl,
                AccountID   = _settings.AvalaraSettings.AccountID,
                LicenseKey  = _settings.AvalaraSettings.LicenseKey,
                CompanyCode = _settings.AvalaraSettings.CompanyCode,
                CompanyID   = _settings.AvalaraSettings.CompanyID
            };

            var currencyConfig = new BlobServiceConfig()
            {
                ConnectionString = _settings.StorageAccountSettings.ConnectionString,
                Container        = _settings.StorageAccountSettings.BlobContainerNameExchangeRates
            };
            var assetConfig = new BlobServiceConfig()
            {
                ConnectionString = _settings.StorageAccountSettings.ConnectionString,
                Container        = "assets",
                AccessType       = BlobContainerPublicAccessType.Container
            };

            var flurlClientFactory    = new PerBaseUrlFlurlClientFactory();
            var smartyStreetsUsClient = new ClientBuilder(_settings.SmartyStreetSettings.AuthID, _settings.SmartyStreetSettings.AuthToken).BuildUsStreetApiClient();
            var orderCloudClient      = new OrderCloudClient(new OrderCloudClientConfig
            {
                ApiUrl       = _settings.OrderCloudSettings.ApiUrl,
                AuthUrl      = _settings.OrderCloudSettings.ApiUrl,
                ClientId     = _settings.OrderCloudSettings.MiddlewareClientID,
                ClientSecret = _settings.OrderCloudSettings.MiddlewareClientSecret,
                Roles        = new[] { ApiRole.FullAccess }
            });

            services.AddMvc(o =>
            {
                o.Filters.Add(new ordercloud.integrations.library.ValidateModelAttribute());
                o.EnableEndpointRouting = false;
            })
            .ConfigureApiBehaviorOptions(o => o.SuppressModelStateInvalidFilter = true)
            .SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
            .AddNewtonsoftJson(options =>
            {
                options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
                options.SerializerSettings.Converters.Add(new StringEnumConverter());
            });

            services
            .AddCors(o => o.AddPolicy("integrationcors", builder => { builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); }))
            .AddSingleton <ISimpleCache, LazyCacheService>()    // Replace LazyCacheService with RedisService if you have multiple server instances.
            .AddOrderCloudUserAuth()
            .AddOrderCloudWebhookAuth(opts => opts.HashKey = _settings.OrderCloudSettings.WebhookHashKey)
            .InjectCosmosStore <LogQuery, OrchestrationLog>(cosmosConfig)
            .InjectCosmosStore <ReportTemplateQuery, ReportTemplate>(cosmosConfig)
            .AddCosmosDb(_settings.CosmosSettings.EndpointUri, _settings.CosmosSettings.PrimaryKey, _settings.CosmosSettings.DatabaseName, cosmosContainers)
            .Inject <IPortalService>()
            .Inject <ISmartyStreetsCommand>()
            .Inject <ICheckoutIntegrationCommand>()
            .Inject <IShipmentCommand>()
            .Inject <IOrderCommand>()
            .Inject <IPaymentCommand>()
            .Inject <IOrderSubmitCommand>()
            .Inject <IEnvironmentSeedCommand>()
            .Inject <IHSProductCommand>()
            .Inject <ILineItemCommand>()
            .Inject <IMeProductCommand>()
            .Inject <IHSCatalogCommand>()
            .Inject <ISendgridService>()
            .Inject <IHSSupplierCommand>()
            .Inject <ICreditCardCommand>()
            .Inject <ISupportAlertService>()
            .Inject <ISupplierApiClientHelper>()
            .AddSingleton <ISendGridClient>(x => new SendGridClient(_settings.SendgridSettings.ApiKey))
            .AddSingleton <IFlurlClientFactory>(x => flurlClientFactory)
            .AddSingleton <DownloadReportCommand>()
            .Inject <IRMARepo>()
            .Inject <IZohoClient>()
            .AddSingleton <IZohoCommand>(z => new ZohoCommand(new ZohoClient(
                                                                  new ZohoClientConfig()
            {
                ApiUrl         = "https://books.zoho.com/api/v3",
                AccessToken    = _settings.ZohoSettings.AccessToken,
                ClientId       = _settings.ZohoSettings.ClientId,
                ClientSecret   = _settings.ZohoSettings.ClientSecret,
                OrganizationID = _settings.ZohoSettings.OrgID
            }, flurlClientFactory),
                                                              orderCloudClient))
            .AddSingleton <IOrderCloudIntegrationsExchangeRatesClient, OrderCloudIntegrationsExchangeRatesClient>()
            .AddSingleton <IAssetClient>(provider => new AssetClient(new OrderCloudIntegrationsBlobService(assetConfig), _settings))
            .AddSingleton <IExchangeRatesCommand>(provider => new ExchangeRatesCommand(new OrderCloudIntegrationsBlobService(currencyConfig), flurlClientFactory, provider.GetService <ISimpleCache>()))
            .AddSingleton <IExchangeRatesCommand>(provider => new ExchangeRatesCommand(new OrderCloudIntegrationsBlobService(currencyConfig), flurlClientFactory, provider.GetService <ISimpleCache>()))
            .AddSingleton <IAvalaraCommand>(x => new AvalaraCommand(
                                                orderCloudClient,
                                                avalaraConfig,
                                                new AvaTaxClient("four51_headstart", "v1", "four51_headstart", new Uri(avalaraConfig.BaseApiUrl)
                                                                 ).WithSecurity(_settings.AvalaraSettings.AccountID, _settings.AvalaraSettings.LicenseKey), _settings.EnvironmentSettings.Environment.ToString()))
            .AddSingleton <IEasyPostShippingService>(x => new EasyPostShippingService(new EasyPostConfig()
            {
                APIKey = _settings.EasyPostSettings.APIKey
            }))
            .AddSingleton <ISmartyStreetsService>(x => new SmartyStreetsService(_settings.SmartyStreetSettings, smartyStreetsUsClient))
            .AddSingleton <IOrderCloudIntegrationsCardConnectService>(x => new OrderCloudIntegrationsCardConnectService(_settings.CardConnectSettings, _settings.EnvironmentSettings.Environment.ToString(), flurlClientFactory))
            .AddSingleton <IOrderCloudClient>(provider => orderCloudClient)
            .AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo {
                    Title = "Headstart Middleware API Documentation", Version = "v1"
                });
                c.SchemaFilter <SwaggerExcludeFilter>();
            });
            var serviceProvider = services.BuildServiceProvider();

            services
            .AddApplicationInsightsTelemetry(new ApplicationInsightsServiceOptions
            {
                EnableAdaptiveSampling = false,     // retain all data
                InstrumentationKey     = _settings.ApplicationInsightsSettings.InstrumentationKey
            });


            ServicePointManager.DefaultConnectionLimit       = int.MaxValue;
            FlurlHttp.Configure(settings => settings.Timeout = TimeSpan.FromSeconds(_settings.FlurlSettings.TimeoutInSeconds == 0 ? 30 : _settings.FlurlSettings.TimeoutInSeconds));

            // This adds retry logic for any api call that fails with a transient error (server errors, timeouts, or rate limiting requests)
            // Will retry up to 3 times using exponential backoff and jitter, a mean of 3 seconds wait time in between retries
            // https://github.com/App-vNext/Polly/wiki/Retry-with-jitter#more-complex-jitter
            var delay  = Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromSeconds(3), retryCount: 3);
            var policy = HttpPolicyExtensions
                         .HandleTransientHttpError()
                         .OrResult(response => response.StatusCode == HttpStatusCode.TooManyRequests)
                         .WaitAndRetryAsync(delay);

            FlurlHttp.Configure(settings => settings.HttpClientFactory = new PollyFactory(policy));
        }
Exemple #8
0
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            var cosmosConfig = new CosmosConfig(
                _settings.CosmosSettings.DatabaseName,
                _settings.CosmosSettings.EndpointUri,
                _settings.CosmosSettings.PrimaryKey,
                _settings.CosmosSettings.RequestTimeoutInSeconds
                );
            var cosmosContainers = new List <ContainerInfo>()
            {
                new ContainerInfo()
                {
                    Name         = "salesorderdetail",
                    PartitionKey = "/PartitionKey"
                },
                new ContainerInfo()
                {
                    Name         = "purchaseorderdetail",
                    PartitionKey = "/PartitionKey"
                },
                new ContainerInfo()
                {
                    Name         = "lineitemdetail",
                    PartitionKey = "/PartitionKey"
                },
                new ContainerInfo()
                {
                    Name         = "rmas",
                    PartitionKey = "/PartitionKey"
                },
                new ContainerInfo()
                {
                    Name         = "shipmentdetail",
                    PartitionKey = "/PartitionKey"
                },
                new ContainerInfo()
                {
                    Name         = "productdetail",
                    PartitionKey = "/PartitionKey"
                }
            };

            var avalaraConfig = new AvalaraConfig()
            {
                BaseApiUrl  = _settings.AvalaraSettings.BaseApiUrl,
                AccountID   = _settings.AvalaraSettings.AccountID,
                LicenseKey  = _settings.AvalaraSettings.LicenseKey,
                CompanyCode = _settings.AvalaraSettings.CompanyCode,
                CompanyID   = _settings.AvalaraSettings.CompanyID
            };

            var currencyConfig = new BlobServiceConfig()
            {
                ConnectionString = _settings.StorageAccountSettings.ConnectionString,
                Container        = _settings.StorageAccountSettings.BlobContainerNameExchangeRates
            };
            var assetConfig = new BlobServiceConfig()
            {
                ConnectionString = _settings.StorageAccountSettings.ConnectionString,
                Container        = "assets",
                AccessType       = BlobContainerPublicAccessType.Container
            };

            var flurlClientFactory    = new PerBaseUrlFlurlClientFactory();
            var smartyStreetsUsClient = new ClientBuilder(_settings.SmartyStreetSettings.AuthID, _settings.SmartyStreetSettings.AuthToken).BuildUsStreetApiClient();
            var orderCloudClient      = new OrderCloudClient(new OrderCloudClientConfig
            {
                ApiUrl       = _settings.OrderCloudSettings.ApiUrl,
                AuthUrl      = _settings.OrderCloudSettings.ApiUrl,
                ClientId     = _settings.OrderCloudSettings.MiddlewareClientID,
                ClientSecret = _settings.OrderCloudSettings.MiddlewareClientSecret,
                Roles        = new[] { ApiRole.FullAccess }
            });

            AvalaraCommand avalaraCommand = null;
            VertexCommand  vertexCommand  = null;
            TaxJarCommand  taxJarCommand  = null;

            switch (_settings.EnvironmentSettings.TaxProvider)
            {
            case TaxProvider.Avalara:
                avalaraCommand = new AvalaraCommand(avalaraConfig, _settings.EnvironmentSettings.Environment.ToString());
                break;

            case TaxProvider.Taxjar:
                taxJarCommand = new TaxJarCommand(_settings.TaxJarSettings);
                break;

            case TaxProvider.Vertex:
                vertexCommand = new VertexCommand(_settings.VertexSettings);
                break;

            default:
                break;
            }

            services.AddMvc(o =>
            {
                o.Filters.Add(new ordercloud.integrations.library.ValidateModelAttribute());
                o.EnableEndpointRouting = false;
            })
            .ConfigureApiBehaviorOptions(o => o.SuppressModelStateInvalidFilter = true)
            .SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
            .AddNewtonsoftJson(options =>
            {
                options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
                options.SerializerSettings.Converters.Add(new StringEnumConverter());
            });

            services
            .AddCors(o => o.AddPolicy("integrationcors", builder => { builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); }))
            .AddSingleton <ISimpleCache, LazyCacheService>()    // Replace LazyCacheService with RedisService if you have multiple server instances.
            .AddOrderCloudUserAuth()
            .AddOrderCloudWebhookAuth(opts => opts.HashKey = _settings.OrderCloudSettings.WebhookHashKey)
            .InjectCosmosStore <LogQuery, OrchestrationLog>(cosmosConfig)
            .InjectCosmosStore <ReportTemplateQuery, ReportTemplate>(cosmosConfig)
            .AddCosmosDb(_settings.CosmosSettings.EndpointUri, _settings.CosmosSettings.PrimaryKey, _settings.CosmosSettings.DatabaseName, cosmosContainers)
            .Inject <IPortalService>()
            .Inject <ISmartyStreetsCommand>()
            .Inject <ICheckoutIntegrationCommand>()
            .Inject <IShipmentCommand>()
            .Inject <IOrderCommand>()
            .Inject <IPaymentCommand>()
            .Inject <IOrderSubmitCommand>()
            .Inject <IEnvironmentSeedCommand>()
            .Inject <IHSProductCommand>()
            .Inject <ILineItemCommand>()
            .Inject <IMeProductCommand>()
            .Inject <IDiscountDistributionService>()
            .Inject <IHSCatalogCommand>()
            .Inject <ISendgridService>()
            .Inject <IHSSupplierCommand>()
            .Inject <ICreditCardCommand>()
            .Inject <ISupportAlertService>()
            .Inject <ISupplierApiClientHelper>()
            .AddSingleton <ISendGridClient>(x => new SendGridClient(_settings.SendgridSettings.ApiKey))
            .AddSingleton <IFlurlClientFactory>(x => flurlClientFactory)
            .AddSingleton <DownloadReportCommand>()
            .Inject <IRMARepo>()
            .Inject <IZohoClient>()
            .AddSingleton <IZohoCommand>(z => new ZohoCommand(new ZohoClient(
                                                                  new ZohoClientConfig()
            {
                ApiUrl         = "https://books.zoho.com/api/v3",
                AccessToken    = _settings.ZohoSettings.AccessToken,
                ClientId       = _settings.ZohoSettings.ClientId,
                ClientSecret   = _settings.ZohoSettings.ClientSecret,
                OrganizationID = _settings.ZohoSettings.OrgID
            }, flurlClientFactory),
                                                              orderCloudClient))
            .AddSingleton <IOrderCloudIntegrationsExchangeRatesClient, OrderCloudIntegrationsExchangeRatesClient>()
            .AddSingleton <IAssetClient>(provider => new AssetClient(new OrderCloudIntegrationsBlobService(assetConfig), _settings))
            .AddSingleton <IExchangeRatesCommand>(provider => new ExchangeRatesCommand(new OrderCloudIntegrationsBlobService(currencyConfig), flurlClientFactory, provider.GetService <ISimpleCache>()))
            .AddSingleton <ITaxCodesProvider>(provider =>
            {
                return(_settings.EnvironmentSettings.TaxProvider switch
                {
                    TaxProvider.Avalara => avalaraCommand,
                    TaxProvider.Taxjar => taxJarCommand,
                    TaxProvider.Vertex => new NotImplementedTaxCodesProvider(),
                    _ => avalaraCommand     // Avalara is default
                });
            })