From dedd6b2275d7114bbf02598bf5bc7da20bd2e417 Mon Sep 17 00:00:00 2001 From: Martin Kyjac Date: Fri, 26 Jul 2024 15:54:30 +0200 Subject: [PATCH 1/9] Add new configuration section --- examples/DancingGoat-Shopify/appsettings.json | 18 +++++++ .../ShopifyWebsiteChannelConfigOptions.cs | 52 +++++++++++++++++++ .../Extensions/ServiceCollectionExtensions.cs | 1 + 3 files changed, 71 insertions(+) create mode 100644 src/Kentico.Xperience.Shopify/Config/ShopifyWebsiteChannelConfigOptions.cs diff --git a/examples/DancingGoat-Shopify/appsettings.json b/examples/DancingGoat-Shopify/appsettings.json index 67e5137..a5468e0 100644 --- a/examples/DancingGoat-Shopify/appsettings.json +++ b/examples/DancingGoat-Shopify/appsettings.json @@ -19,5 +19,23 @@ "AdminApiToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "StorefrontApiToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "StorefrontApiVersion": "YYYY-MM" + }, + "ShopifyWebsiteChannelsConfig": { + "Settings": [ + { + "ChannelName": "MyWebsiteChannel1", + "CurrencyCode": "CZK", + "Country": "CZ" + }, + { + "ChannelName": "MyWebsiteChannel2", + "CurrencyCode": "USD", + "Country": "US" + } + ], + "DefaultSetting": { + "CurrencyCode": "CZK", + "Country": "CZ" + } } } \ No newline at end of file diff --git a/src/Kentico.Xperience.Shopify/Config/ShopifyWebsiteChannelConfigOptions.cs b/src/Kentico.Xperience.Shopify/Config/ShopifyWebsiteChannelConfigOptions.cs new file mode 100644 index 0000000..9d93ddc --- /dev/null +++ b/src/Kentico.Xperience.Shopify/Config/ShopifyWebsiteChannelConfigOptions.cs @@ -0,0 +1,52 @@ +namespace Kentico.Xperience.Shopify.Config +{ + /// + /// Class with list of website channel configurations. + /// + public class ShopifyWebsiteChannelConfigOptions + { + /// + /// The name of configuration section. + /// + public const string SECTION_NAME = "ShopifyWebsiteChannelsConfig"; + + /// + /// Website channel configurations list. + /// + public required List Settings { get; set; } + + /// + /// Default setting used if no setting for current website channel is found. + /// + public required ShopifyWebsiteChannelConfig? DefaultSetting { get; set; } + } + + + /// + /// Class for website channel configuration + /// + public class ShopifySpecificWebsiteChannelConfig : ShopifyWebsiteChannelConfig + { + /// + /// Website channel name. + /// + public required string ChannelName { get; set; } + } + + + /// + /// Class for default channel configuration. + /// + public class ShopifyWebsiteChannelConfig + { + /// + /// ISO 4217 currency code. + /// + public required string CurrencyCode { get; set; } + + /// + /// Two letter country code. + /// + public required string Country { get; set; } + } +} diff --git a/src/Kentico.Xperience.Shopify/Extensions/ServiceCollectionExtensions.cs b/src/Kentico.Xperience.Shopify/Extensions/ServiceCollectionExtensions.cs index c9fe547..c46ad41 100644 --- a/src/Kentico.Xperience.Shopify/Extensions/ServiceCollectionExtensions.cs +++ b/src/Kentico.Xperience.Shopify/Extensions/ServiceCollectionExtensions.cs @@ -53,6 +53,7 @@ public static void RegisterShopifyServices(this IServiceCollection services, ICo // Add options monitor services.Configure(configuration.GetSection(ShopifyConfig.SECTION_NAME)); + services.Configure(configuration.GetSection(ShopifyWebsiteChannelConfigOptions.SECTION_NAME)); // Add HTTP session services services.AddSession(); From e321e23d015e9f050ea8354eea033fd356537676 Mon Sep 17 00:00:00 2001 From: Martin Kyjac Date: Fri, 26 Jul 2024 15:55:12 +0200 Subject: [PATCH 2/9] Retrieve website channel config from configuration --- .../IShopifyIntegrationSettingsService.cs | 8 ++++ .../ShopifyIntegrationSettingsService.cs | 38 ++++++++++++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/Kentico.Xperience.Shopify/Config/IShopifyIntegrationSettingsService.cs b/src/Kentico.Xperience.Shopify/Config/IShopifyIntegrationSettingsService.cs index e1dc668..cd75ee2 100644 --- a/src/Kentico.Xperience.Shopify/Config/IShopifyIntegrationSettingsService.cs +++ b/src/Kentico.Xperience.Shopify/Config/IShopifyIntegrationSettingsService.cs @@ -11,5 +11,13 @@ public interface IShopifyIntegrationSettingsService /// /// containing the settings or NULL if no configuration is found. ShopifyConfig? GetSettings(); + + /// + /// Get current website channel configuration from appsettings. + /// + /// + /// containing configuration for current website channel or default value if no configuration is found. + /// + ShopifyWebsiteChannelConfig? GetWebsiteChannelSettings(); } } diff --git a/src/Kentico.Xperience.Shopify/Config/ShopifyIntegrationSettingsService.cs b/src/Kentico.Xperience.Shopify/Config/ShopifyIntegrationSettingsService.cs index 4c969b4..52e0138 100644 --- a/src/Kentico.Xperience.Shopify/Config/ShopifyIntegrationSettingsService.cs +++ b/src/Kentico.Xperience.Shopify/Config/ShopifyIntegrationSettingsService.cs @@ -1,5 +1,8 @@ -using CMS.DataEngine; +using System.Diagnostics; + +using CMS.DataEngine; using CMS.Helpers; +using CMS.Websites.Routing; using Kentico.Xperience.Shopify.Admin; @@ -10,22 +13,28 @@ namespace Kentico.Xperience.Shopify.Config internal class ShopifyIntegrationSettingsService : IShopifyIntegrationSettingsService { private readonly IProgressiveCache cache; - private readonly IOptionsMonitor monitor; + private readonly IOptionsMonitor shopifyConfigMonitor; private readonly IInfoProvider integrationSettingsProvider; + private readonly IOptionsMonitor websiteChannelConfigMonitor; + private readonly IWebsiteChannelContext websiteChannelContext; public ShopifyIntegrationSettingsService( IProgressiveCache cache, - IOptionsMonitor monitor, - IInfoProvider integrationSettingsProvider) + IOptionsMonitor shopifyConfigMonitor, + IInfoProvider integrationSettingsProvider, + IOptionsMonitor websiteChannelConfigMonitor, + IWebsiteChannelContext websiteChannelContext) { this.cache = cache; - this.monitor = monitor; + this.shopifyConfigMonitor = shopifyConfigMonitor; this.integrationSettingsProvider = integrationSettingsProvider; + this.websiteChannelConfigMonitor = websiteChannelConfigMonitor; + this.websiteChannelContext = websiteChannelContext; } public ShopifyConfig? GetSettings() { - var monitorValue = monitor.CurrentValue; + var monitorValue = shopifyConfigMonitor.CurrentValue; if (ShopifyConfigIsFilled(monitorValue)) { @@ -40,6 +49,23 @@ public ShopifyIntegrationSettingsService( } + public ShopifyWebsiteChannelConfig? GetWebsiteChannelSettings() + { + var monitorValue = websiteChannelConfigMonitor.CurrentValue; + + if (monitorValue == null) + { + return null; + } + + string? currentChannel = websiteChannelContext.WebsiteChannelName; + if (string.IsNullOrEmpty(currentChannel)) + { + return monitorValue.DefaultSetting; + } + return monitorValue.Settings?.Find(x => x.ChannelName == currentChannel) ?? monitorValue.DefaultSetting; + } + private ShopifyConfig? GetConfigFromSettings() { var settingsInfo = integrationSettingsProvider.Get() From e55802a457e232e942eefae461d920ba023493aa Mon Sep 17 00:00:00 2001 From: Martin Kyjac Date: Fri, 26 Jul 2024 15:56:25 +0200 Subject: [PATCH 3/9] Remove static currency codes and replace with value from configuration --- .../ShopifyProductListWidgetProperties.cs | 1 - .../Shopify/ShopifyCategoryController.cs | 10 ++++++++-- .../Shopify/ShopifyProductDetailController.cs | 19 +++++++++++++------ .../Shopify/ShopifyStoreController.cs | 10 ++++++++-- .../CategoryPage/CategoryPageViewModel.cs | 13 +++++++++---- .../ShopifyProductListItemViewModel.cs | 5 ++--- .../DisplayTemplates/ProductListItem.cshtml | 2 +- .../Products/Models/ProductFilter.cs | 2 +- .../Products/ShopifyPriceService.cs | 15 ++++++++++----- .../Products/ShopifyProductService.cs | 9 ++++++--- 10 files changed, 58 insertions(+), 28 deletions(-) diff --git a/examples/DancingGoat-Shopify/Components/Widgets/Shopify/ProductListWidget/ShopifyProductListWidgetProperties.cs b/examples/DancingGoat-Shopify/Components/Widgets/Shopify/ProductListWidget/ShopifyProductListWidgetProperties.cs index 50923c2..4e529b2 100644 --- a/examples/DancingGoat-Shopify/Components/Widgets/Shopify/ProductListWidget/ShopifyProductListWidgetProperties.cs +++ b/examples/DancingGoat-Shopify/Components/Widgets/Shopify/ProductListWidget/ShopifyProductListWidgetProperties.cs @@ -12,7 +12,6 @@ public class ShopifyProductListWidgetProperties : IWidgetProperties [EditingComponent(TextInputComponent.IDENTIFIER, Label = "Title", Order = 0)] public string Title { get; set; } - // TODO - create selector for long datatype [EditingComponent(ShopifyCollectionSelectorComponent.IDENTIFIER, Label = "Collection", Order = 10)] public string CollectionID { get; set; } diff --git a/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyCategoryController.cs b/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyCategoryController.cs index fb8d92a..77ac5f9 100644 --- a/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyCategoryController.cs +++ b/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyCategoryController.cs @@ -7,6 +7,7 @@ using Kentico.Content.Web.Mvc; using Kentico.Content.Web.Mvc.Routing; +using Kentico.Xperience.Shopify.Config; using Kentico.Xperience.Shopify.Products; using Kentico.Xperience.Shopify.Products.Models; @@ -28,6 +29,7 @@ public class ShopifyCategoryController : Controller private readonly ISettingsService settingsService; private readonly IConversionService conversionService; private readonly ILogger logger; + private readonly IShopifyIntegrationSettingsService shopifySettingsService; public ShopifyCategoryController( CategoryPageRepository categoryPageRepository, @@ -37,7 +39,8 @@ public ShopifyCategoryController( IProgressiveCache progressiveCache, ISettingsService settingsService, IConversionService conversionService, - ILogger logger) + ILogger logger, + IShopifyIntegrationSettingsService shopifySettingsService) { this.categoryPageRepository = categoryPageRepository; this.webPageDataContextRetriever = webPageDataContextRetriever; @@ -47,6 +50,7 @@ public ShopifyCategoryController( this.settingsService = settingsService; this.conversionService = conversionService; this.logger = logger; + this.shopifySettingsService = shopifySettingsService; } public async Task Index() { @@ -62,7 +66,9 @@ public async Task Index() async (_) => await GetProductPrices(products.Select(x => x.Product.FirstOrDefault())), new CacheSettings(cacheMinutes, webPage.WebsiteChannelName, webPage.LanguageName, categoryPage.SystemFields.WebPageItemGUID)); - return View(CategoryPageViewModel.GetViewModel(categoryPage, prices, products, urls, logger)); + string currencyCode = shopifySettingsService.GetWebsiteChannelSettings()?.CurrencyCode ?? string.Empty; + + return View(CategoryPageViewModel.GetViewModel(categoryPage, prices, products, urls, logger, currencyCode)); } private async Task> GetProductPrices(IEnumerable productContentItems) diff --git a/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyProductDetailController.cs b/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyProductDetailController.cs index 65e5b58..a91db24 100644 --- a/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyProductDetailController.cs +++ b/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyProductDetailController.cs @@ -5,10 +5,12 @@ using Kentico.Content.Web.Mvc; using Kentico.Content.Web.Mvc.Routing; +using Kentico.Xperience.Shopify.Config; using Kentico.Xperience.Shopify.Products.Models; using Kentico.Xperience.Shopify.ShoppingCart; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; using Shopify.Controllers; @@ -24,28 +26,33 @@ public class ShopifyProductDetailController : Controller private readonly ProductDetailPageRepository productDetailPageRepository; private readonly IWebPageDataContextRetriever webPageDataContextRetriever; private readonly IShoppingService shoppingService; + private readonly IShopifyIntegrationSettingsService settingsService; public ShopifyProductDetailController(ProductDetailPageRepository productDetailPageRepository, IWebPageDataContextRetriever webPageDataContextRetriever, - IShoppingService shoppingService) + IShoppingService shoppingService, + IShopifyIntegrationSettingsService settingsService) { this.productDetailPageRepository = productDetailPageRepository; this.webPageDataContextRetriever = webPageDataContextRetriever; this.shoppingService = shoppingService; + this.settingsService = settingsService; } [HttpGet] public async Task Index(string variantID = null) { - // TODO - dynamic resolve country - string country = "CZ"; - string currency = "CZK"; - if (!TempData.TryGetValue(ERROR_MESSAGES_KEY, out object tempDataErrors) || tempDataErrors is not string[] errorMessages) { errorMessages = []; } + var config = settingsService.GetWebsiteChannelSettings(); + if (config == null) + { + return View(new ProductDetailViewModel()); + } + var webPage = webPageDataContextRetriever.Retrieve().WebPage; var productDetail = await productDetailPageRepository.GetProductDetailPage(webPage.WebPageItemID, webPage.LanguageName, HttpContext.RequestAborted); @@ -54,7 +61,7 @@ public async Task Index(string variantID = null) return View(new ProductDetailViewModel()); } - return View(ProductDetailViewModel.GetViewModel(productDetail, variantID ?? string.Empty, country, currency, errorMessages)); + return View(ProductDetailViewModel.GetViewModel(productDetail, variantID ?? string.Empty, config.Country, config.CurrencyCode, errorMessages)); } [HttpPost] diff --git a/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyStoreController.cs b/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyStoreController.cs index 742e0a1..d3d42cf 100644 --- a/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyStoreController.cs +++ b/examples/DancingGoat-Shopify/Controllers/Shopify/ShopifyStoreController.cs @@ -9,6 +9,7 @@ using Kentico.Content.Web.Mvc; using Kentico.Content.Web.Mvc.Routing; +using Kentico.Xperience.Shopify.Config; using Kentico.Xperience.Shopify.Products; using Kentico.Xperience.Shopify.Products.Models; @@ -32,6 +33,7 @@ public class ShopifyStoreController : Controller private readonly ISettingsService settingsService; private readonly IConversionService conversionService; private readonly IShopifyPriceService priceService; + private readonly IShopifyIntegrationSettingsService shopifySettingsService; public ShopifyStoreController(StorePageRepository storePageRepository, IWebPageDataContextRetriever webPageDataContextRetriever, @@ -42,7 +44,8 @@ public ShopifyStoreController(StorePageRepository storePageRepository, IProgressiveCache progressiveCache, ISettingsService settingsService, IConversionService conversionService, - IShopifyPriceService priceService) + IShopifyPriceService priceService, + IShopifyIntegrationSettingsService shopifySettingsService) { this.storePageRepository = storePageRepository; this.webPageDataContextRetriever = webPageDataContextRetriever; @@ -54,6 +57,7 @@ public ShopifyStoreController(StorePageRepository storePageRepository, this.settingsService = settingsService; this.conversionService = conversionService; this.priceService = priceService; + this.shopifySettingsService = shopifySettingsService; } public async Task Index() @@ -86,7 +90,9 @@ private async Task> GetProductListV if (!string.IsNullOrEmpty(shopifyProductId) && prices.TryGetValue(shopifyProductId, out var price)) { - productViewModels.Add(ShopifyProductListItemViewModel.GetViewModel(productPage.Product.FirstOrDefault(), url, price)); + string currencyCode = shopifySettingsService.GetWebsiteChannelSettings()?.CurrencyCode ?? string.Empty; + + productViewModels.Add(ShopifyProductListItemViewModel.GetViewModel(productPage.Product.FirstOrDefault(), url, price, currencyCode)); } } diff --git a/examples/DancingGoat-Shopify/Models/WebPage/Shopify/CategoryPage/CategoryPageViewModel.cs b/examples/DancingGoat-Shopify/Models/WebPage/Shopify/CategoryPage/CategoryPageViewModel.cs index 92ff531..bc4e2cd 100644 --- a/examples/DancingGoat-Shopify/Models/WebPage/Shopify/CategoryPage/CategoryPageViewModel.cs +++ b/examples/DancingGoat-Shopify/Models/WebPage/Shopify/CategoryPage/CategoryPageViewModel.cs @@ -14,7 +14,8 @@ public static CategoryPageViewModel GetViewModel( IDictionary productPrices, IEnumerable products, IDictionary productUrls, - ILogger logger) + ILogger logger, + string currencyCode) { var productListItems = new List(); foreach (var product in products) @@ -25,7 +26,7 @@ public static CategoryPageViewModel GetViewModel( } else { - productListItems.Add(GetProductListItem(product, productUrls, productPrices)); + productListItems.Add(GetProductListItem(product, productUrls, productPrices, currencyCode)); } } var model = new CategoryPageViewModel @@ -37,7 +38,11 @@ public static CategoryPageViewModel GetViewModel( return model; } - private static ShopifyProductListItemViewModel GetProductListItem(ProductDetailPage productPage, IDictionary productUrls, IDictionary productPrices) + private static ShopifyProductListItemViewModel GetProductListItem( + ProductDetailPage productPage, + IDictionary productUrls, + IDictionary productPrices, + string currencyCode) { var product = productPage.Product.FirstOrDefault(); if (product == null) @@ -47,6 +52,6 @@ private static ShopifyProductListItemViewModel GetProductListItem(ProductDetailP productUrls.TryGetValue(productPage.SystemFields.WebPageItemGUID, out var url); productPrices.TryGetValue(product.ShopifyProductID, out var productPriceModel); - return ShopifyProductListItemViewModel.GetViewModel(product, url, productPriceModel); + return ShopifyProductListItemViewModel.GetViewModel(product, url, productPriceModel, currencyCode); } } diff --git a/examples/DancingGoat-Shopify/Models/WebPage/Shopify/_Shared/ShopifyProductListItemViewModel.cs b/examples/DancingGoat-Shopify/Models/WebPage/Shopify/_Shared/ShopifyProductListItemViewModel.cs index 64d20ec..453069e 100644 --- a/examples/DancingGoat-Shopify/Models/WebPage/Shopify/_Shared/ShopifyProductListItemViewModel.cs +++ b/examples/DancingGoat-Shopify/Models/WebPage/Shopify/_Shared/ShopifyProductListItemViewModel.cs @@ -16,10 +16,9 @@ public record ShopifyProductListItemViewModel public string ListPrice { get; init; } public bool HasMultipleVariants { get; init; } - public static ShopifyProductListItemViewModel GetViewModel(Product product, WebPageUrl productUrl, ProductPriceModel priceModel) + public static ShopifyProductListItemViewModel GetViewModel(Product product, WebPageUrl productUrl, ProductPriceModel priceModel, string currencyCode) { - // TODO resolve currency - string currency = "CZK"; + string currency = currencyCode; var mainImage = product.Images.FirstOrDefault() ?? product.Variants.FirstOrDefault(x => x.Image.Any())?.Image.FirstOrDefault(); return new ShopifyProductListItemViewModel() { diff --git a/src/Kentico.Xperience.Shopify.Rcl/Views/Shared/DisplayTemplates/ProductListItem.cshtml b/src/Kentico.Xperience.Shopify.Rcl/Views/Shared/DisplayTemplates/ProductListItem.cshtml index ebfcb54..1aac45f 100644 --- a/src/Kentico.Xperience.Shopify.Rcl/Views/Shared/DisplayTemplates/ProductListItem.cshtml +++ b/src/Kentico.Xperience.Shopify.Rcl/Views/Shared/DisplayTemplates/ProductListItem.cshtml @@ -3,7 +3,7 @@ @{ var price = Model.PriceDetail; - var currencyFormat = "0:00#";//TODO; + var currencyFormat = "0:00#"; }
diff --git a/src/Kentico.Xperience.Shopify/Products/Models/ProductFilter.cs b/src/Kentico.Xperience.Shopify/Products/Models/ProductFilter.cs index 902880f..b48c4d1 100644 --- a/src/Kentico.Xperience.Shopify/Products/Models/ProductFilter.cs +++ b/src/Kentico.Xperience.Shopify/Products/Models/ProductFilter.cs @@ -16,7 +16,7 @@ public class ProductFilter /// /// The currency to use for pricing. /// - public CurrencyCode? Currency { get; set; } + public CurrencyCode Currency { get; set; } /// diff --git a/src/Kentico.Xperience.Shopify/Products/ShopifyPriceService.cs b/src/Kentico.Xperience.Shopify/Products/ShopifyPriceService.cs index 664b36e..66dbc55 100644 --- a/src/Kentico.Xperience.Shopify/Products/ShopifyPriceService.cs +++ b/src/Kentico.Xperience.Shopify/Products/ShopifyPriceService.cs @@ -4,18 +4,22 @@ using ShopifySharp; using ShopifySharp.Factories; using ShopifySharp.Filters; -using ShopifySharp.GraphQL; namespace Kentico.Xperience.Shopify.Products; internal class ShopifyPriceService : ShopifyServiceBase, IShopifyPriceService { private readonly IProductService productService; + private readonly IShopifyIntegrationSettingsService settingsService; - public ShopifyPriceService(IShopifyIntegrationSettingsService integrationSettingsService, IProductServiceFactory productServiceFactory) + public ShopifyPriceService( + IShopifyIntegrationSettingsService integrationSettingsService, + IProductServiceFactory productServiceFactory, + IShopifyIntegrationSettingsService settingsService) : base(integrationSettingsService) { productService = productServiceFactory.Create(shopifyCredentials); + this.settingsService = settingsService; } public async Task> GetProductsPrice(IEnumerable shopifyProductIds) @@ -27,14 +31,15 @@ public async Task> GetProductsPrice(IEnum private async Task> GetProductsPriceInternal(IEnumerable shopifyProductIds) { - string currency = CurrencyCode.CZK.ToString(); + string currencyCode = settingsService.GetWebsiteChannelSettings()?.CurrencyCode ?? string.Empty; + var dict = new Dictionary(); var filter = new ProductListFilter() { Ids = shopifyProductIds.Select(long.Parse), Fields = "Variants,Id", - PresentmentCurrencies = [currency] + PresentmentCurrencies = [currencyCode] }; var result = await productService.ListAsync(filter, true); @@ -45,7 +50,7 @@ private async Task> GetProductsPriceInter continue; } - var prices = product.Variants.Select(x => x.PresentmentPrices.FirstOrDefault(x => x.Price.CurrencyCode.Equals(currency, StringComparison.Ordinal))); + var prices = product.Variants.Select(x => x.PresentmentPrices.FirstOrDefault(x => x.Price.CurrencyCode.Equals(currencyCode, StringComparison.Ordinal))); dict.TryAdd(product.Id.Value.ToString(), new ProductPriceModel() { diff --git a/src/Kentico.Xperience.Shopify/Products/ShopifyProductService.cs b/src/Kentico.Xperience.Shopify/Products/ShopifyProductService.cs index b4cc668..f03297a 100644 --- a/src/Kentico.Xperience.Shopify/Products/ShopifyProductService.cs +++ b/src/Kentico.Xperience.Shopify/Products/ShopifyProductService.cs @@ -15,6 +15,7 @@ internal class ShopifyProductService : ShopifyServiceBase, IShopifyProductServic private readonly IProductService productService; private readonly IShopifyInventoryService inventoryService; private readonly IShoppingService shoppingService; + private readonly IShopifyIntegrationSettingsService settingsService; private readonly Uri shopifyProductUrlBase; private readonly string[] _shopifyFields = ["title", "body_html", "handle", "images", "variants"]; @@ -26,10 +27,12 @@ public ShopifyProductService( IShopifyIntegrationSettingsService integrationSettingsService, IProductServiceFactory productServiceFactory, IShopifyInventoryService inventoryService, - IShoppingService shoppingService) : base(integrationSettingsService) + IShoppingService shoppingService, + IShopifyIntegrationSettingsService settingsService) : base(integrationSettingsService) { this.inventoryService = inventoryService; this.shoppingService = shoppingService; + this.settingsService = settingsService; productService = productServiceFactory.Create(shopifyCredentials); @@ -79,7 +82,7 @@ private async Task> GetProductsIntern { var filter = new ListFilter(filterParams?.PageInfo, filterParams?.Limit, ShopifyFields); var result = await productService.ListAsync(filter, true); - return CreateResultModel(result, "USD"); + return CreateResultModel(result, settingsService.GetWebsiteChannelSettings()?.CurrencyCode ?? string.Empty); } private async Task> GetProductVariantsInternal(string shopifyProductID, string currencyCode) @@ -106,7 +109,7 @@ private async Task> GetProductsAsyncI CollectionId = initialFilter.CollectionID, Limit = initialFilter.Limit, Ids = initialFilter.Ids, - PresentmentCurrencies = [initialFilter.Currency?.ToString() ?? string.Empty] + PresentmentCurrencies = [initialFilter.Currency.ToString()] }; var result = await productService.ListAsync(filter, true); From f5cb431c5ff3f850c5b865b1125da4edb13c73cf Mon Sep 17 00:00:00 2001 From: Martin Kyjac Date: Mon, 29 Jul 2024 09:34:21 +0200 Subject: [PATCH 4/9] Add new setting description into readme --- README.md | 175 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 112 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 4685ca4..419919d 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,21 @@ [![7-day bug-fix policy](https://img.shields.io/badge/-7--days_bug--fixing_policy-grey?labelColor=orange&logo=data:image/svg+xml;base64,PHN2ZyBjbGFzcz0ic3ZnLWljb24iIHN0eWxlPSJ3aWR0aDogMWVtOyBoZWlnaHQ6IDFlbTt2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlO2ZpbGw6IGN1cnJlbnRDb2xvcjtvdmVyZmxvdzogaGlkZGVuOyIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik04ODguNDkgMjIyLjY4NnYtMzEuNTRsLTY1LjY3Mi0wLjk1NWgtMC4yMDVhNDY1LjcxNSA0NjUuNzE1IDAgMCAxLTE0NC4zMTUtMzEuMzM0Yy03Ny4wMDUtMzEuMTk4LTEyNi4yOTQtNjYuNzY1LTEyNi43MDMtNjcuMTA3bC0zOS44LTI4LjY3Mi0zOS4xODUgMjguNDY4Yy0yLjA0OCAxLjUwMS00OS45MDMgMzYuMDQ0LTEyNi45MDggNjcuMzFhNDQ3LjQyIDQ0Ny40MiAwIDAgMS0xNDQuNTIgMzEuMzM1bC02NS44NzcgMC45NTZ2Mzc4Ljg4YzAgODcuMDQgNDkuODM0IDE4NC42NjEgMTM3LjAxIDI2Ny44MSAzNy41NDcgMzUuODQgNzkuMjU4IDY2LjM1NSAxMjAuODMzIDg4LjIgNDMuMjggMjIuNzMzIDg0LjI0IDM0LjYxMiAxMTguODUyIDM0LjYxMiAzNC40MDYgMCA3NS43NzYtMTIuMTUyIDExOS42MDMtMzUuMTU4YTU0Ny45NzcgNTQ3Ljk3NyAwIDAgMCAxMjAuMDEzLTg3LjY1NCA1MTUuMjA5IDUxNS4yMDkgMCAwIDAgOTYuMTg4LTEyMi44OGMyNy4xMDItNDkuNTYyIDQwLjgyMy05OC4zMDQgNDAuODIzLTE0NC45OTlsLTAuMTM2LTM0Ny4yMDR6TTUxMC4wOSAxNDMuNDI4bDEuNzA2LTEuMzY1IDEuNzc1IDEuMzY1YzUuODAzIDQuMTY1IDU5LjUyOSA0MS44NDggMTQwLjM1NiA3NC43NTIgNzkuMTkgMzIuMDg2IDE1My42IDM1LjYzNSAxNjcuNjYzIDM2LjA0NWwyLjU5NCAwLjA2OCAwLjIwNSAzMTUuNzM0YzAuMTM3IDY5LjQ5NS00Mi41OTggMTUwLjE4Ni0xMTcuMDc3IDIyMS40NTdDNjQxLjU3IDg1NC4yODkgNTYzLjEzIDg5Ni40NzggNTEyIDg5Ni40NzhjLTIzLjY4OSAwLTU1LjU3LTkuODk5LTg5LjcwMi0yNy43ODVhNDc4LjgyMiA0NzguODIyIDAgMCAxLTEwNS42MDktNzcuMjc4QzI0Mi4yMSA3MjAuMjEzIDE5OS40NzUgNjM5LjUyMiAxOTkuNDc1IDU2OS44OVYyNTQuMjI1bDIuNzMtMC4xMzZjMy4yNzggMCA4Mi42MDQtMS41MDIgMTY3LjY2NC0zNS45NzdhNzM5Ljk0MiA3MzkuOTQyIDAgMCAwIDE0MC4yMi03NC42MTV2LTAuMDY5eiIgIC8+PHBhdGggZD0iTTcxMy4zMTggMzY4LjY0YTMyLjIyMiAzMi4yMjIgMCAwIDAtNDUuMzI5IDBMNDQ5LjE5NSA1ODcuNDM1bC05My4xODQtOTMuMTE2YTMyLjIyMiAzMi4yMjIgMCAwIDAtNDUuMzMgMCAzMi4yMjIgMzIuMjIyIDAgMCAwIDAgNDUuMjZsMTE1Ljg1IDExNS44NWEzMi4yOSAzMi4yOSAwIDAgMCA0NS4zMjggMEw3MTMuMzIgNDEzLjlhMzIuMjIyIDMyLjIyMiAwIDAgMCAwLTQ1LjMzeiIgIC8+PC9zdmc+)](https://github.com/Kentico/.github/blob/main/SUPPORT.md#full-support) [![CI: Build and Test](https://github.com/Kentico/xperience-by-kentico-shopify/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/Kentico/xperience-by-kentico-shopify/actions/workflows/ci.yml) -| Name | Package | -|----------------------------------- | --------------- | -| Kentico.Xperience.Shopify | [![NuGet Package](https://img.shields.io/nuget/v/Kentico.Xperience.Shopify.svg)](https://www.nuget.org/packages/Kentico.Xperience.Shopify) | -| Kentico.Xperience.Shopify.Rcl | [![NuGet Package](https://img.shields.io/nuget/v/Kentico.Xperience.Shopify.Rcl.svg)](https://www.nuget.org/packages/Kentico.Xperience.Shopify.Rcl)| +| Name | Package | +| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| Kentico.Xperience.Shopify | [![NuGet Package](https://img.shields.io/nuget/v/Kentico.Xperience.Shopify.svg)](https://www.nuget.org/packages/Kentico.Xperience.Shopify) | +| Kentico.Xperience.Shopify.Rcl | [![NuGet Package](https://img.shields.io/nuget/v/Kentico.Xperience.Shopify.Rcl.svg)](https://www.nuget.org/packages/Kentico.Xperience.Shopify.Rcl) | ## Description + This integration connects your Shopify store with the Xperience by Kentico application using [Shopify Storefront](https://shopify.dev/docs/api/storefront) and [Shopify Admin](https://shopify.dev/docs/api/admin) APIs. It provides synchronization of products and e-commerce actions between the two platforms. Implemented features provide users with the options to: + - Add, update and remove products in the shopping cart. - Manage discount coupons. - Proceed to checkout directly on the Shopify store page. ### Limitations + **Shopify API can return maximum of 250 items in one API request**. For larger number of products, pagination needs to be implemented. More info can be found in the [Usage-Guide.md](./docs/Usage-Guide.md#limitations). ## Screenshots @@ -29,99 +32,145 @@ This integration connects your Shopify store with the Xperience by Kentico appli Summary of libraries (NuGet packages) used by this integration and their Xperience by Kentico version requirements. To use this integration, your Xperience project must be upgraded to at least the highest version listed. | Library | Xperience by Kentico Version | Library Version | -|----------------------------------- |------------------------------| --------------- | +| ---------------------------------- | ---------------------------- | --------------- | | Kentico.Xperience.Ecommerce.Common | \>= 29.0.1 | 1.0.0 | | Kentico.Xperience.Shopify | \>= 29.0.2 | 1.0.0 | | Kentico.Xperience.Shopify.Rcl | \>= 29.0.2 | 1.0.0 | ### Dependencies + - [ASP.NET Core 8.0](https://dotnet.microsoft.com/en-us/download) - [Xperience by Kentico](https://docs.kentico.com/changelog) ## Quick Start + 1. Generate Shopify API access tokens (see [Generating Shopify API credentials](./docs/Usage-Guide.md#generating-shopify-api-credentials) for details). - - [Install](https://shopify.dev/docs/custom-storefronts/building-with-the-storefront-api/getting-started) the [Headless channel](https://shopify.dev/docs/custom-storefronts/getting-started/build-options#the-headless-channel) into your Shopify admin. During the installation, select **Create storefront** and [generate a private Storefront API token](https://shopify.dev/docs/api/usage/authentication#getting-started-with-private-access). - - Install a [custom application](https://help.shopify.com/en/manual/apps/app-types/custom-apps#create-and-install-a-custom-app) into your Shopify admin and generate a Shopify Admin API access token. Set following access scopes: `write_product_listings`, `read_product_listings`, `write_products`, `read_products`, `read_inventory`, `write_orders`, `read_orders`. + + - [Install](https://shopify.dev/docs/custom-storefronts/building-with-the-storefront-api/getting-started) the [Headless channel](https://shopify.dev/docs/custom-storefronts/getting-started/build-options#the-headless-channel) into your Shopify admin. During the installation, select **Create storefront** and [generate a private Storefront API token](https://shopify.dev/docs/api/usage/authentication#getting-started-with-private-access). + - Install a [custom application](https://help.shopify.com/en/manual/apps/app-types/custom-apps#create-and-install-a-custom-app) into your Shopify admin and generate a Shopify Admin API access token. Set following access scopes: `write_product_listings`, `read_product_listings`, `write_products`, `read_products`, `read_inventory`, `write_orders`, `read_orders`. 2. Add these packages to your Xperience by Kentico application using the .NET CLI. - ```powershell - dotnet add package Kentico.Xperience.Shopify - dotnet add package Kentico.Xperience.Shopify.Rcl - ``` + + ```powershell + dotnet add package Kentico.Xperience.Shopify + dotnet add package Kentico.Xperience.Shopify.Rcl + ``` 3. Configure your Xperience application to connect to your Shopify instance via the `CMSShopifyConfig` object. For development purposes, you can also configure these settings directly in the Xperience by Kentico administration via the **Shopify integration** application. - ```json - { - "CMSShopifyConfig": { - "ShopifyUrl": "https://your-shopify-store-url.com/", - "AdminApiToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "StorefrontApiToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - "StorefrontApiVersion": "YYYY-MM" - } - } - ``` - **Setting description** - | Setting | Description | - | -------------------- | ------------------------------------------------------------------------------------- | - | ShopifyUrl | URL of the Shopify store | - | AdminApiToken | Access token for the Admin API calls | - | StorefrontApiToken | Access token for the Storefront API calls | - | StorefrontApiVersion | Storefront API version that will be used in API calls. Must use the format: `YYYY-MM` | - - Note: The `StorefrontApiVersion` refers to the version of the Shopify Storefront API you are using. You can find the available versions and their release dates in the [Shopify API versioning documentation](https://shopify.dev/docs/api/usage/versioning). - - You can also configure the integration via the [Shopify integration](#shopify-configuration-module) application in the Xperience admin UI. However, note that this approach should only be used for development purposes. For the production, use one of the recommended [configuration methods](https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration). - -4. Add services provided by the integration to the service container. - ```csharp - // Program.cs - - // Registers Shopify services - builder.Services.RegisterShopifyServices(builder.Configuration); - ``` - -5. Enable session state for the application. - ```csharp - // Program.cs - - // Enable session state for appliation - app.UseSession(); - ``` - -6. Add Xperience objects used by the integration to your project using [Continuous Integration](https://docs.kentico.com/x/FAKQC). The integration's CI files are located under `.\examples\DancingGoat-Shopify\App_Data\CIRepository\`. This CI repository does not contain any other objects than objects related to Shopify integration so it can be merged into your existing CI repository. Copy these files to your Continuous Integration repository and run: - ```powershell - dotnet run --no-build --kxp-ci-restore - ``` + + ```json + { + "CMSShopifyConfig": { + "ShopifyUrl": "https://your-shopify-store-url.com/", + "AdminApiToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "StorefrontApiToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "StorefrontApiVersion": "YYYY-MM" + } + } + ``` + + **Setting description** + | Setting | Description | + | -------------------- | ------------------------------------------------------------------------------------- | + | ShopifyUrl | URL of the Shopify store | + | AdminApiToken | Access token for the Admin API calls | + | StorefrontApiToken | Access token for the Storefront API calls | + | StorefrontApiVersion | Storefront API version that will be used in API calls. Must use the format: `YYYY-MM` | + + Note: The `StorefrontApiVersion` refers to the version of the Shopify Storefront API you are using. You can find the available versions and their release dates in the [Shopify API versioning documentation](https://shopify.dev/docs/api/usage/versioning). + + You can also configure the integration via the [Shopify integration](#shopify-configuration-module) application in the Xperience admin UI. However, note that this approach should only be used for development purposes. For the production, use one of the recommended [configuration methods](https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration). + +4. Configure currency and country code for website channels via the `ShopifyWebsiteChannelsConfig` object. + + ```json + { + "ShopifyWebsiteChannelsConfig": { + "Settings": [ + { + "ChannelName": "MyWebsiteChannel1", + "CurrencyCode": "CZK", + "Country": "CZ" + }, + { + "ChannelName": "MyWebsiteChannel2", + "CurrencyCode": "USD", + "Country": "US" + } + ], + "DefaultSetting": { + "CurrencyCode": "CZK", + "Country": "CZ" + } + } + } + ``` + + **Setting description** + | Setting | Description | | | | + |----------------|----------------------------------------------------------------------------------------------------------------|---|---|---| + | Settings | List of configurations. Each item contains configuration for specific website channel defined by `ChannelName` | | | | + | DefaultSetting | Configuration used if no configuration found in the `Settings` for the given website channel. | | | | + + Note: Only currencies set in the shopify store can be used. + +5. Add services provided by the integration to the service container. + + ```csharp + // Program.cs + + // Registers Shopify services + builder.Services.RegisterShopifyServices(builder.Configuration); + ``` + +6. Enable session state for the application. + + ```csharp + // Program.cs + + // Enable session state for appliation + app.UseSession(); + ``` + +7. Add Xperience objects used by the integration to your project using [Continuous Integration](https://docs.kentico.com/x/FAKQC). The integration's CI files are located under `.\examples\DancingGoat-Shopify\App_Data\CIRepository\`. This CI repository does not contain any other objects than objects related to Shopify integration so it can be merged into your existing CI repository. Copy these files to your Continuous Integration repository and run: + + ```powershell + dotnet run --no-build --kxp-ci-restore + ``` The command restores the following objects: - Page content types: Thank you page, Shopping cart page, Shopify product detail page, Store page, Shopify category page(more info in [Usage-Guide.md](./docs/Usage-Guide.md#e-commerce-content-types). - Content types used for [synchronization with Shopify](./docs/Usage-Guide.md#shopify-products-synchronization): Shopify product, Shopify Product Variant, Shopify Image(more info in [Usage-Guide.md](./docs/Usage-Guide.md#e-commerce-content-types). - [Shopify integration module](#shopify-configuration-module) for setting API credentials and adding currency codes. - Custom activities: Product added to shopping cart, Product removed from shopping cart, Purchase, Purchased product. - Using CD is not recommended for restoring `Product detail`, `Category`, `Store` pages and content items that were created by [synchronization with Shopify](./docs/Usage-Guide.md#shopify-products-synchronization). This is because the `Shopify product` content item is connected to Product detail page. Therefore, both Product detail page and Shopify product content item will be restored. However, the synchronization already created the same Shopify product content item, using CD restore will result in duplicate Shopify product content items. To filter these objects from continuous deployment, add following rule into `repository.config`: - ```xml - - - Shopify.Image;Shopify.StorePage;Shopify.Product;Shopify.ProductDetailPage;Shopify.ProductVariant;Shopify.CategoryPage - - ``` + Using CD is not recommended for restoring `Product detail`, `Category`, `Store` pages and content items that were created by [synchronization with Shopify](./docs/Usage-Guide.md#shopify-products-synchronization). This is because the `Shopify product` content item is connected to Product detail page. Therefore, both Product detail page and Shopify product content item will be restored. However, the synchronization already created the same Shopify product content item, using CD restore will result in duplicate Shopify product content items. To filter these objects from continuous deployment, add following rule into `repository.config`: + + ```xml + + + Shopify.Image;Shopify.StorePage;Shopify.Product;Shopify.ProductDetailPage;Shopify.ProductVariant;Shopify.CategoryPage + + ``` -7. The integration uses a **Products** [Page Builder](https://docs.kentico.com/x/6QWiCQ) widget to display products from your Shopify store. Since Page Builder widgets cannot be distributed as part of NuGet packages, you must copy the **Products** widget implementation from the example project in this repository to your project. The widget implementation is located in [this folder](./examples/DancingGoat-Shopify/Components/Widgets/Shopify/ProductListWidget). +8. The integration uses a **Products** [Page Builder](https://docs.kentico.com/x/6QWiCQ) widget to display products from your Shopify store. Since Page Builder widgets cannot be distributed as part of NuGet packages, you must copy the **Products** widget implementation from the example project in this repository to your project. The widget implementation is located in [this folder](./examples/DancingGoat-Shopify/Components/Widgets/Shopify/ProductListWidget). -8. Run your Xperience application (e.g., `using dotnet run`). +9. Run your Xperience application (e.g., `using dotnet run`). -9. In the Xperience admin UI, open the [Shopify integration](#shopify-configuration-module) application and on the **Shopify currencies formats** tab and add the required currencies for your store. For currency codes, use the values from [CurrencyCodeEnum](https://shopify.dev/docs/api/storefront/2024-01/enums/CurrencyCode). To format the output, we recommend using [custom numeric format strings](https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings). +10. In the Xperience admin UI, open the [Shopify integration](#shopify-configuration-module) application and on the **Shopify currencies formats** tab and add the required currencies for your store. For currency codes, use the values from [CurrencyCodeEnum](https://shopify.dev/docs/api/storefront/2024-01/enums/CurrencyCode). To format the output, we recommend using [custom numeric format strings](https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings). ## Full Instructions View the [Usage Guide](./docs/Usage-Guide.md) for more detailed instructions. ## Shopify configuration module + The integration adds a new **Shopify integration** application to the admin UI. Using the application, administrators can set Shopify API credentials and add currency formats. If Shopify API credentials are provided both via this application and configuration providers (e.g., appsettings.json), values from the configuration will take precedence. The application is located under the `Configuration` category. ![Shopify integration module overview](./images/screenshots/shopify_integration_module.jpg "Shopify integration module overview") ## Codebase overview + Repository contains solution with Xperience by Kentico integration to Shopify. It shows the connection to the Shopify headless API and shows the implementation of a simple e-shop on Xperience by Kentico (extended Dancing Goat sample site). The solution consists of these parts: + - Kentico.Xperience.Shopify - class library that contains all services necessary for this integration. - Kentico.Xperience.Shopify.Rcl - razor class library for selector components(used in standalone product listing widget). - DancingGoat - Sample Dancing Goat site. From db8d50a40bd833646fa75cb6bdf0cc447965913d Mon Sep 17 00:00:00 2001 From: Martin Kyjac Date: Mon, 29 Jul 2024 10:36:40 +0200 Subject: [PATCH 5/9] Update readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 419919d..d179f2f 100644 --- a/README.md +++ b/README.md @@ -107,10 +107,10 @@ Summary of libraries (NuGet packages) used by this integration and their Xperien ``` **Setting description** - | Setting | Description | | | | - |----------------|----------------------------------------------------------------------------------------------------------------|---|---|---| - | Settings | List of configurations. Each item contains configuration for specific website channel defined by `ChannelName` | | | | - | DefaultSetting | Configuration used if no configuration found in the `Settings` for the given website channel. | | | | + | Setting | Description | + |----------------|----------------------------------------------------------------------------------------------------------------| + | Settings | List of configurations. Each item contains configuration for specific website channel defined by `ChannelName` | + | DefaultSetting | Configuration used if no configuration found in the `Settings` for the given website channel. | Note: Only currencies set in the shopify store can be used. From 8a0de8f843a3dfab1ebed6d496ec8501301f8233 Mon Sep 17 00:00:00 2001 From: Martin Kyjac Date: Mon, 29 Jul 2024 10:39:22 +0200 Subject: [PATCH 6/9] Add currency limitations --- README.md | 4 +++- docs/Usage-Guide.md | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d179f2f..d72f8a5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,9 @@ This integration connects your Shopify store with the Xperience by Kentico appli ### Limitations -**Shopify API can return maximum of 250 items in one API request**. For larger number of products, pagination needs to be implemented. More info can be found in the [Usage-Guide.md](./docs/Usage-Guide.md#limitations). +- **Shopify API can return maximum of 250 items in one API request**. For larger number of products, pagination needs to be implemented. More info can be found in the [Usage-Guide.md](./docs/Usage-Guide.md#limitations). + +- Only one currency pre website channel is supported. ## Screenshots diff --git a/docs/Usage-Guide.md b/docs/Usage-Guide.md index 2e5bd80..9a6342e 100644 --- a/docs/Usage-Guide.md +++ b/docs/Usage-Guide.md @@ -11,7 +11,8 @@ Class library consists of 2 main parts - Shopify products synchronization and th The **Product listing** widget is a standalone widget that displays Shopify products using the Shopify Admin REST API. The Shopify integration must be set up in `appsettings.json` or in the `Shopify integration` application for this widget to work (no products need to be synchronized). The widget can be used to display products from your Shopify store outside of the [content type structure](#integration-specific-content-types) the integration provides. #### Limitations -Shopify API can return maximum of 250 items in one API request. For larger number of products, pagination needs to be implemented. Detailed information on Shopify API pagination can be found [here](https://shopify.dev/docs/api/usage/pagination-rest). The `ShopifyProductService` wraps result in [ListResultWrapper](../src/Kentico.Xperience.Shopify/Products/Models/ListResultWrapper.cs). This wrapper returns retrieved items along with next page and previous page [PagingFilterParams](../src/Kentico.Xperience.Shopify/Products/Models/PagingFilterParams.cs). These filters can then be used in the `GetProductsAsync` method parameters to retrieve next or previous page from Shopify API. Due to this limitation, the maximum number of retrieved results in the [product listing widget](#product-listing-widget) is 250. To increase this limit, pagination must be implemented in the widget. This limitation also affects product synchronization, where only first 250 products are synchronized, and shopping cart, which can have maximum of 250 cart items. +- Shopify API can return maximum of 250 items in one API request. For larger number of products, pagination needs to be implemented. Detailed information on Shopify API pagination can be found [here](https://shopify.dev/docs/api/usage/pagination-rest). The `ShopifyProductService` wraps result in [ListResultWrapper](../src/Kentico.Xperience.Shopify/Products/Models/ListResultWrapper.cs). This wrapper returns retrieved items along with next page and previous page [PagingFilterParams](../src/Kentico.Xperience.Shopify/Products/Models/PagingFilterParams.cs). These filters can then be used in the `GetProductsAsync` method parameters to retrieve next or previous page from Shopify API. Due to this limitation, the maximum number of retrieved results in the [product listing widget](#product-listing-widget) is 250. To increase this limit, pagination must be implemented in the widget. This limitation also affects product synchronization, where only first 250 products are synchronized, and shopping cart, which can have maximum of 250 cart items. +- Only one currency pre website channel is supported. ### Shopify products synchronization Synchronization running in background thread worker periodically every 15 minutes and all synchronization items are stored as following content items: From 815d80e78c2f1ad265c0fbb87dbd81754c9e4ab7 Mon Sep 17 00:00:00 2001 From: Martin Kyjac Date: Thu, 1 Aug 2024 09:44:14 +0200 Subject: [PATCH 7/9] Fix typo --- docs/Usage-Guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Usage-Guide.md b/docs/Usage-Guide.md index 9a6342e..9764a97 100644 --- a/docs/Usage-Guide.md +++ b/docs/Usage-Guide.md @@ -12,7 +12,7 @@ The **Product listing** widget is a standalone widget that displays Shopify prod #### Limitations - Shopify API can return maximum of 250 items in one API request. For larger number of products, pagination needs to be implemented. Detailed information on Shopify API pagination can be found [here](https://shopify.dev/docs/api/usage/pagination-rest). The `ShopifyProductService` wraps result in [ListResultWrapper](../src/Kentico.Xperience.Shopify/Products/Models/ListResultWrapper.cs). This wrapper returns retrieved items along with next page and previous page [PagingFilterParams](../src/Kentico.Xperience.Shopify/Products/Models/PagingFilterParams.cs). These filters can then be used in the `GetProductsAsync` method parameters to retrieve next or previous page from Shopify API. Due to this limitation, the maximum number of retrieved results in the [product listing widget](#product-listing-widget) is 250. To increase this limit, pagination must be implemented in the widget. This limitation also affects product synchronization, where only first 250 products are synchronized, and shopping cart, which can have maximum of 250 cart items. -- Only one currency pre website channel is supported. +- Only one currency per website channel is supported. ### Shopify products synchronization Synchronization running in background thread worker periodically every 15 minutes and all synchronization items are stored as following content items: From 602c1619e231d326712c38a2072509efacd19e79 Mon Sep 17 00:00:00 2001 From: Martin Kyjac Date: Thu, 1 Aug 2024 09:46:39 +0200 Subject: [PATCH 8/9] Change name ShopifyWebsiteChannelsConfig to CMSShopifyWebsiteChannelsConfig --- README.md | 4 ++-- examples/DancingGoat-Shopify/appsettings.json | 2 +- .../Config/ShopifyWebsiteChannelConfigOptions.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d72f8a5..0003a14 100644 --- a/README.md +++ b/README.md @@ -83,11 +83,11 @@ Summary of libraries (NuGet packages) used by this integration and their Xperien You can also configure the integration via the [Shopify integration](#shopify-configuration-module) application in the Xperience admin UI. However, note that this approach should only be used for development purposes. For the production, use one of the recommended [configuration methods](https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration). -4. Configure currency and country code for website channels via the `ShopifyWebsiteChannelsConfig` object. +4. Configure currency and country code for website channels via the `CMSShopifyWebsiteChannelsConfig` object. ```json { - "ShopifyWebsiteChannelsConfig": { + "CMSShopifyWebsiteChannelsConfig": { "Settings": [ { "ChannelName": "MyWebsiteChannel1", diff --git a/examples/DancingGoat-Shopify/appsettings.json b/examples/DancingGoat-Shopify/appsettings.json index a5468e0..664bc3b 100644 --- a/examples/DancingGoat-Shopify/appsettings.json +++ b/examples/DancingGoat-Shopify/appsettings.json @@ -20,7 +20,7 @@ "StorefrontApiToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "StorefrontApiVersion": "YYYY-MM" }, - "ShopifyWebsiteChannelsConfig": { + "CMSShopifyWebsiteChannelsConfig": { "Settings": [ { "ChannelName": "MyWebsiteChannel1", diff --git a/src/Kentico.Xperience.Shopify/Config/ShopifyWebsiteChannelConfigOptions.cs b/src/Kentico.Xperience.Shopify/Config/ShopifyWebsiteChannelConfigOptions.cs index 9d93ddc..3a5a25a 100644 --- a/src/Kentico.Xperience.Shopify/Config/ShopifyWebsiteChannelConfigOptions.cs +++ b/src/Kentico.Xperience.Shopify/Config/ShopifyWebsiteChannelConfigOptions.cs @@ -8,7 +8,7 @@ public class ShopifyWebsiteChannelConfigOptions /// /// The name of configuration section. /// - public const string SECTION_NAME = "ShopifyWebsiteChannelsConfig"; + public const string SECTION_NAME = "CMSShopifyWebsiteChannelsConfig"; /// /// Website channel configurations list. From 16ff840a8e4c8acb1c0d55c6ff52913d5c9a40f8 Mon Sep 17 00:00:00 2001 From: Martin Kyjac Date: Thu, 1 Aug 2024 09:54:16 +0200 Subject: [PATCH 9/9] Use option monitor's current value in constructor --- .../ShopifyIntegrationSettingsService.cs | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/Kentico.Xperience.Shopify/Config/ShopifyIntegrationSettingsService.cs b/src/Kentico.Xperience.Shopify/Config/ShopifyIntegrationSettingsService.cs index 52e0138..c80ad6e 100644 --- a/src/Kentico.Xperience.Shopify/Config/ShopifyIntegrationSettingsService.cs +++ b/src/Kentico.Xperience.Shopify/Config/ShopifyIntegrationSettingsService.cs @@ -1,6 +1,4 @@ -using System.Diagnostics; - -using CMS.DataEngine; +using CMS.DataEngine; using CMS.Helpers; using CMS.Websites.Routing; @@ -13,9 +11,9 @@ namespace Kentico.Xperience.Shopify.Config internal class ShopifyIntegrationSettingsService : IShopifyIntegrationSettingsService { private readonly IProgressiveCache cache; - private readonly IOptionsMonitor shopifyConfigMonitor; + private readonly ShopifyConfig shopifyConfig; private readonly IInfoProvider integrationSettingsProvider; - private readonly IOptionsMonitor websiteChannelConfigMonitor; + private readonly ShopifyWebsiteChannelConfigOptions websiteChannelConfig; private readonly IWebsiteChannelContext websiteChannelContext; public ShopifyIntegrationSettingsService( @@ -26,19 +24,17 @@ public ShopifyIntegrationSettingsService( IWebsiteChannelContext websiteChannelContext) { this.cache = cache; - this.shopifyConfigMonitor = shopifyConfigMonitor; this.integrationSettingsProvider = integrationSettingsProvider; - this.websiteChannelConfigMonitor = websiteChannelConfigMonitor; this.websiteChannelContext = websiteChannelContext; + shopifyConfig = shopifyConfigMonitor.CurrentValue; + websiteChannelConfig = websiteChannelConfigMonitor.CurrentValue; } public ShopifyConfig? GetSettings() { - var monitorValue = shopifyConfigMonitor.CurrentValue; - - if (ShopifyConfigIsFilled(monitorValue)) + if (ShopifyConfigIsFilled(shopifyConfig)) { - return monitorValue; + return shopifyConfig; } return cache.Load(cs => GetConfigFromSettings(), @@ -51,9 +47,7 @@ public ShopifyIntegrationSettingsService( public ShopifyWebsiteChannelConfig? GetWebsiteChannelSettings() { - var monitorValue = websiteChannelConfigMonitor.CurrentValue; - - if (monitorValue == null) + if (websiteChannelConfig == null) { return null; } @@ -61,9 +55,9 @@ public ShopifyIntegrationSettingsService( string? currentChannel = websiteChannelContext.WebsiteChannelName; if (string.IsNullOrEmpty(currentChannel)) { - return monitorValue.DefaultSetting; + return websiteChannelConfig.DefaultSetting; } - return monitorValue.Settings?.Find(x => x.ChannelName == currentChannel) ?? monitorValue.DefaultSetting; + return websiteChannelConfig.Settings?.Find(x => x.ChannelName == currentChannel) ?? websiteChannelConfig.DefaultSetting; } private ShopifyConfig? GetConfigFromSettings()