Cart Management from frontend

Some solutions – like CPQ (Configure, Price, Quote) solutions – need to be implemented in a way which allows users to work with carts from frontend, often multiple named carts.

In this guide we will implement a simple setup where the customer can work with carts from frontend – in concrete terms:

  • Show a list of carts to a user
  • Create new empty carts
  • Switch between open carts
  • Change cart states from frontend

We will also touch upon how users with impersonation rights can apply custom discounts to open carts.

Most of the functionality implemented in this guide uses cart commands – and while cart commands can be used anywhere, some of the more advanced functionalities depend on the customer center app. There are two ways to tackle this:

  1. Implement this funcitonality inside a customer center template
  2. Implement this functionality using the more modern customer experience center, and use javascript to post to a customer center elsewhere on the solution when required

In this guide we will go for #2, as the Customer Experience Center is much nicer to work with than the old customer center.

The first thing you want to do is show a list of open carts to a user:

  1. Add a Customer Experience Center app to a paragraph
  2. Under Order type select Carts
  3. Under Retrieve list based on select Own orders and orders made by users that current user can impersonate
  4. Under List select a field to sort by – e.g. ID – and an appropriate sort order
  5. Under Templates select or create a simple template for rendering the list of carts associated with the user – it could look like this:
RAZOR
@using Dynamicweb.Rendering @using Dynamicweb.Ecommerce.Frontend @inherits ViewModelTemplate<OrderListViewModel> <table class="table"> <tr> <th>Cart ID</th> <th>Name</th> <th>Current total</th> </tr> @foreach (var cart in Model.Orders) { <tr> <td>@cart.Id</td> <td>@cart.DisplayName</td> <td>@cart.Price.PriceFormatted</td> </tr> } </table>

Now that we have the list of carts associated with the user and any users that the current user can impersonate we want to see more details about the currently active cart.

This can be done very easily by fetching the cart object using the API and rendering details about it – here in a sort of cart-like view:

RAZOR
@using Dynamicweb.Rendering @using Dynamicweb.Ecommerce.Frontend @inherits ViewModelTemplate<OrderListViewModel> <h2>Current Cart</h2> @{ var currentcart = Dynamicweb.Ecommerce.Common.Context.Cart; } @if (currentcart != null) { <table class="table"> <thead> <tr> <th>Product name</th> <th>Quantity</th> <th>Price</th> </tr> </thead> <tbody> @foreach (var orderline in currentcart.OrderLines) { <tr> <td>@orderline.ProductName</td> <td>@orderline.Quantity</td> <td>@orderline.Price</td> </tr> } <tr> <td><b>Total with VAT</b></td> <td></td> <td><b>@currentcart.Price</b></td> </tr> </tbody> <tfoot> <tr> <td> <div><em><small><b>AutoID:</b> @currentcart.AutoId</small></em></div> <div><em><small><b>CartID:</b> @currentcart.Id</small></em></div> <div><em><small><b>Creation Date:</b> @currentcart.Date</small></em></div> <div><em><small><b>Display Name:</b> @currentcart.DisplayName</small></em></div> <div><em><small><b>Language ID:</b> @currentcart.LanguageId</small></em></div> <div><em><small><b>Shop ID:</b> @currentcart.ShopId</small></em></div> </td> <td></td> <td> </td> </tr> </tfoot> </table> } else { <div>There is no active cart</div> } <h2>Other carts</h2> <table class="table"> <tr> <th>Cart ID</th> <th>Name</th> <th>Current total</th> </tr> @foreach (var cart in Model.Orders) { <tr> <td>@cart.Id</td> <td>@cart.DisplayName</td> <td>@cart.Price.PriceFormatted</td> </tr> } </table>

Now that you can see information about the current cart we can implement some of the common cart management operations:

  • (Re)naming
  • Copying
  • Archiving

This is, as mentioned previously, done by using cart management cart commands – specifically setname, copy, and setname. In this example we’ve created two buttons for copying & archiving a cart, and a form for setting the display name to something else:

RAZOR
@using Dynamicweb.Rendering @using Dynamicweb.Ecommerce.Frontend @inherits ViewModelTemplate<OrderListViewModel> <h2>Current Cart</h2> @{ var currentcart = Dynamicweb.Ecommerce.Common.Context.Cart; } @if (currentcart != null) { <table class="table"> <thead> <tr> <th>Product name</th> <th>Quantity</th> <th>Price</th> </tr> </thead> <tbody> @foreach (var orderline in currentcart.OrderLines) { <tr> <td>@orderline.ProductName</td> <td>@orderline.Quantity</td> <td>@orderline.Price</td> </tr> } <tr> <td><b>Total with VAT</b></td> <td></td> <td><b>@currentcart.Price</b></td> </tr> </tbody> <tfoot> <tr> <td> <div><em><small><b>AutoID:</b> @currentcart.AutoId</small></em></div> <div><em><small><b>CartID:</b> @currentcart.Id</small></em></div> <div><em><small><b>Creation Date:</b> @currentcart.Date</small></em></div> <div><em><small><b>Display Name:</b> @currentcart.DisplayName</small></em></div> <div><em><small><b>Language ID:</b> @currentcart.LanguageId</small></em></div> <div><em><small><b>Shop ID:</b> @currentcart.ShopId</small></em></div> </td> <td></td> <td> <a class="btn btn-primary" href='@Pageview.SearchFriendlyUrl?cartcmd=copy&CartId=@currentcart.Id&CartName=Copy_of_@currentcart.Id'>Copy</a> <a class="btn btn-danger" href='@Pageview.SearchFriendlyUrl?cartcmd=archive'>Archive</a> <br /> <br /> <form class="form-inline" method="post"> <input class="form-control" type="text" name="CartName" id="CartName" value="" /> <input type="hidden" name="CartID" id="CartID" value="@currentcart.Id" /> <button class="btn btn-primary" type="submit" name="CartCmd" value="setname">Set name</button> </form> </td> </tr> </tfoot> </table> } else { <div>There is no active cart</div> } <h2>Other carts</h2> <table class="table"> <tr> <th>Cart ID</th> <th>Name</th> <th>Current total</th> </tr> @foreach (var cart in Model.Orders) { <tr> <td>@cart.Id</td> <td>@cart.DisplayName</td> <td>@cart.Price.PriceFormatted</td> </tr> } </table>

