umbraco / Umbraco.Commerce.Issues

18 stars 2 forks source link

Price of order and orderlines are 0 in ShippingCalculator #359

Closed yannicsmeets closed 2 years ago

yannicsmeets commented 2 years ago

This might be the same/similar issue as #348.

Describe the bug I added a ShippingCalculator that sets the shipping costs based on the subtotalprice of the order. When calling ShippingMethod.CalculatePrice(), the subtotalPrice is filled correctly. In the checkoutstep after selecting the shippingMethod, the calculation function is called again. This time, the subtotalPrice of the order and the orderLines are 0.

Steps To Reproduce I tested this in the demo store Steps to reproduce the behavior:

  1. Add the following shipping calculator:

    public override Price CalculateShippingMethodPrice(
            ShippingMethodReadOnly shippingMethod,
            Guid currencyId,
            Guid? countryId,
            Guid? regionId,
            TaxRate taxRate,
            ShippingCalculatorContext context)
        {
            var subtotalPrice = context.OrderCalculation?.SubtotalPrice.Value;
            if (subtotalPrice == null || subtotalPrice.WithTax == 0)
            {
                subtotalPrice = context.Order.SubtotalPrice.Value;
            }
    
            var shippingCost = subtotalPrice.WithTax > 10
                ? 5m
                : 10m;
    
            var shippingCostWithoutTax = shippingCost / (1 + taxRate.Value);
            var tax = shippingCost - shippingCostWithoutTax;
    
            var price = new Price(shippingCostWithoutTax, tax, currencyId);
            return price;
        }

    and the following in the DemoStoreComposer:

builder.Services.AddUnique<IShippingCalculator, CustomShippingCalculator>();

  1. Order a product that is more expensive than 10
  2. Go to checkout, fill in the contact information, and go to the shipping information
  3. Note that prices for both methods is 5. Select one and go to the payment information
  4. On the right side, it says the shipping costs are 8.26 (which is 10 with tax)

Expected behavior The shipping costs should remain 5. The subtotalPrice of the order and the orderLines should not be 0 in the shippingCalculator, but should be the same as the price of the item that was selected.

Screenshots The shipping method step: image

After selecting a shipping method: image

Additional context This is tested on the latest commit of the netcore/dev branch of the demo store.

Stack trace of when the prices are not set correctly:

