Umbraco Commerce
CMSCloudHeartcoreDXP
15.latest
15.latest
  • Umbraco Commerce Documentation
  • Release Notes
    • v15.1.0-Rc
    • v15.0.0-Rc
  • Commerce Products
    • Commerce Packages
    • Commerce Payment Providers
    • Commerce Shipping Providers
  • Getting Started
    • Requirements
    • Installation
    • Licensing
    • Configuration
    • User Interface
  • Upgrading
    • Upgrading Umbraco Commerce
    • Version Specific Upgrade Notes
    • Migrate from Vendr to Umbraco Commerce
      • Migrate Umbraco Commerce Checkout
      • Migrate custom Payment Providers
  • Tutorials
    • Build a Store in Umbraco using Umbraco Commerce
      • Installation
      • Creating a Store
        • Configuring your Store
      • Creating your first Product
      • Implementing a Shopping Cart
        • Using the Umbraco.Commerce.Cart Drop-in Shopping Cart
        • Creating a Custom Shopping Cart
      • Implementing a Checkout Flow
        • Using the Umbraco.Commerce.Checkout Drop-in Checkout Flow
        • Creating a Custom Checkout Flow
      • Configuring Store Access Permissions
  • How-To Guides
    • Overview
    • Configure SQLite support
    • Use an Alternative Database for Umbraco Commerce Tables
    • Customizing Templates
    • Configuring Cart Cleanup
    • Limit Order Line Quantity
    • Implementing Product Bundles
    • Implementing Member Based Pricing
    • Implementing Dynamically Priced Products
    • Implementing Personalized Products
    • Implementing a Currency Switcher
    • Building a Members Portal
    • Order Number Customization
    • Sending Payment Links to Customers
    • Create an Order via Code
  • Key Concepts
    • Get to know the main features
    • Base Currency
    • Calculators
    • Currency Exchange Rate Service Provider
    • Dependency Injection
    • Discount Rules / Rewards
    • Events
      • List of validation events
      • List of notification events
    • Fluent API
    • Order Calculation State
    • Payment Forms
    • Payment Providers
    • Pipelines
    • Price/Amount Adjustments
    • Price Freezing
    • Product Adapters
    • Product Bundles
    • Product Variants
      • Complex Variants
    • Properties
    • ReadOnly and Writable Entities
    • Sales Tax Providers
    • Search Specifications
    • Settings Objects
    • Shipping Package Factories
    • Shipping Providers
    • Shipping Range/Rate Providers
    • Tax Sources
    • UI Extensions
      • Analytics Widgets
      • Entity Quick Actions
      • Order Line Actions
      • Order Properties
      • Order Collection Properties
      • Order Line Properties
      • Store Menu Items
    • Umbraco Properties
    • Unit of Work
    • Umbraco Commerce Builder
    • Webhooks
  • Reference
    • Stores
    • Shipping
      • Fixed Rate Shipping
      • Dynamic Rate Shipping
      • Realtime Rate Shipping
    • Payments
      • Configure Refunds
      • Issue Refunds
    • Taxes
      • Fixed Tax Rates
      • Calculated Tax Rates
    • Storefront API
      • Endpoints
        • Order
        • Checkout
        • Product
        • Customer
        • Store
        • Currency
        • Country
        • Payment method
        • Shipping method
        • Content
    • Management API
    • Go behind the scenes
    • Telemetry
Powered by GitBook
On this page
  • Capturing User Input
  • Storing the User Input
  • Calculating the Order Line Price
  • Backoffice UI

Was this helpful?

Edit on GitHub
Export as PDF
  1. How-To Guides

Implementing Dynamically Priced Products

Learn how to implement dynamically priced products in Umbraco Commerce.

PreviousImplementing Member Based PricingNextImplementing Personalized Products

Last updated 5 days ago

Was this helpful?

Sometimes products do not have a fixed price. Depending on the customer's requirements, the price of the product can change. Examples could be window blinds that a priced based on the size of the window, or wallpaper that is priced by the meter.

This guide shows you how to implement dynamically priced products in Umbraco Commerce.

This guide is not a direct follow-on from the . It is assumed that your store is set up in a similar structure.

Capturing User Input

Add a new field on the product's frontend page, to capture the desired length we want to purchase.

The selected length will reflect on the cart value.

To provide the correct calculations for an order, the captured data will need to go through two different processes behind the scenes:

  • Store the user input against the order line property.

  • Implement a custom order line calculator to calculate the prices based on the user input.

Storing the User Input

  1. Add a new property to the AddToCartDto class to capture the length.

