At the moment item counts are decremented when they are added to a shipment. We have had one comment that it would be nice to make this configurable so that you have the option of setting this to when an order is created (so after an invoice has been paid).
Just to offer my 2p (or 2ยข) - I think this is definitely something that should be configurable. In my (limited) experience of small businesses entering into ecommerce they do not hold large volumes of stock and so decrementing stock at the point of fulfilment is too risky because they are potentially continuing to sell stock they no longer have as it is committed to unfulfilled orders.
It would be very nice to be able to configure it, we need it to either decrement when the order is created or at least reserved somehow so the same item doesn't get bought twice.
The current shop I'm working on has a small storage so when ever the inventory is 0 it means we have to backorder items.
I have looked into chains, would it be possible to create a new chain that decrements on order creation?
Then add a new task to the OrderPreparationOrderCreate chain. It has to go after the ConvertInvoiceItemsToOrderItemsTask so that the line items will be available.
The MyInventoryTask need to sub-class the OrderCreationAttemptChainTaskBase class.
/// <summary>
/// The convert invoice items to order items task.
/// </summary>
public class MyInventoryTask : OrderCreationAttemptChainTaskBase
{
public MyInventoryTask(IInvoice invoice)
: base(invoice)
{
}
public override Attempt<IOrder> PerformTask(IOrder value)
{
// THIS IS A COPY PASTE FROM THE REMOVED TASK (UNTESTED)
var trackableItems = Order.InventoryTrackedItems().Where(x => KeysToShip.Contains(x.Key)).ToArray();
var variants = _productVariantService.GetByKeys(trackableItems.Select(x => x.ExtendedData.GetProductVariantKey())).ToArray();
if (variants.Any())
{
foreach (var item in trackableItems)
{
var variant = variants.FirstOrDefault(x => x.Key == item.ExtendedData.GetProductVariantKey());
if (variant == null) return Attempt<IShipment>.Fail(new NullReferenceException("A ProductVariant reference in the order could not be found"));
var inventory = variant.CatalogInventories.FirstOrDefault(x => x.CatalogKey == item.ExtendedData.GetWarehouseCatalogKey());
if (inventory == null) return Attempt<IShipment>.Fail(new NullReferenceException("An inventory record could not be found for an order line item"));
inventory.Count -= item.Quantity;
}
if (trackableItems.Any()) _productVariantService.Save(variants);
}
return Attempt<IOrder>.Succeed(value);
}
}
This is exactly what I would like to do, however I am unable to get this to work. Here is what I have:
public class RemoveOrderItemsFromInventory : OrderCreationAttemptChainTaskBase
{
public RemoveOrderItemsFromInventory(IInvoice invoice)
: base(invoice)
{
}
/// <summary>
/// The shipment service.
/// </summary>
private readonly IShipmentService _shipmentService;
/// <summary>
/// The order service.
/// </summary>
private readonly IOrderService _orderService;
/// <summary>
/// The product variant service.
/// </summary>
private readonly IProductVariantService _productVariantService;
public override Attempt<IOrder> PerformTask(IOrder value)
{
var trackableItems = value.InventoryTrackedItems().ToArray();
var variants = _productVariantService.GetByKeys(trackableItems.Select(x => x.ExtendedData.GetProductVariantKey())).ToArray();
if (variants.Any())
{
foreach (var item in trackableItems)
{
var variant = variants.FirstOrDefault(x => x.Key == item.ExtendedData.GetProductVariantKey());
if (variant == null) return Attempt<IOrder>.Fail(new NullReferenceException("A ProductVariant reference in the order could not be found"));
var inventory = variant.CatalogInventories.FirstOrDefault(x => x.CatalogKey == item.ExtendedData.GetWarehouseCatalogKey());
if (inventory == null) return Attempt<IOrder>.Fail(new NullReferenceException("An inventory record could not be found for an order line item"));
inventory.Count -= item.Quantity;
}
if (trackableItems.Any()) _productVariantService.Save(variants);
}
return Attempt<IOrder>.Succeed(value);
}
}
The main thing I changed is that trackableItems is using the parameter 'value' since it is the IOrder. I then took out the Where clause since essentially all trackable items need to be tracked, not just Shippable items. Anyway, this is not working for me so am I not understanding?
@Tyler - does the code execute all the way through or is it failing. Also, are the inventory values changing in the database, just not showing in the UI?
The sale goes through. I don't think it's actually hitting my code. The inventory is not changing in the DB or the UI. This is how I'm registering the task, where myTasks is my namespace for for my class:
Is the payment for the sale captured or just authorized?
An order will not be created (by default) if the payment is just authorized.
You can override this by changing the setting AlwaysApproveOrderCreation to "true" in the merchello.config
<!--
Overrides the Payment Method's IPaymentResponse ApproveOrderCreation indicating an order should always be created no matter
if the payment has been collected or not.
-->
<setting alias="AlwaysApproveOrderCreation" value="true" />
One thing to note is that I am dealing with non-physical products. Essentially, users are buying access to the site (access to courses and tests) and so my checkout controller is calling AuthorizeCapturePayment instead of AuthorizePayment so that the sale is made and the funds are captured at the same time (so users get instant access to courses and tests). Would cause an issue?
Can you look in the merchOrder table and see if orders are being created for your use case? They should have related items in the merchOrderItem table ...
No, it does not sound like anything is screwed up. The payment result from an AuthorizeCapturePayment should have ApproveOrderCreation = true if the payment was successful.
I wonder if the Merchello dependencies on the stripe provider need to be updated. Perhaps what is going on is the event that creates the order is not being triggered ....
I know that the stripe provider is only confirmed to be compatible with Merchello 1.4.1 but I decided to try it since I need this version of Merchello and everything 'seemed' to be working...
the provider is using Merchello 1.9.0. When does the event that creates the order fire?
I've made some progress... when debugging, it seemed that the ApproveOrderCreation was coming back as true, however, I hardcoded ApproveOrderCreation to 'true' by changing this line in my Stripe provider from this:
return new PaymentResult(Attempt<IPayment>.Succeed(payment), invoice, invoice.ShippingLineItems().Any());
to this:
return new PaymentResult(Attempt
which has now allowed the creation of an order in the DB, but when my new task is enabled, I get an error:
Value name cannot be null. Parameter name: task
So apart from fixing my earlier issue with ApproveOrderCreation, any ideas on this error? Am I registering it correctly in the merchello.config?
I don't see anything wrong with the way you've registered the task. Are you able to step through your task, or are you saying it errors before ever hitting it?
Ok! It's finally getting to my code. My dll was absent ยฏ_(ใ)_/ยฏ
Now I get an object reference error. The following variable ends up being an empty array:
var trackableItems = value.InventoryTrackedItems().ToArray();
I have confirmed that I am tracking this particular product and that the parameter value itself is not null.
The difference between this code and RemoveShipmentOrderItemsFromInventoryAndPersistShipmentTask is that I'm using the passed IOrder 'value' to grab inventory items. any ideas?
You could get a collection of all variants in the order based off the product variant keys the same way as your doing after you filter and then filter those off of tracks inventory.
Even with a large order the query will be just about the same as if you did it after the filter.
This would be a good validation step anyway in off chance that a back office user changes a variant from tracks inventory = true or false after the product has been added to the basket but before it reaches this point in the flow.
I actually have a to do that introduces this sort of validation throughout the checkout process.
Thanks for the help! How would I go about grabbing all variants? I see that I can get all items using value.Items (instead of value.InventoryTrackedItems) and that array is populated but I'm not sure how to invoke product variant service from inside OrderCreationAttemptChain. I get an object ref error on the line:
var variants = _productVariantService.GetByKeys(Items.Select(x => x.ExtendedData.GetProductVariantKey())).ToArray();
I'm getting a warning that _productVariantService is never assigned to and will always have its default value as null.
I've narrowed down at least what is going wrong, although I don't know why:
var inventory is null at this line:
var inventory = variant.CatalogInventories.FirstOrDefault(x => x.CatalogKey == item.ExtendedData.GetWarehouseCatalogKey());
because item.ExtendedData.GetWarehouseCatalogKey comes back as an empty guid. If you dig into the database and look at the ExtendedData xml, the 'merchWarehouseCatalogKey' element does not exist for this item. When I created the product, I just checked the box for 'track inventory for this variant'. I also checked the box 'make this variant shippable'. Am I missing another step? Is there another reason why merchWarehouseCatalogKey would not show up for an item in and Order?
I began this project by using the Merchello basket tutorial that just uses the cash payment method, as a proof of concept. I've added the Stripe provider.
Now, because the site I want to build is selling access to online exams for certification, there is no physical product. Members are paying for access to be able to take exams. I'm updating custom tables to enable access to exams for Umbraco members based on whether or not capturing payment is successful. I am capturing payment automatically when a user purchases a product. So far, that is working as expected.
Long story short, I am trying to use the quantity property to limit how many times a product (in this case, site access) can be purchased. And that is where I am stuck. I can't seem to get this quantity property to decrement.
So it sounds like I'm missing a step with shipment? As stated earlier, invoice.ShippingLineItems().Any() is coming back as false so to run my task, I have hardcoded it as true. But it sounds like this could be the problem. How do I reconcile shipment for a non-physical product?
The WarehouseCatalogKey is there so that the association can be made between the item, the inventory and shipping rules - so you can different shipping rules, for different countries for the same item ...
In your case, you can just add the catalog key when you add your item to the basket. The ExtendedDataKey is
Constants.ExtendedDataKeys.WarehouseCatalogKey
and the value will be the Guid associated with your inventory record.
ah ha! well alright- I get it now! That worked and everything is working as expected. For anyone curious to see the final result (It's not much different than the original) here is the task:
public class RemoveOrderItemsFromInventory : OrderCreationAttemptChainTaskBase
{
public RemoveOrderItemsFromInventory(IInvoice invoice)
: base(invoice)
{
}
public override Attempt<IOrder> PerformTask(IOrder value)
{
var productVariantService = MerchelloContext.Current.Services.ProductVariantService;
var trackableItems = value.InventoryTrackedItems().ToArray();
var variants = productVariantService.GetByKeys(trackableItems.Select(x => x.ExtendedData.GetProductVariantKey())).ToArray();
if (variants.Any())
{
foreach (var item in trackableItems)
{
var variant = variants.FirstOrDefault(x => x.Key == item.ExtendedData.GetProductVariantKey());
if (variant == null) return Attempt<IOrder>.Fail(new NullReferenceException("A ProductVariant reference in the order could not be found"));
var inventory = variant.CatalogInventories.FirstOrDefault(x => x.CatalogKey == item.ExtendedData.GetWarehouseCatalogKey());
if (inventory == null) return Attempt<IOrder>.Fail(new NullReferenceException("An inventory record could not be found for an order line item"));
inventory.Count -= item.Quantity;
}
if (trackableItems.Any()) productVariantService.Save(variants);
}
return Attempt<IOrder>.Succeed(value);
}
}
I am trying to do a similar thing - well kind of similar, I want to update stock after payment has been made (SagePay plugin) rather than when the order is fulfilled.
I have code in the PaymentGatewayMethodBase.CaptureAttempted event similar to the above. It runs OK but I don't have a return statement so the changes do not get saved ... how do I save / persist the changes?
@Gordon - The inventory is tracked with the variants, so you really don't need to save the order or return anything - just save the variants as is done above ... or are your trying to write your own taskChain?
Below is the code I have in the event handler (minus some email stuff):
private void Payment_CaptureAttempted(PaymentGatewayMethodBase sender, PaymentAttemptEventArgs<IPaymentResult> e)
{
try
{
if (!e.Entity.Payment.Success) return;
Payment p = (Payment) e.Entity.Payment.Result;
// Only do emails if this is a Credit Card (Sage) sale
if (p.PaymentMethodType != PaymentMethodType.CreditCard)
{
return;
}
IOrder value = e.Entity.Invoice.PrepareOrder();
var productVariantService = MerchelloContext.Current.Services.ProductVariantService;
var trackableItems = value.InventoryTrackedItems().ToArray();
var variants = productVariantService.GetByKeys(trackableItems.Select(x => x.ExtendedData.GetProductVariantKey())).ToArray();
foreach (var item in e.Entity.Invoice.Items)
{
var variant = variants.FirstOrDefault(x => x.Key == item.ExtendedData.GetProductVariantKey());
//if (variant == null) return Attempt<IOrder>.Fail(new NullReferenceException("A ProductVariant reference in the order could not be found"));
if (variant != null)
{
var inventory = variant.CatalogInventories.FirstOrDefault(x => x.CatalogKey == item.ExtendedData.GetWarehouseCatalogKey());
//if (inventory == null) return Attempt<IOrder>.Fail(new NullReferenceException("An inventory record could not be found for an order line item"));
if (inventory != null) inventory.Count -= item.Quantity;
}
}
}
catch (Exception ex)
{
LogHelper.Error<AppStartupEventHandler>("Error triggering order confirmation notification.", ex);
}
}
I stepped through it and it did decrement the ordered item ... but the change wasn't shown when I viewed the product in the Admin.
It is false ... it also seems to vary and I'm not 100% sure, but it may work differently depending on how many times I save the product in the Admin?!?!
My situation turned out to be slightly different (that's probably the polite phrase!!) in that none of the products had any variants, so the code above didn't work well.
With Rusty's help, we came up with this which deals with the products themselves:
// Send Order Confirmation emails (Customer and VFA Admin)
Notification.Trigger("OrderConfirmation", e.Entity, new[] { contactEmail });
// Update the stock quantity for the purchased product(s)
IOrder value = e.Entity.Invoice.PrepareOrder();
var productService = MerchelloContext.Current.Services.ProductService;
var trackableItems = value.InventoryTrackedItems().ToArray();
var products = productService.GetByKeys(trackableItems.Select(x => x.ExtendedData.GetProductKey())).ToArray();
foreach (var item in e.Entity.Invoice.Items)
{
var product = products.FirstOrDefault(x => x.Key == item.ExtendedData.GetProductKey());
if (product != null)
{
var inventory = product.CatalogInventories.FirstOrDefault(x => x.CatalogKey == item.ExtendedData.GetWarehouseCatalogKey());
if (inventory != null) inventory.Count -= item.Quantity;
productService.Save(product);
}
}
I tracking the inventory products stock but it seems there isn't a stop-point where user will be notified that he has chosen more quantity than there is in stock.
For example trying to checkout and user has chosen 100 quantity of a product and actually there is 10 in stock.
with one more specific to your sites requirements. If you look in the merchello.config file, you will see a task chain:
<taskChain alias="ItemCacheValidation">
<!-- Added Merchello Version 1.11.0
This chain validates basket and wish list items against values in the back office to assert that the customer has not
added items to their basket that were subsequently changed in the back office prior to checkout. The process is needed
as the relation between the basket and wish list items are decoupled from the actual persisted values.
-->
<tasks>
<task type="Merchello.Web.Validation.Tasks.ValidateProductsExistTask, Merchello.Web" />
<!--
The following task is intended to assert that pricing and/or on sale value has not changed in the back office since the
customer has placed an item into their basket or wish list. If you have made custom pricing modifications in your
implementation, you may either remove this task or adjust your code to add a new extended data value
merchLineItemAllowsValidation = false
to the line item so that it is skipped in the validation process.
-->
<task type="Merchello.Web.Validation.Tasks.ValidateProductPriceTask, Merchello.Web" />
<!--
Validates that products are still in inventory
-->
<task type="Merchello.Web.Validation.Tasks.ValidateProductInventoryTask, Merchello.Web" />
</tasks>
</taskChain>
If you have time, I'd love to see your use case ..
Basically, all I want is that users would not be allowed to continue with checkout if they choose items in the cart which are out of stock or choose to update quantity of a product which there is not enough in stock.
And when should the task be fired (PerformTask) since I am testing and added a breakpoint in the PerformTask() in the ValidateProductInventoryTask and is not entering when I add product to cart, or update product cart item, or continue with the checkout?
Validate() is public so you can also call it directly from the Basket, Wishlist, and SalePreparation objects.
I think I would create my own custom validation task to check the quantity against existing inventory and instead of simply removing the item as the existing task does show a message to your customer.
But may I ask why it might not working, since I have tried to reduce the quantity to 0 and try to buy something from that product, and nothing happens and stops me from buying.
I am stuck how I am going to check the quantity selected from the user in the cart via the totalInventoryCount.
I want that user will be disallowed to update the cart of chekcout when the user has some of the items which has the quantity requested greater than the current stock.
I did not think about updating the quantities ... I'll add another event to the CustomerItemCacheBase class for updating / saving and get it into 1.13.1.
Can you guide me please how I can add a new task and a new class etc, from where I need to extend etc... as I have already tried and it was not being fired /working
Basically, I want that the cart will be validate once user checkout to go to the addresses page. Here I need to check the quantities of each product and if found a product in cart which has quantity greater the the stock, will be redirected back to the cart, informing the user.
If so, you should be able to call the Validate() on the basket itself and get the result of your attempt right in your controller and if it returns an failed attempt you could do your redirect with a message you return.
The validation should also be kicking off automatically as soon as your start interacting with the SalePreparation (BasketSalePreparation) object.
Hey Simon - can you send me a DM on twitter so we can coordinate. I'm thinking it would be easiest if your repository was somewhere I could see (even privately) . If we come up with a solution, it'd be great to be able to use the code as an example "how to" in the new documentation stuff we are working on.
I am trying to put together an example for you - have you written a customer Basket or Checkout controller or are you trying to do this via the controllers that are included in the Bazaar starter kit?
But when i fulfill the order in backoffice i get this message: "Shipment #0 created" and no shipment is created. How could i trace what is happening? if i uncomment the line in merchello.config the shipment is created.
Sorry, i have just read the name of the task "RemoveShipmentOrderItemsFromInventoryAndPersistShipmentTask" so if i removed the shipment is not persisted. So i am going to create a new task that persists the shipment without removing inventory!
Has anyone on this thread dealt with the issue of stock levels not being updated with selling non-physical products? Reading some of the issues above, it sounds like the stock level is only affected when it's associated with shipments. In our case we have no shipments as the products are event tickets (they don't even have a download element, it's just taking a payment for something that's administered offline) in which case our stock levels are never modified.
We're using Merchello 2.5.0 and I can't see any fixes for this issue in the 2.6.0 commits.
Can anyone suggest how to work around this issue and get stock levels updating automatically at some point in the process?
Inventory tracking
When does the inventory get updates? And do I need to manage it in the checkout flow?
Running Umbraco 7.2.2, Merchello 1.8.0. Shop is based of off Kitten Sample
At the moment item counts are decremented when they are added to a shipment. We have had one comment that it would be nice to make this configurable so that you have the option of setting this to when an order is created (so after an invoice has been paid).
Do you have thoughts about that?
Hi Rusty,
Just to offer my 2p (or 2ยข) - I think this is definitely something that should be configurable. In my (limited) experience of small businesses entering into ecommerce they do not hold large volumes of stock and so decrementing stock at the point of fulfilment is too risky because they are potentially continuing to sell stock they no longer have as it is committed to unfulfilled orders.
Cheers, Simon
Hey Simon,
I think you saw the feature request in the issue tracker and will add that in V3.
It would be very nice to be able to configure it, we need it to either decrement when the order is created or at least reserved somehow so the same item doesn't get bought twice. The current shop I'm working on has a small storage so when ever the inventory is 0 it means we have to backorder items.
I have looked into chains, would it be possible to create a new chain that decrements on order creation?
You add a task to the order chain to do it there but then remove the task from the shipment chain.
Is it possible to get some help with the code? :)
I can't get the custom chain to take effect, can anybody help with a code example of how to propper implement it?
Jakob,
You are trying to add a task to a chain, not create your own chain right? Just want to know what I should post as an example =)
Yes as you suggested adding a task to the order chain and removing from shipment chain
Great.
In the merchello.config file you should find a list of taskChains.
First you would want to remove the task
Should look like this after
Then add a new task to the OrderPreparationOrderCreate chain. It has to go after the ConvertInvoiceItemsToOrderItemsTask so that the line items will be available.
The MyInventoryTask need to sub-class the OrderCreationAttemptChainTaskBase class.
Let me know how it goes.
This is exactly what I would like to do, however I am unable to get this to work. Here is what I have:
The main thing I changed is that trackableItems is using the parameter 'value' since it is the IOrder. I then took out the Where clause since essentially all trackable items need to be tracked, not just Shippable items. Anyway, this is not working for me so am I not understanding?
Thanks!
@Tyler - does the code execute all the way through or is it failing. Also, are the inventory values changing in the database, just not showing in the UI?
The sale goes through. I don't think it's actually hitting my code. The inventory is not changing in the DB or the UI. This is how I'm registering the task, where myTasks is my namespace for for my class:
Is the payment for the sale captured or just authorized?
An order will not be created (by default) if the payment is just authorized.
You can override this by changing the setting AlwaysApproveOrderCreation to "true" in the merchello.config
One thing to note is that I am dealing with non-physical products. Essentially, users are buying access to the site (access to courses and tests) and so my checkout controller is calling AuthorizeCapturePayment instead of AuthorizePayment so that the sale is made and the funds are captured at the same time (so users get instant access to courses and tests). Would cause an issue?
Can you look in the merchOrder table and see if orders are being created for your use case? They should have related items in the merchOrderItem table ...
There are no orders in the merchOrder table.
I wrote my last post prior to seeing your mention of AlwaysApproveOrderCreation in the merchello.config. That is good to know!
By not calling AuthorizePayment, but going straight to AuthorizeCapturePayment, am I screwing things up?
I should have some orders though and I have none...
No, it does not sound like anything is screwed up. The payment result from an AuthorizeCapturePayment should have ApproveOrderCreation = true if the payment was successful.
What payment provider are you using?
I'm using the Stripe Payment Provider from Alex Lindgren
Merchello version 1.9.0 Umbraco 7.2
What is the value of ApproveOrderCreation in the result returned from the stripe provider?
It is coming in as true
I wonder if the Merchello dependencies on the stripe provider need to be updated. Perhaps what is going on is the event that creates the order is not being triggered ....
I know that the stripe provider is only confirmed to be compatible with Merchello 1.4.1 but I decided to try it since I need this version of Merchello and everything 'seemed' to be working...
the provider is using Merchello 1.9.0. When does the event that creates the order fire?
I've made some progress... when debugging, it seemed that the ApproveOrderCreation was coming back as true, however, I hardcoded ApproveOrderCreation to 'true' by changing this line in my Stripe provider from this:
to this:
return new PaymentResult(Attempt
which has now allowed the creation of an order in the DB, but when my new task is enabled, I get an error:
Value name cannot be null. Parameter name: task
So apart from fixing my earlier issue with ApproveOrderCreation, any ideas on this error? Am I registering it correctly in the merchello.config?
Thanks for the quick responses!
I don't see anything wrong with the way you've registered the task. Are you able to step through your task, or are you saying it errors before ever hitting it?
It isn't getting to my task. It is getting as far as:
Merchello.Core\Chains\AttemptChainTaskHandler.cs: line 16
Ok - it is failing to construct your type.
Some things to confirm:
Ok! It's finally getting to my code. My dll was absent ยฏ_(ใ)_/ยฏ
Now I get an object reference error. The following variable ends up being an empty array:
I have confirmed that I am tracking this particular product and that the parameter value itself is not null.
The difference between this code and RemoveShipmentOrderItemsFromInventoryAndPersistShipmentTask is that I'm using the passed IOrder 'value' to grab inventory items. any ideas?
You could get a collection of all variants in the order based off the product variant keys the same way as your doing after you filter and then filter those off of tracks inventory.
Even with a large order the query will be just about the same as if you did it after the filter.
This would be a good validation step anyway in off chance that a back office user changes a variant from tracks inventory = true or false after the product has been added to the basket but before it reaches this point in the flow.
I actually have a to do that introduces this sort of validation throughout the checkout process.
Thanks for the help! How would I go about grabbing all variants? I see that I can get all items using value.Items (instead of value.InventoryTrackedItems) and that array is populated but I'm not sure how to invoke product variant service from inside OrderCreationAttemptChain. I get an object ref error on the line:
I'm getting a warning that _productVariantService is never assigned to and will always have its default value as null.
You can either do that in your constructor or get rid of your readonly field and do
I've narrowed down at least what is going wrong, although I don't know why:
var inventory is null at this line:
because item.ExtendedData.GetWarehouseCatalogKey comes back as an empty guid. If you dig into the database and look at the ExtendedData xml, the 'merchWarehouseCatalogKey' element does not exist for this item. When I created the product, I just checked the box for 'track inventory for this variant'. I also checked the box 'make this variant shippable'. Am I missing another step? Is there another reason why merchWarehouseCatalogKey would not show up for an item in and Order?
That gets looked up when the basket is packaged. How are you quoting your shipment?
I'm not doing anything with shipment.
I began this project by using the Merchello basket tutorial that just uses the cash payment method, as a proof of concept. I've added the Stripe provider.
Now, because the site I want to build is selling access to online exams for certification, there is no physical product. Members are paying for access to be able to take exams. I'm updating custom tables to enable access to exams for Umbraco members based on whether or not capturing payment is successful. I am capturing payment automatically when a user purchases a product. So far, that is working as expected.
Long story short, I am trying to use the quantity property to limit how many times a product (in this case, site access) can be purchased. And that is where I am stuck. I can't seem to get this quantity property to decrement.
So it sounds like I'm missing a step with shipment? As stated earlier, invoice.ShippingLineItems().Any() is coming back as false so to run my task, I have hardcoded it as true. But it sounds like this could be the problem. How do I reconcile shipment for a non-physical product?
The WarehouseCatalogKey is there so that the association can be made between the item, the inventory and shipping rules - so you can different shipping rules, for different countries for the same item ...
In your case, you can just add the catalog key when you add your item to the basket. The ExtendedDataKey is
and the value will be the Guid associated with your inventory record.
That should give you the tie you need.
ah ha! well alright- I get it now! That worked and everything is working as expected. For anyone curious to see the final result (It's not much different than the original) here is the task:
Thanks for all the help, Rusty!
I am trying to do a similar thing - well kind of similar, I want to update stock after payment has been made (SagePay plugin) rather than when the order is fulfilled.
I have code in the PaymentGatewayMethodBase.CaptureAttempted event similar to the above. It runs OK but I don't have a return statement so the changes do not get saved ... how do I save / persist the changes?
@Gordon - The inventory is tracked with the variants, so you really don't need to save the order or return anything - just save the variants as is done above ... or are your trying to write your own taskChain?
Below is the code I have in the event handler (minus some email stuff):
I stepped through it and it did decrement the ordered item ... but the change wasn't shown when I viewed the product in the Admin.
After you foreach you need to save the variants ...
Thanks Rusty, that sorted it!
Ah, spoke too soon ... even though I am setting the quantity to 1 on a product, repeated orders are making the quantity go down one each time.
I have just done an order and the product qty was set to 1. It now shows -2 in the Admin!?
By the way, I have commented out the following
in merchello.config,
what is the outofstockpurchase value on the variant?
Urm, do you mean the "Low Count" value? If so, 0 (zero).
no, there is a bool setting OutOfStockPurchase
On each product? I can't see that, nor is there anything on the Merchello settings? Can you let me know where it is.
Sorry, It should be false - but you'll have to step through your code to see it. I've added a task to expose it in the editors in version 1.12.0
http://issues.merchello.com/youtrack/issue/M-821
It is false ... it also seems to vary and I'm not 100% sure, but it may work differently depending on how many times I save the product in the Admin?!?!
:-(
My situation turned out to be slightly different (that's probably the polite phrase!!) in that none of the products had any variants, so the code above didn't work well.
With Rusty's help, we came up with this which deals with the products themselves:
Hi Guys,
I am using the PayPal Express Payment Provider as a payment.
Once I redirecting to Paypal, I tried to implement the CaptureAttempted Event but it did not fires up.
Does anyone can help me achieve this since I need to do some custom logic after a customer will pay via paypal.
Thank you.
Kind Regards.
Hi Guys,
I tracking the inventory products stock but it seems there isn't a stop-point where user will be notified that he has chosen more quantity than there is in stock.
For example trying to checkout and user has chosen 100 quantity of a product and actually there is 10 in stock.
Does anyone knows how can I tackle this please?
Thank you.
Kind Regards.
Hey Simon,
Products have a total inventory count property that could be checked (this is also on the variant if your product has options).
There are a couple of options.
You could using handle the Basket events AddingItem / RemovingItem to check inventory counts.
Another way would be to replace the validation task
with one more specific to your sites requirements. If you look in the merchello.config file, you will see a task chain:
If you have time, I'd love to see your use case ..
Hi Rusty,
thanks for your reply.
Basically, all I want is that users would not be allowed to continue with checkout if they choose items in the cart which are out of stock or choose to update quantity of a product which there is not enough in stock.
Could help me sort this out please?
Thank you.
And when should the task be fired (PerformTask) since I am testing and added a breakpoint in the PerformTask() in the ValidateProductInventoryTask and is not entering when I add product to cart, or update product cart item, or continue with the checkout?
Thank you.
Hey Simon,
The chain is executed when the basket is created and when the basket is converted to SalePreparation.
Here is the method:
https://github.com/Merchello/Merchello/blob/merchello-dev/src/Merchello.Web/Workflow/CustomerItemCache/CustomerItemCacheBase.cs#L690
From the basket it is called:
https://github.com/Merchello/Merchello/blob/merchello-dev/src/Merchello.Web/Workflow/Basket.cs#L117
When you start the sale preparation portion of the workflow it is called
https://github.com/Merchello/Merchello/blob/merchello-dev/src/Merchello.Web/Workflow/BasketSalePreparation.cs#L171
Validate() is public so you can also call it directly from the Basket, Wishlist, and SalePreparation objects.
I think I would create my own custom validation task to check the quantity against existing inventory and instead of simply removing the item as the existing task does show a message to your customer.
Here is the existing task:
https://github.com/Merchello/Merchello/blob/merchello-dev/src/Merchello.Web/Validation/Tasks/ValidateProductInventoryTask.cs
You could create your own class
Here is the code for the ValidationResult
https://github.com/Merchello/Merchello/blob/merchello-dev/src/Merchello.Web/Validation/ValidationResult%7BT%7D.cs
Hi Rusty,
Thanks again for your reply.
But may I ask why it might not working, since I have tried to reduce the quantity to 0 and try to buy something from that product, and nothing happens and stops me from buying.
Hi Rusty,
I am stuck how I am going to check the quantity selected from the user in the cart via the totalInventoryCount.
I want that user will be disallowed to update the cart of chekcout when the user has some of the items which has the quantity requested greater than the current stock.
Appreciate any help.
Thank you.
Hey Simon,
Are you still struggling with this? I think handling the AddingItem event on the basket would be the best approach.
Hi Rusty,
Yes, I'm still struggling with this. The user tough, can choose to update the quantity as well in cart, so it is still a problem.
Can you please show examples how can I achieve this, please? Appreciate.
thank you.
Hi Simon,
I did not think about updating the quantities ... I'll add another event to the CustomerItemCacheBase class for updating / saving and get it into 1.13.1.
http://issues.merchello.com/youtrack/issue/M-908
Hi Rusty,
Aren't there any way how I can check each cart product quantities after the user click on the checkout button to go to the addresses section?
Thank you.
That could be done as a validation task as mentioned above.
Ahh I see.
Can you guide me please how I can add a new task and a new class etc, from where I need to extend etc... as I have already tried and it was not being fired /working
Basically, I want that the cart will be validate once user checkout to go to the addresses page. Here I need to check the quantities of each product and if found a product in cart which has quantity greater the the stock, will be redirected back to the cart, informing the user.
Thank you rusty very much for your patience.
Hi Rusty,
Any idea or help please?
Thank you
Hi Simon
Have you created your own custom task following https://our.umbraco.org/projects/collaboration/merchello/merchello/63357-Inventory-tracking#comment-232800
If so, you should be able to call the Validate() on the basket itself and get the result of your attempt right in your controller and if it returns an failed attempt you could do your redirect with a message you return.
The validation should also be kicking off automatically as soon as your start interacting with the SalePreparation (BasketSalePreparation) object.
Hi Rusty,
I cannot figure it out in order to work. I cannot understand how can I create the custom task and the following.
If you have patience :), could you please show me and explain to me step by step exactly what I should do, because I cannot get it work.
Thank you very much Rusty, in advance.
Kind Regards.
Hey Simon - can you send me a DM on twitter so we can coordinate. I'm thinking it would be easiest if your repository was somewhere I could see (even privately) . If we come up with a solution, it'd be great to be able to use the code as an example "how to" in the new documentation stuff we are working on.
Hi Rusty,
Thank you for your reply. Even for users that might experiencing the same issue,, can you please show how to/guide me how to:
Create my custom task class, namespace, etc... code to check the current quantity of the cart via the existing product quantity
How to reference and initiate the custom task in the umbracoSettings.config
And Eventually how and where to call the validate() method in the basket.
Appreciate any help via examples please. I hope we can get this working once day :)
Thank you rusty once again.
Kind Regards
Hi Simon,
I am trying to put together an example for you - have you written a customer Basket or Checkout controller or are you trying to do this via the controllers that are included in the Bazaar starter kit?
Hi Rusty,
Thank you very much.
I am using the Bazaar starter kit.
Hi Rusty,
I need to decrement inventory depending on payment method:
Credit card: only when payment is ok Bank transfer: on order creation.
The problem is i have commented this line in merchello.config:
Now only have:
But when i fulfill the order in backoffice i get this message: "Shipment #0 created" and no shipment is created. How could i trace what is happening? if i uncomment the line in merchello.config the shipment is created.
Sorry, i have just read the name of the task "RemoveShipmentOrderItemsFromInventoryAndPersistShipmentTask" so if i removed the shipment is not persisted. So i am going to create a new task that persists the shipment without removing inventory!
I got caught by the exact same problem - only you seem to have found it quicker than I did! :)
btw when you remove the task:
you should add a "Persist Shipment Task" in order, as the name says, to persist the shipment, otherwise is not saved.
(just a note for distracted people like me ;) )
Has anyone on this thread dealt with the issue of stock levels not being updated with selling non-physical products? Reading some of the issues above, it sounds like the stock level is only affected when it's associated with shipments. In our case we have no shipments as the products are event tickets (they don't even have a download element, it's just taking a payment for something that's administered offline) in which case our stock levels are never modified.
We're using Merchello 2.5.0 and I can't see any fixes for this issue in the 2.6.0 commits.
Can anyone suggest how to work around this issue and get stock levels updating automatically at some point in the process?
is working on a reply...