>   Vendr.DemoStore.dll!Vendr.DemoStore.Calculators.CustomShippingCalculator.CalculateShippingMethodPrice(Vendr.Core.Models.ShippingMethodReadOnly shippingMethod, System.Guid currencyId, System.Guid? countryId, System.Guid? regionId, Vendr.Core.Models.TaxRate taxRate, Vendr.Core.Calculators.ShippingCalculatorContext context) Line 43  C#
    Vendr.Core.dll!Vendr.Core.Pipelines.Order.Tasks.CalculateOrderShippingTotalPriceWithoutAdjustmentsTask.Execute(Vendr.Core.Pipelines.Order.OrderCalculationPipelineArgs args)    Unknown
    Vendr.Common.dll!Vendr.Common.Pipelines.InProcPipelineInvoker.Vendr.Common.Pipelines.IPipelineInvoker.Invoke.__next|2_0(Vendr.Common.Pipelines.PipelineArgs e, ref Vendr.Common.Pipelines.InProcPipelineInvoker.<>c__DisplayClass2_0 value) Unknown
    Vendr.Common.dll!Vendr.Common.Pipelines.InProcPipelineInvoker.Vendr.Common.Pipelines.IPipelineInvoker.Invoke(System.Collections.Generic.IEnumerable<Vendr.Common.Pipelines.IPipelineTask> pipelineTasks, Vendr.Common.Pipelines.PipelineArgs args)  Unknown
    Vendr.Common.dll!Vendr.Common.Pipelines.Pipeline.Invoke<Vendr.Core.Pipelines.Order.CalculateOrderPricesPipeline, Vendr.Common.Pipelines.PipelineArgs<Vendr.Core.Models.OrderCalculation>, Vendr.Core.Models.OrderCalculation>(System.Func<Vendr.Common.IUnitOfWork, Vendr.Common.Pipelines.PipelineArgs<Vendr.Core.Models.OrderCalculation>> argsFactory)   Unknown
    Vendr.Common.dll!Vendr.Common.Pipelines.InProcPipelineInvoker.Vendr.Common.Pipelines.IPipelineInvoker.Invoke.__next|2_0(Vendr.Common.Pipelines.PipelineArgs e, ref Vendr.Common.Pipelines.InProcPipelineInvoker.<>c__DisplayClass2_0 value) Unknown
    Vendr.Common.dll!Vendr.Common.Pipelines.InProcPipelineInvoker.Vendr.Common.Pipelines.IPipelineInvoker.Invoke(System.Collections.Generic.IEnumerable<Vendr.Common.Pipelines.IPipelineTask> pipelineTasks, Vendr.Common.Pipelines.PipelineArgs args)  Unknown
    Vendr.Common.dll!Vendr.Common.Pipelines.Pipeline.Invoke<Vendr.Core.Pipelines.Order.CalculateOrderPipeline, Vendr.Core.Pipelines.Order.OrderCalculationPipelineArgs, Vendr.Core.Models.OrderCalculation>(System.Func<Vendr.Common.IUnitOfWork, Vendr.Core.Pipelines.Order.OrderCalculationPipelineArgs> argsFactory) Unknown
    Vendr.Core.dll!Vendr.Core.Calculators.OrderCalculator.CalculateOrder(Vendr.Core.Models.OrderReadOnly order) Unknown
    Vendr.Infrastructure.dll!Vendr.Infrastructure.Persistence.Repositories.OrderRepository.DoProcessDtos(System.Collections.Generic.IEnumerable<Vendr.Infrastructure.Persistence.Dtos.OrderDto> dtos)   Unknown
    Vendr.Infrastructure.dll!Vendr.Infrastructure.Persistence.Repositories.OrderRepository.Get(System.Guid id)  Unknown
    Vendr.Core.dll!Vendr.Core.Services.OrderService.PerformGetState(System.Guid id) Unknown
    Vendr.Core.dll!Vendr.Core.Cache.DefaultEntityStatePolicyCache<Vendr.Core.Models.OrderState, System.Guid>.Get(System.Guid id, System.Func<System.Guid, Vendr.Core.Models.OrderState> performGet, System.Func<System.Guid[], System.Collections.Generic.IEnumerable<Vendr.Core.Models.OrderState>> performGetAll) Unknown
    Vendr.Core.dll!Vendr.Core.Services.OrderService.GetOrder(System.Guid id)    Unknown
    Vendr.Core.dll!Vendr.Core.Session.SessionManager.GetCurrentOrder(System.Guid storeId)   Unknown
    Vendr.Core.dll!Vendr.Core.Session.SessionManager.CheckAndMoveOrderIds(System.Guid storeId)  Unknown
    Vendr.Core.dll!Vendr.Core.Session.SessionManager.CheckAndMoveSessionOrders()    Unknown
    Vendr.Web.dll!Vendr.Web.Attributes.CheckAndMoveSessionOrdersMvcActionFilterAttribute.OnResultExecuting(Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext context) Unknown
    Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute.OnResultExecutionAsync(Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext context, Microsoft.AspNetCore.Mvc.Filters.ResultExecutionDelegate next) Unknown
    Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<Microsoft.AspNetCore.Mvc.Filters.IResultFilter, Microsoft.AspNetCore.Mvc.Filters.IAsyncResultFilter>(ref Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.State next, ref Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Scope scope, ref object state, ref bool isCompleted)  Unknown
    Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeNextResultFilterAsync<Microsoft.AspNetCore.Mvc.Filters.IResultFilter, Microsoft.AspNetCore.Mvc.Filters.IAsyncResultFilter>()    Unknown
    Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<Microsoft.AspNetCore.Mvc.Filters.IResultFilter, Microsoft.AspNetCore.Mvc.Filters.IAsyncResultFilter>(ref Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.State next, ref Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Scope scope, ref object state, ref bool isCompleted)  Unknown
    Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters() Unknown
    Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.State next, ref Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Scope scope, ref object state, ref bool isCompleted) Unknown
    Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeNextResourceFilter()    Unknown
    Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.State next, ref Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Scope scope, ref object state, ref bool isCompleted) Unknown
    Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()   Unknown
    Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeAsync() Unknown
    Microsoft.AspNetCore.Mvc.Core.dll!Microsoft.AspNetCore.Mvc.Routing.ActionEndpointFactory.CreateRequestDelegate.AnonymousMethod__0(Microsoft.AspNetCore.Http.HttpContext context)    Unknown
    Microsoft.AspNetCore.Routing.dll!Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext httpContext)  Unknown
    Umbraco.Web.Website.dll!Umbraco.Cms.Web.Common.Middleware.BasicAuthenticationMiddleware.InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next)  Unknown
    Microsoft.AspNetCore.Http.Abstractions.dll!Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.UseMiddlewareInterface.AnonymousMethod__1(Microsoft.AspNetCore.Http.HttpContext context)    Unknown
    Umbraco.Web.BackOffice.dll!Umbraco.Cms.Web.BackOffice.Middleware.BackOfficeExternalLoginProviderErrorMiddleware.InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next)  Unknown
    Microsoft.AspNetCore.Http.Abstractions.dll!Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.UseMiddlewareInterface.AnonymousMethod__1(Microsoft.AspNetCore.Http.HttpContext context)    Unknown
    Microsoft.AspNetCore.Http.Abstractions.dll!Microsoft.AspNetCore.Builder.Extensions.MapMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context)  Unknown
    Microsoft.AspNetCore.Session.dll!Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context)   Unknown
    Microsoft.AspNetCore.Localization.dll!Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context) Unknown
    Microsoft.AspNetCore.Authorization.Policy.dll!Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context)  Unknown
    Microsoft.AspNetCore.Authentication.dll!Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context)  Unknown
    Microsoft.AspNetCore.StaticFiles.dll!Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context)    Unknown
    Microsoft.AspNetCore.StaticFiles.dll!Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context)    Unknown
    SixLabors.ImageSharp.Web.dll!SixLabors.ImageSharp.Web.Middleware.ImageSharpMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context) Unknown
    Microsoft.AspNetCore.Diagnostics.dll!Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context)   Unknown
    MiniProfiler.AspNetCore.dll!StackExchange.Profiling.MiniProfilerMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context)    Unknown
    Umbraco.Web.Common.dll!Umbraco.Cms.Web.Common.Middleware.UmbracoRequestMiddleware.InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next)    Unknown
    Microsoft.AspNetCore.Http.Abstractions.dll!Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.UseMiddlewareInterface.AnonymousMethod__1(Microsoft.AspNetCore.Http.HttpContext context)    Unknown
    Umbraco.Web.Common.dll!Umbraco.Cms.Web.Common.Middleware.PreviewAuthenticationMiddleware.InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) Unknown
    Microsoft.AspNetCore.Http.Abstractions.dll!Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.UseMiddlewareInterface.AnonymousMethod__1(Microsoft.AspNetCore.Http.HttpContext context)    Unknown
    Umbraco.Web.Common.dll!Umbraco.Cms.Web.Common.Middleware.UmbracoRequestLoggingMiddleware.InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next) Unknown
    Microsoft.AspNetCore.Http.Abstractions.dll!Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.UseMiddlewareInterface.AnonymousMethod__1(Microsoft.AspNetCore.Http.HttpContext context)    Unknown
    Vendr.Umbraco.Web.dll!Vendr.Umbraco.Web.Mvc.VendrRequestBufferingMiddleware.InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Http.RequestDelegate next)  Unknown
    Microsoft.AspNetCore.Http.Abstractions.dll!Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.UseMiddlewareInterface.AnonymousMethod__1(Microsoft.AspNetCore.Http.HttpContext context)    Unknown
    Microsoft.AspNetCore.Diagnostics.dll!Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context)    Unknown
    Microsoft.AspNetCore.HostFiltering.dll!Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context) Unknown
    Microsoft.WebTools.BrowserLink.Net.dll!Microsoft.WebTools.BrowserLink.Net.BrowserLinkMiddleware.ExecuteWithFilterAsync(Microsoft.WebTools.BrowserLink.Net.IHttpSocketAdapter injectScriptSocket, string requestId, Microsoft.AspNetCore.Http.HttpContext httpContext)   Unknown
    Microsoft.WebTools.BrowserLink.Net.dll!Microsoft.WebTools.BrowserLink.Net.BrowserLinkMiddleware.InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context)  Unknown
    Microsoft.AspNetCore.Watch.BrowserRefresh.dll!Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserRefreshMiddleware.InvokeAsync(Microsoft.AspNetCore.Http.HttpContext context) Unknown
    Microsoft.AspNetCore.Http.Abstractions.dll!Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext context)  Unknown
    Microsoft.AspNetCore.Server.IIS.dll!Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT<Microsoft.AspNetCore.Hosting.HostingApplication.Context>.ProcessRequestAsync()   Unknown
    Microsoft.AspNetCore.Server.IIS.dll!Microsoft.AspNetCore.Server.IIS.Core.IISHttpContext.HandleRequest() Unknown
    Microsoft.AspNetCore.Server.IIS.dll!Microsoft.AspNetCore.Server.IIS.Core.IISHttpContext.Execute()   Unknown
    System.Private.CoreLib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()  Unknown