Now that you can do things to the active cart, it’s time to make it possible to set the active cart. This is done using the setcart command with a cartid parameter – here implemented in the list of carts.

RAZOR
(…) <h2>Other carts</h2> <table class="table"> <tr> <th>Cart ID</th> <th>Name</th> <th>Current total</th> <th></th> </tr> @foreach (var cart in Model.Orders) { <tr> <td>@cart.Id</td> <td>@cart.DisplayName</td> <td>@cart.Price.PriceFormatted</td> <th><a class="btn btn-primary" href='@Pageview.SearchFriendlyUrl?cartcmd=setcart&CartID=@cart.Id'>Setcart</a></th> </tr> } </table>

Normally, carts are created when there is no active cart and a customer adds a product to cart – but you can also create new empty carts from frontend using the createnew cart command. This cart command takes two optional parameters; a CartName property and a CartUserId property. The CartUserId property must be set to either the id of the current user or a user he or she can impersonate.

Here we’ve added a simple form for creating a named cart associated with the current user – in more advanced setups you could make it possible to select a user to impersonate and create carts on their behalf.

RAZOR
(…) @if (Pageview.User != null) { <h2>Create new cart</h2> <form class="form-inline" method="post"> <input type="hidden" id="CartUserId" name="CartUserId" value="@Pageview.User.ID" /> <input class="form-control" type="text" id="CartName" name="CartName" value="" /> <button class="btn btn-primary" type="submit" name="CartCmd" value="createnew">Create new cart</button> </form> }

On some solutions you may want to use cart states to create a sort of workflow for carts to move through before being checked out. Currently this feature depends on submitting a form with specific parameters to a customer center app. As mentioned in the introduction to this guide, we’ve chosen to implement this by using javascript to post a form to a customer center app elsewhere on the solution. You can also choose to implement the whole cart management feature in a customer center app template – that’s up to you.

In this example we do the following:

  1. Use the API to fetch cart states based on a property on the cart object
  2. Use a bit of JS to submit form data to a page with a customer center app but prevent redirect and instead reload the current page
  3. Render a form which makes it possible to change the cart state by posting to the page with the customer center app (ID=2) with a parameter required by the customer center (CC1=Carts)
RAZOR
(…) <tr> <td><b>Cart state:</b></td> <td></td> <td> @{ var cartstates = Dynamicweb.Ecommerce.Orders.OrderState.GetAllCartStates(currentcart.OrderState.OrderFlowId); } <script> document.addEventListener('DOMContentLoaded', function () { document.querySelector('#ChangeDraftStatusForm').addEventListener('submit', function (event) { var data = this; fetch(data.getAttribute('action'), { method: data.getAttribute('method'), body: new FormData(data) }).then(res => res.text()) .then(function (data) { window.location.reload(); }); event.preventDefault(); }); }); </script> <form class="form-inline" method="post" id="ChangeDraftStatusForm" action="/Default.aspx?ID=2&CC1=Carts"> <input type="hidden" name="CartID" id="CartID" value="@currentcart.Id" /> <select class="form-control" name="StateId" id="StateID"> @foreach (var state in cartstates) { if (state.Name == @currentcart.OrderState.Name) { <option value="@state.Id" selected>@state.Name</option> } else { <option value="@state.Id">@state.Name</option> } } </select> <button class="btn btn-primary pull-right" type="submit">Change state</button> </form> </td> </tr> (…)

The final thing we will add to this guide is how to apply custom discounts from frontend. This is typically implemented when you have a solution with key account managers or sales people who also interact with orders from frontend – by creating, receiving and/or processing carts from customers.

To implement this you should:

  • Create discounts of the type custom amount discount and custom percentage discount
  • Log in as a user with impersonation rights

You can then use the setdiscount cart command to assign either a percentage based or a fixed amount based discount to a cart, depending on the parameter used:

RAZOR
@if (Pageview.User.SecondaryUsers.Count > 0) { <!--Fixed discount--> <tr> <td><b>Apply fixed discount: </b> (@currentcart.CurrencyCode)</td> <td></td> <td> <form class="form-inline" method="post"> <input type="hidden" name="CartID" id="CartID" value="@currentcart.Id" /> <input class="form-control" type="number" name="OrderDiscount" id="OrderDiscount" value="0" /> <button class="btn btn-primary" type="submit" name="CartCmd" value="setdiscount">Apply</button> </form> </td> </tr> <!--Percentage discount--> <tr> <td><b>Apply % discount: </b></td> <td></td> <td> <form class="form-inline" method="post"> <input type="hidden" name="CartID" id="CartID" value="@currentcart.Id" /> <input class="form-control" type="number" name="OrderDiscountPercentage" id="OrderDiscountPercentage" value="0" /> <button class="btn btn-primary" type="submit" name="CartCmd" value="setdiscount">Apply</button> </form> </td> </tr> }