In this article, we will review and compare Sitecore Experience Commerce carts and OrderCloud unsubmitted orders to facilitate developers looking to transition from XC to OrderCloud as well as identify a path for migration of existing XC solutions to OrderCloud.
We will look at a high-level comparison of architecture, functionality, and data models, with greater focus on transitioning from Sitecore Experience Commerce, meaning identifying a close path to parity in OrderCloud, using the Habitat catalog and SXA Storefront.
Conceptual Architecture and Features
Carts vs Unsubmitted Orders and Carts
Architecture Overview
In XC, the cart entity stores information about the order to be placed. When an order is placed, the contents of a cart is copied over into a new order entity with an initial order status of Pending
.
OrderCloud has two approaches in handling the cart/order lifecycle. The solution can leverage unsubmitted orders or the OrderCloud’s cart resource.
Unsubmitted Orders
In OrderCloud, an order represents both the cart and order within the cart/order lifecycle, using the Status
property to identify its current state. The initial status of the order is Unsubmitted
, which is effectively the XC cart equivalent. When the order is submitted, the order will then transition its status to Open
, matching the XC order with status of Pending
.
It is also possible that the OrderCloud order submitted status will be set to AwaitingApproval
, however this status is not relevant for the SXA storefront comparison.
Referencing figure 2, there are a few important details regarding the XC and SXA cart to order architecture in relation to OrderCloud’s architecture:
- SXA sees that a customer only interacts with a single cart per storefront, by using the naming convention, “Default<customer id><storefront name>“, to identify the cart id and is the only means of a relationship that the customer has with the cart in the XC database.
- The Commerce Engine’s
/carts
endpoint, which retrieves the cart, requires the cartId parameter and does not have consideration for the user context. - While order ids must be unique, orders are created with randomly generated ids and are not copied from the cart’s fixed id. Orders are persisted, accompanied with a list entry, which represent the relationship between the customer and the orders they have placed.
- During order creation, after the cart contents has been copied to the order, the cart is emptied to allow it to be used for subsequent order placement.
In comparison to OrderCloud’s architecture, orders are tightly coupled to their owners via the FromUserID
property, and as both order ids in XC and OrderCloud are generated, there is no chance for Id conflicts.
- In managing a single order, the most recent unsubmitted order is considered the active ‘cart’ and as such does not need to rely on an order id to identify the cart as its relationship to the user as it’s tightly coupled to the owner via the
FromUserID
property. - The
/me/orders
endpoint retrieves the order based on user context, which is extracted from the auth token, rather than the order id. - The order id is also generated by default, so there’s no chance of an ID conflict when allowing the platform to generate it.
- There is no implementation concerns around migrating a cart to an order.
OrderCloud Carts
As an alternate approach to tracking and utilising the unsubmitted orders in an implementation, OrderCloud also provides a cart resource, which can simplify the implementation as it behaves much the same as the orders resource without the need for identifying, persisting, and passing the order ID for requests. Instead, the cart resource will point to the latest unsubmitted orders or create a new order if no unsubmitted orders exist for the user. When the cart is submitted, the next unsubmitted order, if available, will become the active cart.
More information can be found in Introducing the Cart API.
Choosing Between Orders and Cart Implementations
When deciding whether to use the orders or cart resource in an implementation, the cart resource will typically be the preferred implementation practice for the majority of storefronts as it can handle a single cart or multiple carts for profiled users, allowing the implementation to switch the order context for the user.
For marketplaces requiring multiple storefronts, e.g. a storefront per brand or storefront per country, where user accounts are shared across storefronts, the orders resource allows the storefront to control the order context for each user as seen in figure 4.
Storefront User Journey
In figure 5, we see a high-level user journey for the SXA storefront alongside the OrderCloud equivalent. While OrderCloud’s user journey appears to be longer, this is only due to order requiring explicit creation and XC consolidating shipping address and shipping method into fulfillment details, and billing address and payment into payment details. Ultimately, the user journey is matched and there are no sacrifices necessary.
Viewing the Cart/Order
In XC, the GET /Carts({cartId})
endpoint is typically called with the $expand
query parameter to retrieve all cart components, cart lines and their components for the full view of the cart.
In OrderCloud, the RESTful API provides individual endpoints for extracting order data at more granular levels. For example the order details can be retrieved via the GET /orders/{direction}/{orderID}
or GET /cart
endpoint, but it won’t provide the line item details, which can be retrieved in bulk using GET /orders/{direction}/{orderID}/lineitems
or GET /cart/lineitems
, or individually via GET /orders/{direction}/{orderID}/lineitems/{lineitemID}
or GET /cart/lineitems/{lineitemID}
.
Alternately, OrderCloud also provides GET /orders/{direction}/{orderID}/worksheet
and GET /cart/worksheet
endpoints, which return the order object along with all line items and all responses from integration events. providing a more complete view, however some objects such as price schedule details will still require additional requests from the appropriate resources, which are typically only retrieved on demand as required.
Cart vs Order Totals
In the table below, we see a comparison between the cart line totals side by side with the equivalent line item totals. The notable differences are as follows:
- XC
Adjustments
may include taxes, fulfillment fees, and promotion discounts, whereas OrderCloud breaks out the each adjustment classification at the order level. - As calculations with adjustments are calculated differently at the line-level and cart/order level, calculating the
GrandTotal
/Total
may result in differently between the platforms. - The
PaymentsTotal
is not available on the order, however the payments total can be retrieved and calculated by the middleware, using OrderCloud’sGET /orders/{direction}/{orderID}/payments
orGET /cart/payments
endpoint.
Cart (XC) | Calculation | Order (OC) | Calculation | Match |
---|---|---|---|---|
SubTotal | Sum of all line-level SubTotal s. | Subtotal | Sum of all LineItem.LineSubtotals. | Yes |
AdjustmentsTotal | Sum of all cart-level Adjustments . | ShippingCost | Handled by middleware via OrderCalculate integration event. | No |
TaxCost | Handled by middleware via OrderCalculate integration event. | No | ||
PromotionDiscount | Sum of all order-level and line item-level promotion discount amounts applied. | No | ||
GrandTotal | SubTotal + sum of all cart-level adjustments flagged with IncludeInGrandTotal being true + sum of all line-level AdjustmentsTotal s | Total | Subtotal + TaxCost + ShippingCost – PromotionDiscount . | No |
PaymentsTotal | Sum of all PaymentComponent Amounts. | N/A |
Cart Lines vs Line Items
Adding a Cart Line vs Line Item
From an API perspective, to add a cart lines in XC, the request requires the cartId
, quantity
, and itemId
, consisting of catalogId
, sellableItemId
, and variationId
.
The catalogId
is intended to resolve pricing and promotions for sellable items and their variants, based on the catalog associations to price books and promotion books as per the XC architecture.
POST https://authoring.engine.dev/api/AddCartLine HTTP/1.1 { "cartId": "{orderId}", "itemId": "{catalogId}|{sellableItemId}|{variantId}", "quantity": 1 }
Adding a line item in OrderCloud is much the same as seen in the example request below. orderId
parameter replaces the cartId
, while the ProductId
equates to the sellableItemId
. The catalogId
however, is not required as pricing and promotions are not dependent on catalog assignments, and rather than passing the variantId
, the individual product specs with OptionID
s are specified, ultimately resolving to variant equivalent, but also caters for specialised open text specs that OrderCloud supports.
POST https://sandboxapi.ordercloud.io/v1/orders/outgoing/{orderId}/lineitems HTTP/1.1 { "ProductID": "{productId}", "Specs": [ { "SpecID": "{specId}", "OptionID": "{specOptionId}" }, { "SpecID": "{specId2}", "OptionID": "{specOptionId2}" } ] }
Cart Line Rollup
In the SXA storefront, when adding a sellable item/variation to the cart that has already been added, the quantity will be updated to reflect the initial cart’s quantity plus the quantity added. This is known as cart line rollup and is controlled by the RollupCartLinesPolicy.Rollup
property in the Commerce Engine’s environment role configuration.
As line rollup is not a native feature of OrderCloud’s RESTful API, this functionality could be implemented in the middleware. Instead adding line items will create a new line item in the order, in the same manner that XC will with rollup disabled.
Line-Level Pricing
XC’s cart lines contain the UnitListPrice
and the SellPrice
on the PurchaseOptionMoneyPolicy
, which can be thought of as “was” and “now” pricing respectively.
The SellPrice
can evaluate to the same value as the UnitListPrice, to which the SXA storefront will treat this as a standard price.
"Policies": [ { "@odata.type": "#Sitecore.Commerce.Plugin.Pricing.PurchaseOptionMoneyPolicy", "PolicyId": "5e6c1f42c7c94bc4b79696e716b6cda5", "Models": [], "Expires": "2019-04-22T13:13:18.8117816Z", "SellPrice": { "CurrencyCode": "USD", "Amount": 6 }, "FixedSellPrice": false } ], "UnitListPrice": { "CurrencyCode": "USD", "Amount": 2429.99 }
The MessagesComponent
, also shows the audit trail detailing how the cart line’s unit list price and sell price were resolved, which are evaluated from ListPrice
s and Price Card Snapshots at the sellable item and item variation levels.
Cart Line MessageComponent Snippet
{
"@odata.type": "#Sitecore.Commerce.Core.MessagesComponent",
"Id": "7e6bc27965ef4f3a954e6fcdadb96fa5",
"Name": "",
"Comments": "",
"Policies": [],
"Messages": [
{
"Code": "Pricing",
"Text": "SellPrice<=PriceCard.Snapshot: Price=$10.00|Qty=1.0|PriceCard=Habitat_PriceCard"
},
{
"Code": "Pricing",
"Text": "ListPrice<=PricingPolicy: Price=$1,919.69"
},
{
"Code": "Pricing",
"Text": "Variation.SellPrice<=Variation.PriceCard.Snapshot: Price=$9.00|Qty=1.0|Variation=56042567|PriceCard=Habitat_VariantsPriceCard"
},
{
"Code": "Pricing",
"Text": "Variation.ListPrice<=Variation.PricePolicy: Variation=56042567|Price=$2,429.99"
},
{
"Code": "Pricing",
"Text": "CartItem.SellPrice<=PriceCard.ActiveSnapshot: Price=$6.00|Qty=5.0"
},
{
"Code": "Pricing",
"Text": "CartItem.ListPrice<=SellableItem.Variation.ListPrice: Price=$2,429.99"
}
],
"ChildComponents": []
}
OrderCloud stores the resolved UnitPrice
on the line item from the respective PriceBreak
, prioritising the SalePrice
over Price
.
For orders in an unsubmitted state, the /products/
resource may be used to retrieve the product information will return the the current active snapshot, however once the order has been placed the product and price schedules resources are no longer considered the source of truth. If price schedule information, e.g. price breaks, sale start/end dates, etc. is required for historical and reconciliation purposes, a webhook can be created on the order submit endpoint to persist the information on the line items’ xp
.
The other aspect of note is that the Currency
is set at the order level, which applies to all monetary values on the order and line items.
"Order": { "ID": "MyOrder" ... "Currency": "USD" ... }, "LineItems": [ { "ID": "MyLineItem", ... "UnitPrice": 2429.99, ... } ]
Cart Line vs Line Item Totals
In the table below, we see a comparison between the cart line totals side by side with the equivalent line item totals. The notable differences are as follows:
- XC
Adjustments
may include taxes, fulfillment fees, and promotion discounts, whereas OrderCloud only includesPromotionDiscount
. Extended properties (xp
) can be leveraged to persist taxes and fulfillments. - XC inverts the promotion discount adjustments so a $20 discount will be represented as -$20 in XC, so calculation of the
GrandTotal
is valid. - XC’s
GrandTotal
effectively matches the OrderCloud’sLineTotal
as taxes and fulfillment fees aren’t flagged withIncludeInGrandTotal
.
CartLine (XC) | Calculation | LineItem (OC) | Calculation | Match |
---|---|---|---|---|
SubTotal | SellPrice * Quantity | LineSubtotal | UnitPrice * Quantity | Yes |
AdjustmentsTotal | Sum of all line-level Adjustments . | PromotionDiscount | Sum of all line item-level promotion discount amounts applied for the current line item. | No |
GrandTotal | SubTotal + sum of line-level adjustments, where adjustments are flagged with IncludeInGrandTotal being true . | LineTotal | LineSubtotal – PromotionDiscount | Yes |
PaymentsTotal | Payments not considered at line-level without customisation. | N/A |
Extending the Cart and Cart Lines vs Order and Line Items
Those familiar with the SXA storefront and the Commerce Engine will be pleased with the significantly reduced development efforts involved in extending the order and order line items. Gone are the days of the plumbing extended strongly-typed model classes through several application layers in strongly-typed classes and dependency injection. And that’s just the storefront. The Commerce Engine would typically require creating new endpoints and replacing several more classes.
OrderCloud’s extended properties (xp
) is not only a dynamically built object in OrderCloud, but the OrderCloud .NET and JavaScript SDKs also represent xp
as a dynamic type, meaning you can write to a new property within the xp in one class and read from it in another class without any plumbing.
var order = new Order(); order.xp.MyNewProperty = "My New Property Value"; var lineitem = new LineItem(); line.xp.MyLineItemProperty = true;
Merge Carts vs Transfer Order
A typical ecommerce feature is retaining a cart/unsubmitted order of a guest/anonymous user as they log in or register to the storefront. In the SXA storefront, the register component will essentially transfer ownership to the newly registered user, while the login component will merge the anonymous cart with registered user’s cart, if it exists, by copying across all cart lines. If Cart Line Rollup is enabled, quantities will be updated where appropriate instead of copying over the cart line.
OrderCloud contains similar functionality in allowing ownership an order from an anonymous user to be transferred to a profiled (registered) user. For registration, the order will need to be explicitly transferred.
Where a profiled user logs in, the difference between transferring an order and XC’s merging of carts, is that the buyer user may now have two active unsubmitted orders. The implementor would need to manage merging the orders together and either explictly deleting the second order or let OrderCloud’s unsubmitted order cleanup process delete it.
Abandoned Cart/Order Cleanup
The Commerce Engine’s minions role is responsible for cleaning up abandoned and empty carts. As seen below the PurgeCartsMinion
runs daily, removing empty carts that are more than 2 days old and carts that have not been updated for more than 14 days. Both the minion and purge carts policies are configurable.
{ "$type": "Sitecore.Commerce.Core.MinionPolicy, Sitecore.Commerce.Core", "WakeupInterval": "01.00:00:00", "ListsToWatch": [ "Carts" ], "FullyQualifiedName": "Sitecore.Commerce.Plugin.Carts.PurgeCartsMinion, Sitecore.Commerce.Plugin.Carts", "ItemsPerBatch": 10, "SleepBetweenBatches": 500 }
{ "$type": "Sitecore.Commerce.Plugin.Carts.PurgeCartsPolicy, Sitecore.Commerce.Plugin.Carts", "AbandonedCartsThreshold": 14, "EmptyCartsThreshold": 2 }
In OrderCloud, unsubmitted order cleanup is an internal automated process with the rules outlined below, which are not configurable.
User Type | Has Line Items? | Retention Policy |
---|---|---|
All | No | 24 hours |
Anonymous | Yes | 7 days |
Profiled | Yes | 90 days from LastUpdated date |
Data Mapping
With the conceptual analysis above, we will now review what data mapping would look like for migration and from a comparison standpoint.
In the XC Entity/Component column, components are assumed to live on the primary XC entity being mapped.
OrderCloud IDs do not allow spaces. It is important that the IDs are parsed to remove/replace invalid characters consistently.
Orders
POST /orders/{direction}
In the Architecture Overview, we note that the cart equivalent in OrderCloud is an unsubmitted order. While no direct data mapping required, the following table details the alignment with XC.
OC Property | Data Type | Required | Notes |
---|---|---|---|
direction | string | Yes | Is a resource parameter, not body property. Set to Outgoing for buyer users. |
ID | string | No | Allowing the ID to be generated by the platform effectively matches XC order behaviour. |
FromCompanyID | string | No | This value will default to the buyer, which effectively matches XC. |
ToCompanyID | string | No | This value will default to the marketplace owner, which effectively matches XC. |
FromUserID | string | No | Defaults to the current user context. |
BillingAddressID | string | No | Not required at the time of creation. |
ShippingAddressID | string | No | Not required at the time of creation. |
Comments | string | No | |
ShippingCost | number | No | Not required at the time of creation. |
TaxCost | number | No | Not required at the time of creation. |
xp | object | No | Any custom components required for the initial state can be added to xp. |
Line Items
POST /orders/{direction}/{orderID}/lineitems
Adding a Cart Line vs Line Item has covered how to align the create line ltem request to align with XC. The following table covers the full view of the request.
OC Property | Data Type | Required | XC Property | Notes |
---|---|---|---|---|
direction | string | Yes | Is a resource parameter, not body property. Set to Outgoing for buyer users. | |
orderID | string | Yes | Is a resource parameter, not body property. OrderCloud’s order.ID | |
ID | string | No | Both XC and OrderCloud generate this ID by default. | |
ProductID | string | Yes | ItemId | Only the SellableItemId portion of the ItemId is required. |
Quantity | string | Yes | Quantity | This value will default to the marketplace owner, which is effectively matches XC. |
UnitPrice | string | No | Will be resolved from the PriceSchedule assignment of the current user context. | |
CostCenter | string | No | ||
DateNeeded | string | No | ||
ShippingAccount | string | No | ||
ShippingAddressID | string | No | ||
ShipFromAddressID | string | No | ||
InventoryRecordID | string | No | ||
Specs | object | No | ItemId | Used over variant ID. All spec IDs and specOptionIDs that represent the variant product will need to be specified. |
xp | object | No | Custom components can be added to xp as needed. |
References
- OrderCloud: Understanding Orders
- OrderCloud: Introducing the Cart API
- OrderCloud: Flexible Fulfillment
- OrderCloud: OrderCheckout Integration Event
- Sitecore: Cart policies
- Sitecore: Predefined commerce engine minions – Purge Carts minion
Continue the Series
- Transitioning from Sitecore Experience Commerce to OrderCloud: Customers to Buyer Users
- Transitioning from Sitecore Experience Commerce to OrderCloud: Customers and Buyers – API Access
- Transitioning from Sitecore Experience Commerce to OrderCloud: Catalogs and Categories
- Transitioning from Sitecore Experience Commerce to OrderCloud: Sellable Items To Products
- Transitioning from Sitecore Experience Commerce to OrderCloud: Inventory and Pricing
- Transitioning from Sitecore Experience Commerce to OrderCloud: Carts to Unsubmitted Orders and Carts
- Transitioning from Sitecore Experience Commerce to OrderCloud: Fulfillments to Shipping
- Transitioning from Sitecore Experience Commerce to OrderCloud: Tax and Payments
- Transitioning from Sitecore Experience Commerce to OrderCloud: Orders
- Transitioning from Sitecore Experience Commerce to OrderCloud: Order Workflow and Minions
- Transitioning from Sitecore Experience Commerce to OrderCloud: Promotions