Vendr version: 2.1.2 (The store uses 2.1.0, so this was updated by me)

mattbrailsford commented 2 years ago

Hey @yannicsmeets, you say this is running on the demo store, do you have a fork / branch with this in place so I can run the test myself?

yannicsmeets commented 2 years ago

Definitely: https://github.com/yannicsmeets/vendr-demo-store/tree/%23359

mattbrailsford commented 2 years ago

Ok, so I know the issue.

So basically, we calculate prices in a certain order, first of all calculating the normal price of each item (without adjustments) and then we apply any adjustments once this initial calculation is done.

image

It's this adjusted prices that is stored within the OrderCalculation?.SubtotalPrice.Value property and hence why in your calculator this value is zero as this is set in cycle 2 and your code is in cycle 1. The value you have access to in your calculator which is part of that first cycle is the OrderCalculation?.SubtotalPrice.WithoutAdjustments property which will give you the base prices without and adjustments applied.

mattbrailsford commented 2 years ago

I've made a change in the order calculation pipeline such that when we calculate the WithoutAdjustments price (on all relevant price properties), we temporarily set the .Value price too, even though this will shortly get overwritten by the actual calculation for that price.

This should mean that even though you are in cycle 1 of the calculation process, you can still access the .Value property and it's value will be set to the value that is known at that time (which may later change when discounts etc are calculated) but it at least means you don't have to fully understand the calculation process to be able to access the correct price.

That update will be in 2.1.3 for which there is a beta build available on our unstable feed if you wish to test it https://nuget.outfield.digital/unstable/vendr/v3/index.json

mattbrailsford commented 2 years ago

2.1.3 has now been released with this fix in. Thanks for reporting 👍