@HtmlLocalizer["Qty"]
-
+
diff --git a/examples/DancingGoat-Shopify/appsettings.json b/examples/DancingGoat-Shopify/appsettings.json
index b3e6674..67e5137 100644
--- a/examples/DancingGoat-Shopify/appsettings.json
+++ b/examples/DancingGoat-Shopify/appsettings.json
@@ -13,5 +13,11 @@
}
},
"AllowedHosts": "*",
- "CMSHashStringSalt": "9f38667d-c99f-43bb-9a92-043ce36ecb5d"
+ "CMSHashStringSalt": "9f38667d-c99f-43bb-9a92-043ce36ecb5d",
+ "CMSShopifyConfig": {
+ "ShopifyUrl": "https://your-shopify-store-url.com/",
+ "AdminApiToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "StorefrontApiToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "StorefrontApiVersion": "YYYY-MM"
+ }
}
\ No newline at end of file
diff --git a/examples/DancingGoat-Shopify/packages.lock.json b/examples/DancingGoat-Shopify/packages.lock.json
index bf120b3..1210aaa 100644
--- a/examples/DancingGoat-Shopify/packages.lock.json
+++ b/examples/DancingGoat-Shopify/packages.lock.json
@@ -55,9 +55,9 @@
},
"ShopifySharp": {
"type": "Direct",
- "requested": "[6.13.0, )",
- "resolved": "6.13.0",
- "contentHash": "GwM1K0FzTo9BrXSqG9hWxIOGGfhrbBfLXK+b6vMqaabmw0Uv4/n9gskQKSZ9CIB7vJksI4Grn+Tk9qW/dVqBJQ==",
+ "requested": "[6.17.0, )",
+ "resolved": "6.17.0",
+ "contentHash": "0yvFrbdvCaQyWHOXsd5Lp15mPpJDQ0G+PE8vJVRkMiH39nrEemI1i2UYrFTS8axgNndGJzB+E1XzctwELp7uug==",
"dependencies": {
"Microsoft.Extensions.Http": "2.1.0",
"System.Text.Json": "7.0.3",
@@ -66,11 +66,11 @@
},
"ShopifySharp.Extensions.DependencyInjection": {
"type": "Direct",
- "requested": "[1.4.0, )",
- "resolved": "1.4.0",
- "contentHash": "jpaU4WpqjAXm/Tm7vQwC/gBbsdIHa4taO/aAN6BZxT8t91f9+OHkWZzFOsvChVUNn61cShcTR84tUfrv6X2cVg==",
+ "requested": "[1.6.0, )",
+ "resolved": "1.6.0",
+ "contentHash": "Hf0MexgLD6FbTcm81lFMPsw5kWK4XNEl7FPmYuoE9kPlWhWwR/wUWMYUpn6uSBTHCE+62yI2l+i7lsOb0zASnA==",
"dependencies": {
- "ShopifySharp": "6.13.0",
+ "ShopifySharp": "6.16.0",
"microsoft.extensions.dependencyinjection": "8.0.0"
}
},
@@ -1015,8 +1015,8 @@
"Kentico.Xperience.Admin": "[29.0.2, )",
"Kentico.Xperience.Core": "[29.0.2, )",
"Kentico.Xperience.Ecommerce.Common": "[1.0.0-prerelease-1, )",
- "ShopifySharp": "[6.13.0, )",
- "ShopifySharp.Extensions.DependencyInjection": "[1.4.0, )",
+ "ShopifySharp": "[6.17.0, )",
+ "ShopifySharp.Extensions.DependencyInjection": "[1.6.0, )",
"System.Configuration.ConfigurationManager": "[8.0.0, )",
"System.Linq.Async": "[6.0.1, )"
}
@@ -1024,7 +1024,7 @@
"kentico.xperience.shopify.rcl": {
"type": "Project",
"dependencies": {
- "Kentico.Xperience.Shopify": "[1.0.0-prerelease-1, )"
+ "Kentico.Xperience.Shopify": "[0.1.0-prerelease-1, )"
}
},
"GraphQL": {
diff --git a/src/Kentico.Xperience.Shopify.Rcl/packages.lock.json b/src/Kentico.Xperience.Shopify.Rcl/packages.lock.json
index c5002eb..e69a230 100644
--- a/src/Kentico.Xperience.Shopify.Rcl/packages.lock.json
+++ b/src/Kentico.Xperience.Shopify.Rcl/packages.lock.json
@@ -885,8 +885,8 @@
"Kentico.Xperience.Admin": "[29.0.2, )",
"Kentico.Xperience.Core": "[29.0.2, )",
"Kentico.Xperience.Ecommerce.Common": "[1.0.0-prerelease-1, )",
- "ShopifySharp": "[6.13.0, )",
- "ShopifySharp.Extensions.DependencyInjection": "[1.4.0, )",
+ "ShopifySharp": "[6.17.0, )",
+ "ShopifySharp.Extensions.DependencyInjection": "[1.6.0, )",
"System.Configuration.ConfigurationManager": "[8.0.0, )",
"System.Linq.Async": "[6.0.1, )"
}
@@ -974,9 +974,9 @@
},
"ShopifySharp": {
"type": "CentralTransitive",
- "requested": "[6.13.0, )",
- "resolved": "6.13.0",
- "contentHash": "GwM1K0FzTo9BrXSqG9hWxIOGGfhrbBfLXK+b6vMqaabmw0Uv4/n9gskQKSZ9CIB7vJksI4Grn+Tk9qW/dVqBJQ==",
+ "requested": "[6.17.0, )",
+ "resolved": "6.17.0",
+ "contentHash": "0yvFrbdvCaQyWHOXsd5Lp15mPpJDQ0G+PE8vJVRkMiH39nrEemI1i2UYrFTS8axgNndGJzB+E1XzctwELp7uug==",
"dependencies": {
"Microsoft.Extensions.Http": "2.1.0",
"System.Text.Json": "7.0.3",
@@ -985,11 +985,11 @@
},
"ShopifySharp.Extensions.DependencyInjection": {
"type": "CentralTransitive",
- "requested": "[1.4.0, )",
- "resolved": "1.4.0",
- "contentHash": "jpaU4WpqjAXm/Tm7vQwC/gBbsdIHa4taO/aAN6BZxT8t91f9+OHkWZzFOsvChVUNn61cShcTR84tUfrv6X2cVg==",
+ "requested": "[1.6.0, )",
+ "resolved": "1.6.0",
+ "contentHash": "Hf0MexgLD6FbTcm81lFMPsw5kWK4XNEl7FPmYuoE9kPlWhWwR/wUWMYUpn6uSBTHCE+62yI2l+i7lsOb0zASnA==",
"dependencies": {
- "ShopifySharp": "6.13.0",
+ "ShopifySharp": "6.16.0",
"microsoft.extensions.dependencyinjection": "8.0.0"
}
},
diff --git a/src/Kentico.Xperience.Shopify/Orders/ShopifyOrderService.cs b/src/Kentico.Xperience.Shopify/Orders/ShopifyOrderService.cs
index 76d1656..3efc843 100644
--- a/src/Kentico.Xperience.Shopify/Orders/ShopifyOrderService.cs
+++ b/src/Kentico.Xperience.Shopify/Orders/ShopifyOrderService.cs
@@ -37,7 +37,7 @@ public ShopifyOrderService(
var result = await orderService.ListAsync(filter);
- return result.Items.FirstOrDefault(x => x.SourceIdentifier == sourceId);
+ return result.Items.FirstOrDefault(x => x.SourceIdentifier.Equals(sourceId, StringComparison.Ordinal));
}
}
}
diff --git a/src/Kentico.Xperience.Shopify/Products/ShopifyPriceService.cs b/src/Kentico.Xperience.Shopify/Products/ShopifyPriceService.cs
index 69e9eee..664b36e 100644
--- a/src/Kentico.Xperience.Shopify/Products/ShopifyPriceService.cs
+++ b/src/Kentico.Xperience.Shopify/Products/ShopifyPriceService.cs
@@ -45,7 +45,7 @@ private async Task
> GetProductsPriceInter
continue;
}
- var prices = product.Variants.Select(x => x.PresentmentPrices.FirstOrDefault(x => x.Price.CurrencyCode == currency));
+ var prices = product.Variants.Select(x => x.PresentmentPrices.FirstOrDefault(x => x.Price.CurrencyCode.Equals(currency, 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 c9146b1..b4cc668 100644
--- a/src/Kentico.Xperience.Shopify/Products/ShopifyProductService.cs
+++ b/src/Kentico.Xperience.Shopify/Products/ShopifyProductService.cs
@@ -90,7 +90,7 @@ private async Task> GetProductVarian
{
foreach (var variant in variants.Values)
{
- var cartItem = cart.Items.FirstOrDefault(x => x.VariantGraphQLId == variant.MerchandiseID);
+ var cartItem = cart.Items.FirstOrDefault(x => x.VariantGraphQLId.Equals(variant.MerchandiseID, StringComparison.Ordinal));
variant.ItemsInCart = cartItem?.Quantity ?? 0;
}
}
@@ -163,7 +163,7 @@ private ListResultWrapper CreateResultModel(ListResult<
if (variants.Count() == 1)
{
var onlyVariant = variants.First();
- var currencyPrice = onlyVariant.PresentmentPrices?.FirstOrDefault(x => x.Price.CurrencyCode == currency);
+ var currencyPrice = onlyVariant.PresentmentPrices?.FirstOrDefault(x => x.Price.CurrencyCode.Equals(currency, StringComparison.Ordinal));
return currencyPrice is { Price: not null } ?
(currencyPrice.Price.Amount, currencyPrice.CompareAtPrice?.Amount) : (null, null);
@@ -173,7 +173,7 @@ private ListResultWrapper CreateResultModel(ListResult<
foreach (var variant in variants)
{
- var currencyPrice = variant.PresentmentPrices?.FirstOrDefault(x => x.Price.CurrencyCode == currency);
+ var currencyPrice = variant.PresentmentPrices?.FirstOrDefault(x => x.Price.CurrencyCode.Equals(currency, StringComparison.Ordinal));
if (currencyPrice?.Price.Amount != null)
{
diff --git a/src/Kentico.Xperience.Shopify/ShopifyConstants.cs b/src/Kentico.Xperience.Shopify/ShopifyConstants.cs
index 9d9a8ce..1c0cf10 100644
--- a/src/Kentico.Xperience.Shopify/ShopifyConstants.cs
+++ b/src/Kentico.Xperience.Shopify/ShopifyConstants.cs
@@ -21,5 +21,11 @@ public static class ShopifyConstants
/// The default name for product variants.
///
public const string DEFAULT_VARIANT_NAME = "default title";
+
+
+ ///
+ /// The name of the header where buyer IP address should be added.
+ ///
+ public const string STOREFRONT_API_BUYER_IP_NAME = "Shopify-Storefront-Buyer-IP";
}
}
diff --git a/src/Kentico.Xperience.Shopify/ShoppingCart/GraphQLHttpClientFactory.cs b/src/Kentico.Xperience.Shopify/ShoppingCart/GraphQLHttpClientFactory.cs
index d730ffb..141615d 100644
--- a/src/Kentico.Xperience.Shopify/ShoppingCart/GraphQLHttpClientFactory.cs
+++ b/src/Kentico.Xperience.Shopify/ShoppingCart/GraphQLHttpClientFactory.cs
@@ -2,15 +2,23 @@
using GraphQL.Client.Http;
using GraphQL.Client.Serializer.Newtonsoft;
+using Microsoft.AspNetCore.Http;
+
namespace Kentico.Xperience.Shopify.ShoppingCart
{
internal class GraphQLHttpClientFactory : IGraphQLHttpClientFactory
{
private readonly HttpClient httpClient;
- public GraphQLHttpClientFactory(IHttpClientFactory httpClientFactory)
+ public GraphQLHttpClientFactory(IHttpClientFactory httpClientFactory, IHttpContextAccessor httpContextAccessor)
{
httpClient = httpClientFactory.CreateClient(ShopifyConstants.STOREFRONT_API_CLIENT_NAME);
+ string? buyerIP = httpContextAccessor.HttpContext?.Connection?.RemoteIpAddress?.ToString();
+
+ if (!string.IsNullOrEmpty(buyerIP))
+ {
+ httpClient.DefaultRequestHeaders.Add(ShopifyConstants.STOREFRONT_API_BUYER_IP_NAME, buyerIP);
+ }
}
///
diff --git a/src/Kentico.Xperience.Shopify/ShoppingCart/IShoppingService.cs b/src/Kentico.Xperience.Shopify/ShoppingCart/IShoppingService.cs
index 589cdfc..4cf6f1b 100644
--- a/src/Kentico.Xperience.Shopify/ShoppingCart/IShoppingService.cs
+++ b/src/Kentico.Xperience.Shopify/ShoppingCart/IShoppingService.cs
@@ -13,12 +13,20 @@ public interface IShoppingService
Task UpdateCartItem(ShoppingCartItemParameters parameters);
+ ///
+ /// Remove all shopping cart items of specific product variant.
+ ///
+ /// Product variant graphQL ID.
+ /// with updated shopping cart if operation was successful.
+ Task RemoveProductVariantFromCart(string variantGraphQLId);
+
+
///
/// Remove shopping cart item.
///
- /// Shopify product variant ID
+ /// Shopify shopping cart item ID
/// with updated shopping cart if operation was successful.
- Task RemoveCartItem(string merchandiseId);
+ Task RemoveCartItem(string cartItemId);
///
diff --git a/src/Kentico.Xperience.Shopify/ShoppingCart/Models/ShoppingCartItemParameters.cs b/src/Kentico.Xperience.Shopify/ShoppingCart/Models/ShoppingCartItemParameters.cs
index debe4b6..4e6fbf0 100644
--- a/src/Kentico.Xperience.Shopify/ShoppingCart/Models/ShoppingCartItemParameters.cs
+++ b/src/Kentico.Xperience.Shopify/ShoppingCart/Models/ShoppingCartItemParameters.cs
@@ -19,6 +19,13 @@ public class ShoppingCartItemParameters
public string MerchandiseID { get; set; } = string.Empty;
+ ///
+ /// ID of the shopping cart item if item was already in shopping cart.
+ /// If the cart item is new, the value will be empty string.
+ ///
+ public string ShoppingCartItemID { get; set; } = string.Empty;
+
+
///
/// Country code for the item.
///
diff --git a/src/Kentico.Xperience.Shopify/ShoppingCart/ShoppingService.cs b/src/Kentico.Xperience.Shopify/ShoppingCart/ShoppingService.cs
index 7bb3110..8b0a87d 100644
--- a/src/Kentico.Xperience.Shopify/ShoppingCart/ShoppingService.cs
+++ b/src/Kentico.Xperience.Shopify/ShoppingCart/ShoppingService.cs
@@ -36,11 +36,15 @@ public async Task UpdateCartItem(ShoppingCartItemParameters
{
var cart = await GetCurrentShoppingCart();
- var cartItemToUpdate = cart?.Items.FirstOrDefault(x => x.ShopifyCartItemId == parameters.MerchandiseID);
+ var cartItemToUpdate = cart?.Items.FirstOrDefault(x => x.ShopifyCartItemId.Equals(parameters.ShoppingCartItemID, StringComparison.Ordinal));
if (cart == null || cartItemToUpdate == null)
{
return await AddItemToCart(parameters);
}
+ if (cartItemToUpdate.Quantity == parameters.Quantity)
+ {
+ return new CartOperationResult(cart, true);
+ }
int quantity = Math.Max(parameters.Quantity, 0);
var result = await UpdateCartItemInternal(cart.CartId, cartItemToUpdate, quantity);
@@ -62,7 +66,38 @@ public async Task UpdateCartItem(ShoppingCartItemParameters
}
- public async Task RemoveCartItem(string merchandiseId)
+ public async Task RemoveProductVariantFromCart(string variantGraphQLId)
+ {
+ var cart = await GetCurrentShoppingCart();
+ if (cart == null)
+ {
+ return new CartOperationResult(null, true);
+ }
+
+ var shopifyCartLines = cart.Items.Where(x => x.VariantGraphQLId.Equals(variantGraphQLId, StringComparison.Ordinal));
+ bool success = true;
+ CartOperationResult? result = null;
+
+ foreach (var shopifyCartLine in shopifyCartLines)
+ {
+ result = await RemoveCartItem(shopifyCartLine.ShopifyCartItemId);
+ if (!result.Success)
+ {
+ success = false;
+ }
+ }
+
+ // There was no cart item with given variant graphQL ID
+ if (result == null)
+ {
+ return new CartOperationResult(cart, success);
+ }
+
+ return new CartOperationResult(result.Cart, success);
+ }
+
+
+ public async Task RemoveCartItem(string cartItemId)
{
var cart = await GetCurrentShoppingCart();
if (cart == null)
@@ -70,7 +105,7 @@ public async Task RemoveCartItem(string merchandiseId)
return new CartOperationResult(null, true);
}
- var shopifyCartLine = cart.Items.FirstOrDefault(x => x.VariantGraphQLId == merchandiseId);
+ var shopifyCartLine = cart.Items.FirstOrDefault(x => x.ShopifyCartItemId.Equals(cartItemId, StringComparison.Ordinal));
if (shopifyCartLine == null)
{
return new CartOperationResult(cart, true);
@@ -166,7 +201,7 @@ public async Task AddItemToCart(ShoppingCartItemParameters
StoreCartToCookiesAndSession(cart.CartId);
}
- var addedItem = cart.Items.FirstOrDefault(x => x.VariantGraphQLId == parameters.MerchandiseID);
+ var addedItem = cart.Items.FirstOrDefault(x => x.VariantGraphQLId.Equals(parameters.MerchandiseID));
activityLogger.LogProductAddedToShoppingCartActivity(addedItem, parameters.Quantity);
}
return result;
diff --git a/src/Kentico.Xperience.Shopify/Synchronization/SynchronizationServiceBase.cs b/src/Kentico.Xperience.Shopify/Synchronization/SynchronizationServiceBase.cs
index 3f06934..dade9d4 100644
--- a/src/Kentico.Xperience.Shopify/Synchronization/SynchronizationServiceBase.cs
+++ b/src/Kentico.Xperience.Shopify/Synchronization/SynchronizationServiceBase.cs
@@ -50,7 +50,7 @@ protected IEnumerable OrderItemsByShopify(IEnumerable co
{
foreach (var shopifyObject in shopifyObjects.Where(x => x.Id.HasValue))
{
- var contentItem = contentItems.FirstOrDefault(x => x.ShopifyObjectID == (shopifyObject.Id?.ToString() ?? string.Empty));
+ var contentItem = contentItems.FirstOrDefault(x => x.ShopifyObjectID.Equals(shopifyObject.Id?.ToString() ?? string.Empty, StringComparison.Ordinal));
yield return contentItem?.SystemFields.ContentItemGUID ?? Guid.Empty;
}
}
diff --git a/src/Kentico.Xperience.Shopify/packages.lock.json b/src/Kentico.Xperience.Shopify/packages.lock.json
index ca5310e..faca169 100644
--- a/src/Kentico.Xperience.Shopify/packages.lock.json
+++ b/src/Kentico.Xperience.Shopify/packages.lock.json
@@ -69,9 +69,9 @@
},
"ShopifySharp": {
"type": "Direct",
- "requested": "[6.13.0, )",
- "resolved": "6.13.0",
- "contentHash": "GwM1K0FzTo9BrXSqG9hWxIOGGfhrbBfLXK+b6vMqaabmw0Uv4/n9gskQKSZ9CIB7vJksI4Grn+Tk9qW/dVqBJQ==",
+ "requested": "[6.17.0, )",
+ "resolved": "6.17.0",
+ "contentHash": "0yvFrbdvCaQyWHOXsd5Lp15mPpJDQ0G+PE8vJVRkMiH39nrEemI1i2UYrFTS8axgNndGJzB+E1XzctwELp7uug==",
"dependencies": {
"Microsoft.Extensions.Http": "2.1.0",
"System.Text.Json": "7.0.3",
@@ -80,11 +80,11 @@
},
"ShopifySharp.Extensions.DependencyInjection": {
"type": "Direct",
- "requested": "[1.4.0, )",
- "resolved": "1.4.0",
- "contentHash": "jpaU4WpqjAXm/Tm7vQwC/gBbsdIHa4taO/aAN6BZxT8t91f9+OHkWZzFOsvChVUNn61cShcTR84tUfrv6X2cVg==",
+ "requested": "[1.6.0, )",
+ "resolved": "1.6.0",
+ "contentHash": "Hf0MexgLD6FbTcm81lFMPsw5kWK4XNEl7FPmYuoE9kPlWhWwR/wUWMYUpn6uSBTHCE+62yI2l+i7lsOb0zASnA==",
"dependencies": {
- "ShopifySharp": "6.13.0",
+ "ShopifySharp": "6.16.0",
"microsoft.extensions.dependencyinjection": "8.0.0"
}
},
diff --git a/test/Kentico.Xperience.Shopify.Tests/Mocks/GraphQLHttpClientMock.cs b/test/Kentico.Xperience.Shopify.Tests/Mocks/GraphQLHttpClientMock.cs
index 82fd484..1e8dd71 100644
--- a/test/Kentico.Xperience.Shopify.Tests/Mocks/GraphQLHttpClientMock.cs
+++ b/test/Kentico.Xperience.Shopify.Tests/Mocks/GraphQLHttpClientMock.cs
@@ -9,12 +9,13 @@ namespace Kentico.Xperience.Shopify.Tests.Mocks
{
internal class GraphQLHttpClientMock : IGraphQLClient
{
- private ShoppingCartRepository CartRepository => new();
+ private readonly IShoppingCartRepository cartRepository;
+
private DiscountCodesRepository DiscountCodesRepository => new();
- public GraphQLHttpClientMock()
+ public GraphQLHttpClientMock(IShoppingCartRepository cartRepository)
{
-
+ this.cartRepository = cartRepository;
}
public IObservable> CreateSubscriptionStream(GraphQLRequest request) => throw new NotImplementedException();
@@ -60,7 +61,7 @@ private UpdateDiscountCodesResponse HandleUpdateDiscountCodes(GraphQLRequest req
string[]? discountCodes = request.GetProperty("discountCodes");
var availableDiscountCodes = DiscountCodesRepository.DiscountCodes;
- var cart = CartRepository.Carts.FirstOrDefault(x => x.Id == cartId);
+ var cart = cartRepository.Carts.FirstOrDefault(x => x.Id == cartId);
if (cart != null && discountCodes != null)
{
@@ -83,7 +84,7 @@ private UpdateDiscountCodesResponse HandleUpdateDiscountCodes(GraphQLRequest req
private CreateCartResponse HandleCreateCart(GraphQLRequest request)
{
- var cart = CartRepository.Carts.First();
+ var cart = cartRepository.Carts.First();
var createCartParams = request.GetProperty("CartInput");
if (cart.Lines?.Edges is not null && createCartParams?.Lines is not null)
@@ -131,7 +132,7 @@ public Task> SendQueryAsync(GraphQLRequest
CartLinesUpdate = new CartResponseBase()
{
UserErrors = [],
- Cart = CartRepository.Carts.First()
+ Cart = cartRepository.Carts.First()
}
};
var cartLines = cartResponse.CartLinesUpdate.Cart.Lines?.Edges;
@@ -159,7 +160,7 @@ public Task> SendQueryAsync(GraphQLRequest
{
string? cartId = request.GetProperty("cartId")?.ToString();
- var cart = CartRepository.Carts.FirstOrDefault(x => x.Id == cartId);
+ var cart = cartRepository.Carts.FirstOrDefault(x => x.Id == cartId);
if (cart?.Id is null || cartId is null || cart.Id != cartId)
{
return null;
@@ -176,7 +177,7 @@ private RemoveCartItemResponse HandleCartItemRemove(GraphQLRequest request)
string? cartId = request.GetProperty("CartId")?.ToString();
string[]? cartLinesToRemove = (string[]?)Convert.ChangeType(request.GetProperty("LineIds"), typeof(string[]));
- var cart = CartRepository.Carts.FirstOrDefault(cart => cart.Id == cartId);
+ var cart = cartRepository.Carts.FirstOrDefault(cart => cart.Id == cartId);
var updatedCartLines = cart?.Lines?.Edges.Where(x => !cartLinesToRemove?.Contains(x.Node.Id) ?? false) ?? [];
if (cart?.Lines is not null)
@@ -231,7 +232,7 @@ private AddToCartResponse HandleAddToCart(GraphQLRequest request)
}
};
- var cart = CartRepository.Carts.FirstOrDefault(cart => cart.Id == cartId);
+ var cart = cartRepository.Carts.FirstOrDefault(cart => cart.Id == cartId);
var updatedCartLines = cart?.Lines?.Edges.Append(newLineItem) ?? [];
if (cart?.Lines is not null)
{
diff --git a/test/Kentico.Xperience.Shopify.Tests/Repositories/IShoppingCartRepository.cs b/test/Kentico.Xperience.Shopify.Tests/Repositories/IShoppingCartRepository.cs
new file mode 100644
index 0000000..0d85595
--- /dev/null
+++ b/test/Kentico.Xperience.Shopify.Tests/Repositories/IShoppingCartRepository.cs
@@ -0,0 +1,13 @@
+using Kentico.Xperience.Shopify.ShoppingCart;
+
+namespace Kentico.Xperience.Shopify.Tests.Repositories
+{
+ internal interface IShoppingCartRepository
+ {
+ public IEnumerable Carts { get; set; }
+
+ public CartObjectModel CartWithDiscountCode { get; set; }
+
+ public CartObjectModel CartWithSameProductVariant { get; set; }
+ }
+}
diff --git a/test/Kentico.Xperience.Shopify.Tests/Repositories/ShoppingCartRepository.cs b/test/Kentico.Xperience.Shopify.Tests/Repositories/ShoppingCartRepository.cs
index b4b31a2..297ba37 100644
--- a/test/Kentico.Xperience.Shopify.Tests/Repositories/ShoppingCartRepository.cs
+++ b/test/Kentico.Xperience.Shopify.Tests/Repositories/ShoppingCartRepository.cs
@@ -1,19 +1,24 @@
using Kentico.Xperience.Shopify.ShoppingCart;
using Kentico.Xperience.Shopify.ShoppingCart.GraphQLModels;
+
using ShopifySharp.GraphQL;
namespace Kentico.Xperience.Shopify.Tests.Repositories
{
- internal class ShoppingCartRepository
+ internal class ShoppingCartRepository : IShoppingCartRepository
{
private readonly DiscountCodesRepository discountCodesRepository = new();
public IEnumerable Carts { get; set; }
- public CartObjectModel CartWithDiscountCode => GenerateCartWithDiscountCodes();
+ public CartObjectModel CartWithDiscountCode { get; set; }
+
+ public CartObjectModel CartWithSameProductVariant { get; set; }
public ShoppingCartRepository()
{
+ CartWithDiscountCode = GenerateCartWithDiscountCodes();
+ CartWithSameProductVariant = GenerateCartWithSameProductVariant();
Carts =
[
GenerateCart1(),
@@ -21,10 +26,55 @@ public ShoppingCartRepository()
GenerateCart3(),
GenerateCart4(),
GenerateCartWithoutLines(),
- GenerateCartWithDiscountCodes()
+ GenerateCartWithDiscountCodes(),
+ GenerateCartWithSameProductVariant()
];
}
+ private CartObjectModel GenerateCartWithSameProductVariant()
+ {
+ // Create VariantProduct instances
+ var product1 = new VariantProduct { Title = "Panama Los Lajones Honey" };
+ var product2 = new VariantProduct { Title = "Kenya Gakuyuni AA" };
+
+ // Create Merchandise instances
+ var merchandise1 = new Merchandise { Id = "gid://shopify/ProductVariant/47401942581544", Title = "2 lb", Product = product1 };
+ var merchandise2 = new Merchandise { Id = "gid://shopify/ProductVariant/47401957785896", Title = "Default Title", Product = product2 };
+
+ // Create CartCost instances
+ var totalAmount1 = new PriceDto { Amount = 3267.0m, CurrencyCode = CurrencyCode.CZK };
+ var totalAmount2 = new PriceDto { Amount = 1272.0m, CurrencyCode = CurrencyCode.CZK };
+ var subtotalAmount = new PriceDto { Amount = 4539.0m, CurrencyCode = CurrencyCode.CZK };
+
+ var cost1 = new CartCost { TotalAmount = totalAmount1 };
+ var cost2 = new CartCost { TotalAmount = totalAmount2 };
+
+ // Create CartLineNode instances
+ var node1 = new CartLineNode { Id = "gid://shopify/CartLine/244da069-6219-4943-8741-a0c8690d461c?cart=Z2NwLXVzLWVhc3QxOjAxSFM5NUFDWUI4OTQ0N05HUVdKU0hXVFlN", Quantity = 3, Cost = cost1, Merchandise = merchandise1 };
+ var node2 = new CartLineNode { Id = "gid://shopify/CartLine/efb4b873-08b8-48dc-94d4-b026fdebfa07?cart=Z2NwLXVzLWVhc3QxOjAxSFM5NUFDWUI4OTQ0N05HUVdKU0hXVFlN", Quantity = 3, Cost = cost2, Merchandise = merchandise2 };
+ var node3 = new CartLineNode { Id = "gid://shopify/CartLine/8rt7qwpv-z8mk-297g-8eq4g-b9eq7g8e96rn?cart=Z2NwLXVzLWVhc3QxOjAxSFM5NUFDWUI4OTQ0N05HUVdKU0hXVFlN", Quantity = 2, Cost = cost1, Merchandise = merchandise1 };
+
+ // Create CartLineEdge instances
+ var edge1 = new CartLineEdge { Node = node1 };
+ var edge2 = new CartLineEdge { Node = node2 };
+ var edge3 = new CartLineEdge { Node = node3 };
+
+ // Create CartLines instance
+ var lines = new CartLines { Edges = [edge1, edge2, edge3] };
+
+ // Create CartCost instance
+ var cost = new CartCost { TotalAmount = totalAmount1, SubtotalAmount = subtotalAmount };
+
+ // Create CartObjectModel instance
+ return new CartObjectModel
+ {
+ Id = "gid://shopify/Cart/Z2NwLWV1cm9wZS13ZXN0MTowMUhXUTE4TjNCV0FaTlNEMTdTVks2RVhUQQ",
+ TotalQuantity = 6,
+ CheckoutUrl = "https://quickstart-3b0f1a15.myshopify.com/cart/c/Z2NwLWV1cm9wZS13ZXN0MTowMUhXUTE4TjNCV0FaTlNEMTdTVks2RVhUQQ?key=ca0127810e60f064542c8a70ee304b76",
+ Cost = cost,
+ Lines = lines
+ };
+ }
private CartObjectModel GenerateCartWithDiscountCodes()
{
diff --git a/test/Kentico.Xperience.Shopify.Tests/ShoppingServiceTests.cs b/test/Kentico.Xperience.Shopify.Tests/ShoppingServiceTests.cs
index 985fadb..8edc994 100644
--- a/test/Kentico.Xperience.Shopify.Tests/ShoppingServiceTests.cs
+++ b/test/Kentico.Xperience.Shopify.Tests/ShoppingServiceTests.cs
@@ -18,7 +18,7 @@ namespace Kentico.Xperience.Shopify.Tests
public class ShoppingServiceTests
{
private readonly AutoMocker mocker;
- private ShoppingCartRepository CartRepository { get; set; }
+ private IShoppingCartRepository CartRepository { get; set; }
private DiscountCodesRepository DiscountCodesRepository { get; set; }
@@ -37,7 +37,6 @@ public ShoppingServiceTests()
[OneTimeSetUp]
public void OneTimeSetUp()
{
- mocker.Setup(c => c.CreateGraphQLHttpClient()).Returns(new GraphQLHttpClientMock());
mocker.Use(new ShoppingCartCacheServiceMock());
mocker.Use(new EcommerceActivityLoggerMock());
}
@@ -52,6 +51,8 @@ public void SetUp()
{
CartRepository = new ShoppingCartRepository();
DiscountCodesRepository = new DiscountCodesRepository();
+ mocker.Use(CartRepository);
+ mocker.Setup(c => c.CreateGraphQLHttpClient()).Returns(new GraphQLHttpClientMock(CartRepository));
}
@@ -115,7 +116,7 @@ public async Task UpdateCartItem_SetNegativeQuantity_ShouldRemoveCartItem()
{
Country = CountryCode.CZ,
Quantity = -1,
- MerchandiseID = itemToUpdate.ShopifyCartItemId
+ ShoppingCartItemID = itemToUpdate.ShopifyCartItemId
})).Cart;
Assert.Multiple(() =>
@@ -208,7 +209,7 @@ public async Task UpdateCartItem_UpdatedProductAlreadyInCart_ShouldUpdateExistin
{
Country = CountryCode.CZ,
Quantity = newQuantity,
- MerchandiseID = itemToUpdate.ShopifyCartItemId
+ ShoppingCartItemID = itemToUpdate.ShopifyCartItemId
})).Cart;
Assert.Multiple(() =>
@@ -237,7 +238,7 @@ public async Task RemoveCartItem_CartContainsItem_ShouldRemoveCartItem()
var cartItemsList = shoppingCart.Items.ToList();
var itemToRemove = cartItemsList[0];
- var retrievedCart = (await shoppingService.RemoveCartItem(itemToRemove.VariantGraphQLId)).Cart;
+ var retrievedCart = (await shoppingService.RemoveCartItem(itemToRemove.ShopifyCartItemId)).Cart;
cartItemsList.Remove(itemToRemove);
Assert.Multiple(() =>
@@ -251,6 +252,32 @@ public async Task RemoveCartItem_CartContainsItem_ShouldRemoveCartItem()
}
+ ///
+ /// Remove cart items with the same product variant GraphQL ID.
+ ///
+ [Test]
+ public async Task RemoveCartProductVariant_CartContainsVariant_ShouldRemoveCartItem()
+ {
+ var shoppingCart = new ShoppingCartInfo(CartRepository.CartWithSameProductVariant);
+ SetHttpContext(shoppingCart);
+ var shoppingService = mocker.CreateInstance();
+ var cartItemsList = shoppingCart.Items.ToList();
+ var duplicitItem = cartItemsList[0];
+
+ var retrievedCart = (await shoppingService.RemoveProductVariantFromCart(duplicitItem.VariantGraphQLId)).Cart;
+ cartItemsList.RemoveAll(x => x.VariantGraphQLId == duplicitItem.VariantGraphQLId);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(retrievedCart, Is.Not.Null);
+
+ var retrievedCartItems = retrievedCart!.Items.ToList();
+ Assert.That(retrievedCartItems, Has.Count.EqualTo(cartItemsList.Count));
+ Assert.That(retrievedCartItems.Exists(x => x.VariantGraphQLId == duplicitItem.VariantGraphQLId), Is.False);
+ });
+ }
+
+
///
/// Remove non existing shopping cart item.
///
diff --git a/test/Kentico.Xperience.Shopify.Tests/packages.lock.json b/test/Kentico.Xperience.Shopify.Tests/packages.lock.json
index 682f27f..9869f01 100644
--- a/test/Kentico.Xperience.Shopify.Tests/packages.lock.json
+++ b/test/Kentico.Xperience.Shopify.Tests/packages.lock.json
@@ -986,8 +986,8 @@
"Kentico.Xperience.Admin": "[29.0.2, )",
"Kentico.Xperience.Core": "[29.0.2, )",
"Kentico.Xperience.Ecommerce.Common": "[1.0.0-prerelease-1, )",
- "ShopifySharp": "[6.13.0, )",
- "ShopifySharp.Extensions.DependencyInjection": "[1.4.0, )",
+ "ShopifySharp": "[6.17.0, )",
+ "ShopifySharp.Extensions.DependencyInjection": "[1.6.0, )",
"System.Configuration.ConfigurationManager": "[8.0.0, )",
"System.Linq.Async": "[6.0.1, )"
}
@@ -1075,9 +1075,9 @@
},
"ShopifySharp": {
"type": "CentralTransitive",
- "requested": "[6.13.0, )",
- "resolved": "6.13.0",
- "contentHash": "GwM1K0FzTo9BrXSqG9hWxIOGGfhrbBfLXK+b6vMqaabmw0Uv4/n9gskQKSZ9CIB7vJksI4Grn+Tk9qW/dVqBJQ==",
+ "requested": "[6.17.0, )",
+ "resolved": "6.17.0",
+ "contentHash": "0yvFrbdvCaQyWHOXsd5Lp15mPpJDQ0G+PE8vJVRkMiH39nrEemI1i2UYrFTS8axgNndGJzB+E1XzctwELp7uug==",
"dependencies": {
"Microsoft.Extensions.Http": "2.1.0",
"System.Text.Json": "7.0.3",
@@ -1086,11 +1086,11 @@
},
"ShopifySharp.Extensions.DependencyInjection": {
"type": "CentralTransitive",
- "requested": "[1.4.0, )",
- "resolved": "1.4.0",
- "contentHash": "jpaU4WpqjAXm/Tm7vQwC/gBbsdIHa4taO/aAN6BZxT8t91f9+OHkWZzFOsvChVUNn61cShcTR84tUfrv6X2cVg==",
+ "requested": "[1.6.0, )",
+ "resolved": "1.6.0",
+ "contentHash": "Hf0MexgLD6FbTcm81lFMPsw5kWK4XNEl7FPmYuoE9kPlWhWwR/wUWMYUpn6uSBTHCE+62yI2l+i7lsOb0zASnA==",
"dependencies": {
- "ShopifySharp": "6.13.0",
+ "ShopifySharp": "6.16.0",
"microsoft.extensions.dependencyinjection": "8.0.0"
}
},