AddToCartDto.cs
public class AddToCartDto
{
    ...
    public string? Length { get; set; }
}
CartSurfaceController.cs
[HttpPost]
public async Task<IActionResult> AddToCart(AddToCartDto postModel)
{
    try
    {
        await _commerceApi.Uow.ExecuteAsync(async uow =>
        {
            var store = CurrentPage.GetStore();
            var order = await _commerceApi.GetOrCreateCurrentOrderAsync(store.Id)
                .AsWritableAsync(uow)
                .AddProductAsync(postModel.ProductReference, decimal.Parse(postModel.Quantity), new Dictionary<string, string>{
                    { "length", postModel.Length.ToString() }
                });

            await _commerceApi.SaveOrderAsync(order);

            uow.Complete();
        });
    }
    catch (ValidationException ex)
    {
        ...
    }
    ...
}

Calculating the Order Line Price

We will calculate the price/tax rate of a given order line by multiplying the specified length by the unit price. This is done using a custom order line calculator.

  1. Create a new class that implements the IOrderLineCalculator interface.

SwiftOrderLineCalculator.cs
public class SwiftOrderLineCalculator : IOrderLineCalculator
{
    private ITaxService _taxService;
    private IProductPriceFreezerService _productPriceFreezerService;

    public SwiftOrderLineCalculator(
        ITaxService taxService,
        IProductPriceFreezerService productPriceFreezerService)
    {
        _taxService = taxService;
        _productPriceFreezerService = productPriceFreezerService;
    }

    public async Task<Attempt<TaxRate>> TryCalculateOrderLineTaxRateAsync(OrderReadOnly order, OrderLineReadOnly orderLine, TaxSource taxSource, TaxRate fallbackTaxRate, OrderLineCalculatorContext context = null, CancellationToken cancellationToken = default)
    {
        order.MustNotBeNull(nameof(order));
        orderLine.MustNotBeNull(nameof(orderLine));

        TaxRate taxRate = fallbackTaxRate;

        if (orderLine.TaxClassId != null)
        {
            taxRate = (await _taxService.GetTaxClassAsync(orderLine.TaxClassId.Value)).GetTaxRate(taxSource);
        }

        return Attempt.Succeed(taxRate);
    }

    public async Task<Attempt<Price>> TryCalculateOrderLineUnitPriceAsync(OrderReadOnly order, OrderLineReadOnly orderLine, Guid currencyId, TaxRate taxRate, OrderLineCalculatorContext context = null, CancellationToken cancellationToken = default)
    {
        order.MustNotBeNull(nameof(order));
        orderLine.MustNotBeNull(nameof(orderLine));

        var numberOfMeters = order.Properties.TryGetValue(Constants.OrderProperties.Length, out var propertyValue)
            ? propertyValue.Value
            : string.Empty;

        var unitPrice = order.IsNew
            ? (await _productPriceFreezerService.GetProductPriceAsync(order.StoreId, order.Id, orderLine.ProductReference, orderLine.ProductVariantReference, currencyId)).ProductPrice.Value
            : (await _productPriceFreezerService.GetOrCreateFrozenProductPriceAsync(order.StoreId, order.Id, orderLine.ProductReference, orderLine.ProductVariantReference, currencyId)).Value;

        var price = !string.IsNullOrEmpty(numberOfMeters) && int.TryParse(numberOfMeters, out int result)
            ? result * unitPrice
            : orderLine.UnitPrice;

        var x = Price.Calculate(price, taxRate.Value, currencyId);

        return Attempt.Succeed(Price.Calculate(price, taxRate.Value, currencyId));
    }
}
  1. Register the custom calculator in the Startup.cs file or in an IComposer.

SwiftShopComposer.cs
internal class SwiftShopComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.AddUnique<IOrderLineCalculator, SwiftOrderLineCalculator>();
    }
}

Backoffice UI

A useful extra step is to expose the captured data in the order's details in the Backoffice.

Create a new umbraco-package.json file in a folder in the App_Plugins directory in the root of your project and add the following code:

umbraco-package.json
{
    "name": "SwiftShop",
    "extensions": [
        {
            "type": "ucOrderLineProperty",
            "alias": "Uc.OrderLineProperty.ProductLength",
            "name": "Product Length",
            "weight": 400,
            "meta": {
                "propertyAlias": "productLength",
                "showInOrderLineSummary": true,
                "summaryStyle": "inline",
                "editorUiAlias": "Umb.PropertyEditorUi.TextBox",
                "labelUiAlias": "Umb.PropertyEditorUi.Label"
            }
        },
        {
            "type": "localization",
            "alias": "Uc.OrderLineProperty.ProductLength.EnUS",
            "name": "English",
            "meta": {
                "culture": "en",
                "localizations": {
                    "section": {
                        "ucProperties_productLengthLabel": "Length",
                        "ucProperties_productLengthDescription": "Customer product ordered length"
                    }
                }
            }
        }
    ]
}

The length property is now displayed in the order details in the Backoffice.

Update the AddToCart method of the CartSurfaceController to store the length against the order line as a .

This is implemented as a custom .

property
UI Extension
getting started tutorial
Length Input
Calculated Cart Values
Order Details