Displaying Variant options and adjusting the price
Hi there - new to this and trying to figure it out, was wondering if you could guide me on how to present the variant option on the front end?
If I have an attribute called 'colour' which consists of an sku and priceAdjustment (does that type need to be a Vendr Price?) and then on the product I have created the variants 'black' and 'white' and provided the sku and price there... how should I then pull in this data in the front-end to create the 'select a colour' option as a dropdown, and then add that adjustment to the product price in order to add it to the cart?
Further to this, how could I also add a custom property value related to this variant when adding to the order? is there a way to pull in the selected variant alias - in this case 'colourblack' - and then assign something like:
Essentially what this is doing is, when you toggle a variant dropdown it fetches variant info for that combination, then JS reads that data and updates the UI accordingly. A few important bits are:
Data attributes rendered on the UI elements we want to update from JS
In the JS where we update the UI, make sure to update the productVariantReference input field.
Beyond this, you can tweak it to how you need. If you don't have a lot of variants, you could render all the variant combinations to a JS variable to prevent the need of the API call. Of you can update the API controller to return more or less data to update the UI.
RE a priceAdjustment property on your vairant, yes, this needs to be a Vendr: Price input field. When you add the parent product productReference and the variant productVariantReference to the order, Vendr will automatically work out and apply the price based upon the parent product price and the price adjustment on the variant.
RE the property, you might be better storing that as an order line property, rather than an order property. That way the width is explicitly for that given order line, which is a given attribute combination and you wouldn't need to prefix it.
One more thing - is there any potential issue having two Vendr: Price data types in a document type? As in having one for a 'minimum price' and the other for a 'price per square metre'.
If the width is not specificed then the 'minimum price' would apply (the variable for which it is just named 'price' for the default), however if the width is specified then I will calculate the price, that will involve instantiating a custom IPriceAdjuster correct?
Should I be adding the property to the orderline in the AddToCart method - seems to be the logical place, but from the examples there doesn't appear to be OrderLines yet. Any chance you could point me to an example of this?
Ok, I'm trying to piece this all together as it's not really one succinct question right now, but I'm assuming you have a product that uses the variants prop editor and for each variant you can provider a price and a pricePerSqMetre for. Then, when you add a variant to your order, you want to be able to provide a sqMetre property stored against the order line. Then, when calculating the price for the order line, if it has a sqMetre property defined, then use that and multiply by the variants pricePerSqMetre to define it's price, otherwise, if a sqMetre property isn't defined, then just use the default price.
So based on this assumption, and assuming your properties are named as per the above, you'd probably need to do the following in your add to cart
// DTO to hold posted values so you should be posting these values back
// from your HTML
public class AddToCartDto
{
public string ProductReference { get; set; }
public string ProductVariantReference { get; set; }
public string SqMetre { get; set; }
}
[HttpPost]
public ActionResult AddToCart(AddToCartDto postModel)
{
try
{
using (var uow = _uowProvider.Create())
{
var store = CurrentPage.GetStore();
var order = _sessionManager.GetOrCreateCurrentOrder(store.Id)
.AsWritable(uow)
.AddProduct(postModel.ProductReference, postModel.ProductVariantReference, 1, new Dictionary<string, string> {
{ "sqMetre": postModel.SqMetre }
});
_orderService.SaveOrder(order);
uow.Complete();
}
}
catch (ValidationException ex)
{
ModelState.AddModelError("productReference", "Failed to add product to cart");
return CurrentUmbracoPage();
}
return RedirectToCurrentUmbracoPage();
}
You'll then need to implement a custom IOrderLineCalculator inheriting from the default order line calculator
public class SqMetreOrderLineCalculator : OrderLineCalculator
{
private readonly IPublishedContentQuery _publishedContentQuery;
public OrderLineCalculator(ITaxService taxService,
IStoreService storeService,
IProductPriceFreezerService productPriceFreezerService,
IPublishedContentQuery publishedContentQuery)
: base(taxService, storeService, productPriceFreezerService)
{
_publishedContentQuery = publishedContentQuery;
}
public override Price CalculateOrderLineUnitPrice(OrderReadOnly order, OrderLineReadOnly orderLine, Guid currencyId, TaxRate taxRate)
{
// See if the order line has a sqMetre property
var sqMetre = int.Parse("0"+ orderLine.Properties["sqMetre"]);
if (sqMetre != 0) {
// Get the product + variant ID's from the order line
var productKey = Guid.Parse(orderLine.ProductReference);
var variantKey = Guid.Parse(orderLine.ProductVariantReference);
// Get the product + variant Umbraco nodes, typed to their Models Builder types
var productNode = _publishedContentQuery.Content(productKey) as ProductPage; // Assumes you have a models builder model called ProductPage
var variantNode = productNode.Variants[variantKey].Content as ProductVariant; // Assumes you have a models builder model called ProductVariant
// Fetch a per sq metre price for the given currency
var price = variantNode.PricePerSqMetre.GetPriceFor(currencyId);
if (price != null && price.Value > 0)
{
// Calculate the unit price as the price per sq metre * the defined sq metre
return Price.Calculate(price.Value * sqMetre, taxRate, currencyId);
}
}
// No sq metre property found on the order line so use normal calculation process
// which will ultimately fallback to using the variants `price` property
return base.CalculateOrderLineUnitPrice(order, orderLine, currencyId, taxRate);
}
}
Which you'll need to override the default implementation in a composer
[ComposeAfter(typeof(VendrWebComposer))]
public class Composer : IUserComposer
{
public void Compose(Composition composition)
{
composition.RegisterUnique<IOrderLineCalculator, SqMetreOrderLineCalculator>();
}
}
I've just written this stuff of the top of my head so I can't guarantee it'll work, but it should be the basic outline so you'll need to try it and debug anything that isn't quite right.
This works initially, but then when I navigate through the checkout from 'Information' to 'Shipping Method' I get a YSOD from the following line:
var productNode = _publishedContentQuery.Content(productKey) as Product;
The error I'm seeing is:
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'snapshot'.
at Umbraco.Web.PublishedCache.NuCache.ContentStore.Snapshot.Get(Guid id) in D:\a\1\s\src\Umbraco.Web\PublishedCache\NuCache\ContentStore.cs:line 1599
at Umbraco.Web.PublishedCache.NuCache.ContentCache.GetById(Boolean preview, Guid contentId) in D:\a\1\s\src\Umbraco.Web\PublishedCache\NuCache\ContentCache.cs:line 232
at Umbraco.Web.PublishedCache.PublishedCacheBase.GetById(Guid contentId) in D:\a\1\s\src\Umbraco.Web\PublishedCache\PublishedCacheBase.cs:line 28
at Umbraco.Web.PublishedContentQuery.Content(Guid id) in D:\a\1\s\src\Umbraco.Web\PublishedContentQuery.cs:line 50
at AtlasLogic.Extensions.SqMetreOrderLineCalculator.CalculateOrderLineUnitPrice(OrderReadOnly order, OrderLineReadOnly orderLine, Guid currencyId, TaxRate taxRate) in C:\Projects\proto\ProtoWeb\AtlasLogic\Extensions\SqMetreOrderLineCalculator.cs:line 38
I'm a bit lost here - can you assist further please?
Thank you
Ok, you could probably try swapping the IPublishedContentQuery to a IUmbracoContextFactory instead, and then wrap the inside of the if statement of the CalculateOrderLineUnitPrice with a
Displaying Variant options and adjusting the price
Hi there - new to this and trying to figure it out, was wondering if you could guide me on how to present the variant option on the front end?
If I have an attribute called 'colour' which consists of an sku and priceAdjustment (does that type need to be a Vendr Price?) and then on the product I have created the variants 'black' and 'white' and provided the sku and price there... how should I then pull in this data in the front-end to create the 'select a colour' option as a dropdown, and then add that adjustment to the product price in order to add it to the cart?
Further to this, how could I also add a custom property value related to this variant when adding to the order? is there a way to pull in the selected variant alias - in this case 'colourblack' - and then assign something like:
order.SetProperty("colourBlackWidth", "120mm");
Thanks for your help!
Hey Warren,
You can see an example of one way to do it within our demo store code at https://github.com/vendrhub/vendr-demo-store
If you take a look at the
ProductPage.cshtml
there are a few bits in there.If you look here you can see how we render our dropdowns for each attribute
https://github.com/vendrhub/vendr-demo-store/blob/main/src/Vendr.DemoStore.Web/Views/ProductPage.cshtml#L133-L149
Toggling a dropdown triggers a JS method which fetches product variant details which is here
https://github.com/vendrhub/vendr-demo-store/blob/main/src/Vendr.DemoStore.Web/Views/ProductPage.cshtml#L37-L74
Then the corresponding API controller which fetches and returns a given variants info can be found here
https://github.com/vendrhub/vendr-demo-store/blob/main/src/Vendr.DemoStore/Web/Controllers/ProductApiController.cs#L25-L57
Essentially what this is doing is, when you toggle a variant dropdown it fetches variant info for that combination, then JS reads that data and updates the UI accordingly. A few important bits are:
productVariantReference
input field.Beyond this, you can tweak it to how you need. If you don't have a lot of variants, you could render all the variant combinations to a JS variable to prevent the need of the API call. Of you can update the API controller to return more or less data to update the UI.
RE a
priceAdjustment
property on your vairant, yes, this needs to be a Vendr: Price input field. When you add the parent productproductReference
and the variantproductVariantReference
to the order, Vendr will automatically work out and apply the price based upon the parent product price and the price adjustment on the variant.RE the property, you might be better storing that as an order line property, rather than an order property. That way the width is explicitly for that given order line, which is a given attribute combination and you wouldn't need to prefix it.
Hope this helps
Matt
Awesome thanks for that, will have a play around and see how that goes.
One more thing - is there any potential issue having two Vendr: Price data types in a document type? As in having one for a 'minimum price' and the other for a 'price per square metre'.
If the width is not specificed then the 'minimum price' would apply (the variable for which it is just named 'price' for the default), however if the width is specified then I will calculate the price, that will involve instantiating a custom IPriceAdjuster correct?
Thank again!
Hi Warren,
Shouldn't be a problem of having multiple price fields. Vendr is only interested in one with an alias of
price
(see here for required property aliases https://vendr.net/docs/core/1.8.0/key-concepts/umbraco-properties/).RE calculating the price, yea, you could either use a custom adjuster, or replace the
OrderLineCalculator
( https://vendr.net/docs/core/1.8.0/key-concepts/calculators/ )Matt
Thank you. I nearly jumped on another closed thread again... so will ask here as well if I may...
https://our.umbraco.com/packages/website-utilities/vendr/vendr-support/105253-dynamic-price-from-api-for-products#comment-328311
Should I be adding the property to the orderline in the AddToCart method - seems to be the logical place, but from the examples there doesn't appear to be OrderLines yet. Any chance you could point me to an example of this?
Thanks again!
Hi Warren
Ok, I'm trying to piece this all together as it's not really one succinct question right now, but I'm assuming you have a product that uses the variants prop editor and for each variant you can provider a
price
and apricePerSqMetre
for. Then, when you add a variant to your order, you want to be able to provide asqMetre
property stored against the order line. Then, when calculating the price for the order line, if it has asqMetre
property defined, then use that and multiply by the variantspricePerSqMetre
to define it's price, otherwise, if asqMetre
property isn't defined, then just use the default price.So based on this assumption, and assuming your properties are named as per the above, you'd probably need to do the following in your add to cart
You'll then need to implement a custom
IOrderLineCalculator
inheriting from the default order line calculatorWhich you'll need to override the default implementation in a composer
I've just written this stuff of the top of my head so I can't guarantee it'll work, but it should be the basic outline so you'll need to try it and debug anything that isn't quite right.
Matt
Amazing man! Thanks so much - will have a play and see
This works initially, but then when I navigate through the checkout from 'Information' to 'Shipping Method' I get a YSOD from the following line:
The error I'm seeing is:
I'm a bit lost here - can you assist further please? Thank you
Ok, you could probably try swapping the
IPublishedContentQuery
to aIUmbracoContextFactory
instead, and then wrap the inside of the if statement of theCalculateOrderLineUnitPrice
with athen swap the call to
_publishedContentQuery.Content(productKey)
forctx.ContentCache.GetById(false, productKey)
. I think this should work.Matt
Perfect! Thank you
is working on a reply...