Carts and Orders overview

Overview of the concepts related to carts and orders involved in the checkout process with your Composable Commerce Project.

Carts hold Product Variants that are referred to as Line Items once added to the Cart. A Cart can be ordered and it either belongs to a Customer or an anonymous session. Carts that belong to an anonymous session are referred to as anonymous Carts.

Line Items and Custom Line Items

A Line Item is a snapshot of a Product Variant at the time it is added to a Cart. A Custom Line Item is a generic item not associated to any Product Variant. As such, they can be used for selling services or virtual goods that do not require any tracking of inventory, like additional fees or vouchers.

Line Items

The snapshot of a Product Variant is created from the ProductProjection as follows:

  • The current projection is used.
  • If the Cart is bound to a Store, the following additional checks and projections are performed:
    • For Stores with active Product Selections, the selected Product Variant must be included in at least one of the active Product Selections.
    • The selected Product Variant must be available in a Store.
    • All LocalizedStrings are filtered according to the locales defined in the Store.
    • All Prices are filtered according to the Product distribution Channels defined in the Store. Prices without a Channel are also included.
    • All ProductVariantAvailabilities are filtered according to the Inventory supply Channel defined in the Store. Inventory entries without a Channel are also included.

If the Product Variant changes, the relation to the current ProductProjection is kept, but the Line Item is not updated automatically. The Line Item can be updated through the Carts API on demand. With this update, the ProductVariant data is copied to the variant field of the LineItem.

Carts containing Line Items that reference deleted Product Variants cannot be used to create Orders.

Line Item Price selection

The price of a LineItem is selected from the Product Variant based on several conditions:

  • selection based on the price mode:

    As a first step, the API uses the Product's priceMode value to decide where to look up the prices of a Product Variant. See ProductPriceMode for more details.

  • selection based on the distribution channel, the currency, the country, and the customer group:

    The selection of the price is based on the distributionChannel provided on the LineItemDraft or on the Add LineItem update action, and on the currency, country, and the customerGroup of the Cart. If a price with an exact match cannot be found, wildcards (all countries, all channels, all CustomerGroups) are used.

    The list below describes the order in which the price is selected. If a price could not be found in one step, the rule in the next step will be applied until a price could be found. The priority order for the selection of the price is customer group > channel > country (customer group has the highest priority and country the lowest).
    The price for a specific currency is determined in the following order:

    1. Find a price for the customer group, channel, and country.
    2. Find a price for the customer group and channel for all countries.
    3. Find a price for the customer group and country for all channels.
    4. Find a price for the customer group for all channels and all countries.
    5. Find a price for the country and channel for all customer groups.
    6. Find a price for the channel for all customer groups and all countries.
    7. Find a price for the country for all customer groups and all channels.
    8. Find a price for all customer groups, all channels, and all countries.
  • selection based on the price's validity period:

    For all of the price selection steps, the prices with a validity period are checked before the prices without any validity period.
    If a currently valid price is found, it is used first.

  • tier price selection based on the Line Item quantity:

    If the Product Price has some PriceTier, the tier price valid for the Line Item quantity is selected.
    If no tier price can be used, the base price is used.

Custom Line Items

A generic Custom Line Item can be added to the Cart just like Line Items, but without Line Item Price selection, meaning you control the price and taxes of the Custom Line Item. Since the money value of a Custom Line Item can be negative, it can be used for discounts applied by vouchers. Furthermore, Custom Line Items can be used to implement a more complex cart logic via Cart Predicates.

A Cart's InventoryMode has no effect on Custom Line Items.

Cart price precision

  • Prices with TypedMoney can have cent-precision and high-precision.
  • When calculating net and gross prices, prices with decimals are rounded. In case the fraction of centAmount is exactly 0.5, the RoundingMode defined by taxRoundingMode is applied to the Cart's price.
  • The tax portions are derived from the net prices in case the tax is not included in the price, meaning the includedInPrice field in the TaxRate for the product is set to false. When the tax is included in the price (includedInPrice: true), then the tax portions are derived from the gross prices. This only applies when the Platform TaxMode is used.
  • When prices are discounted with relative discounts, they are always rounded in favor of the customer with the half-down rounding.

Cart tax rate selection

A Cart cannot calculate taxes for the items in it until the shippingAddress property is set. The shippingAddress.country and shippingAddress.state properties affect the selection criteria. These are compared to the TaxRate's country and state fields. A Tax Rate is only eligible if the country and state properties are an exact match. Because the state field is optional on both TaxRate and shippingAddress, this means the following:

  • If shippingAddress.country and TaxRate.country are an exact match, and no state properties are present, the TaxRate applies to the Cart.
  • If shippingAddress.country and TaxRate.country are an exact match, but only one of shippingAddress.state or TaxRate.state is present, the Tax Rate does not apply to the Cart.
  • If shippingAddress.country and TaxRate.country are an exact match, and shippingAddress.state and TaxRate.state are an exact match, the Tax Rate applies to the Cart.
  • If shippingAddress.country and TaxRate.country are present, and shippingAddress.state and TaxRate.state are present, but there is a mismatch of either country or state, the Tax Rate does not apply to the Cart.

If you need to include a state or country sub-unit for shipping purposes, use shippingAddress.region to avoid Tax Rate conflicts.

Cart tax calculation

Tax mode

By default, the taxed price of a Cart is calculated by Composable Commerce based on the tax rate that applies for the country (and optionally state) of the Cart's shippingAddress. In this default Platform TaxMode, the TaxRate is taken from the TaxCategory assigned to Products and Shipping Methods. If you retrieve the tax rates for a location from a third-party service, you can set those yourself in the External TaxMode. With this mode, you need to tell Composable Commerce whether tax is included in the price you provide on a Line Item, Custom Line Item, or on a Shipping Method.

When includedInPrice: true, the tax is calculated in a top-down manner, and when includedInPrice: false, the tax is calculated in a bottom-up manner.

The following example shows the impact of includedInPrice in the Cart calculation with taxRoundingMode: HalfEven and taxCalculationMode: LineItemLevel.

Line ItemProduct Variant AProduct Variant BShipping
Line Item: Price$15$25$5
Quantity105---
External Tax Rate: includedInPricefalsetruefalse
External Tax Rate: amount0.190.150.15
Taxed Item Price: totalNet$150$108.70$5
Taxed Item Price: totalGross$178.5$125$5.75
Calculation formula15 x 10 (1 + 0.19)25 x 5 / (1 + 0.15)5 x (1 + 0.15)

Tax calculation mode

Due to the principle of rounding on every calculation step, this slight difference in when to apply tax can end up having a considerable effect on the Cart total. Composable Commerce offers two different tax calculation modes to support the option your use case requires. The LineItemLevel TaxCalculationMode enforces tax calculation after multiplying the individual item price by the quantity. The UnitPriceLevel mode leads to calculating the taxed price before multiplying by the quantity.

The following example demonstrates the differences in the Cart total depending on the different tax calculation modes. Let's assume:

Line Item #QuantityUnit PriceLine Item Total, Net
(LineItemLevel)
Line Item Total, Net
(UnitPriceLevel)
Line Item Total, Gross
11$1.00$0.84$0.84$1.00
210$1.08$9.08$9.10$10.80
310$108.08$908.24$908.20$1080.80
41$2.00$1.68$1.68$2.00
550$0.01$0.42$0.50$0.50
61$4.90$4.12$4.12$4.90
Cart Total, Net
(LineItemLevel)
Cart Total, Net
(UnitPriceLevel)
Cart Total, Gross
$924.38$924.44$1100.00

As seen from the example, if a Line Item has a quantity of 1, the two modes have no impact on the total net value of a Line Item (see Line Item #1, 4, and 6). However, if the quantity is more than 1, the two modes calculate the total net value of a Line Item with a difference of several cents (see Line Item #2, 3, and 5). Depending on the specific Line Item values, the chosen mode can give a greater or lesser net value (see line item #2 compared to #3). A difference of 6 cents is accumulated in the Cart total. The connection to rounding is especially clear with Line Item #5. Due to rounding after applying tax to 0.01 and multiplying by quantity 50 upon that, the net value when using UnitPriceLevel ends up being equal to the gross value. With LineItemLevel, where quantity multiplication is performed as a first calculation step, the rounding occurs to a greater value, and the net value ends up being $0.42.

Cart updates

The lifetime of a Cart (controlled by the deleteDaysAfterLastModification property) can be adjusted according to your requirements. During this lifetime, a Cart is kept as it was the last time when actions were performed on it. It contains Line Items and Custom Line Items with prices that were valid at the time of the last Cart update and Cart Discounts that were active at that time. Any read method on a Cart alone will not lead to a representation with up-to-date prices and so on, only Cart updates will trigger this.

After all update actions are performed, the Cart:

  • removes any Line Items that are invalid (due to deleted Products, Product Variants, or Prices),

  • updates any LineItem price field if the result of the price selection has changed because of a Product update,

  • updates the ShippingInfo, in particular, the price and shippingMethodState fields,

  • updates the discounts, which may affect the LineItem or CustomLineItem totalPrice fields and ShippingInfo discountedPrice field,

    • if a Cart Discount becomes invalid due to deactivation, expiration, or the predicate no longer matches the Cart's content, the discount is removed.
    • if a Cart Discount becomes active and applicable (since the last update), the discount is added.
    • if a Discount Code becomes invalid due to usage limits, deactivation, expiration, or the predicate no longer matches the Cart's content, the discount is removed.
  • updates the Cart totalPrice field,

  • updates the Tax Rates, in particular, the taxedPrice field,

  • updates the LineItem productKey.

To perform the above-mentioned updates without additional update actions, send a request with only the Recalculate update action.

To update the Product data saved in the Line Items, use the Recalculate update action with updateProductData set to true.

Frozen Carts

If you want to prevent carts from being modified by changes to prices and discounts that become active or inactive over time, you have the option to freeze the cart. When a cart is frozen, all prices stay as they were at the time that the cart was frozen. Update actions as well as background services modifying prices on the cart are deactivated for frozen carts. Inventory is not reserved for line items in frozen carts. Frozen carts can either be ordered or unfrozen. When unfrozen, a cart continues to accept new updates.

Restrictions of frozen carts:

  • Cart Discounts that become active or are created after cart freeze are not applied.
  • Cart Discounts and Product Discounts that expire after cart freeze remain applied.
  • New Discount Codes can still be added to the cart, but their DiscountCodeState is of DoesNotMatchCart.
  • Regardless of a cart freeze, Cart Discounts that are deleted or that become invalid due to Cart Predicates not matching, are removed from the cart.
  • Regardless of a cart freeze, Cart Discounts and Discount Codes that become inactive, are removed from the cart.
  • Regardless of a cart freeze, Discount Codes that become invalid due to Cart Predicates not matching, remain present on the cart with the DiscountCodeState of DoesNotMatchCart.
  • Update actions that could change the price of items in the cart are not accepted. Find the list of restricted update actions on the Frozen value for CartState.
  • All other update actions are still supported and applied. The API still performs the validations in the cart ensuring the cart is convertible to an Order, and reports errors if such a validation fails.

During Order creation, if a Frozen Cart contains a Discount that must be removed (according to the previously mentioned restrictions), the discount is removed, and the Order creation process fails.

Update an Order

An Order is a legally binding purchase agreement between the Merchant and the customer; therefore, changes to it should be carefully considered.

To update the Order, you have two options: direct updates or Order Edits. The option that you choose depends on whether a financial change to the purchase agreement is required.

If, for instance, a customer provides an incorrect shipping address by mistake, a change is required to ensure the goods can be delivered to the customer. Composable Commerce offers a direct update action to change the shipping address for such cases. However, if the proposed change to the shipping address results in changes to the applicable taxes of the Order items, a direct update to the Order is not possible. In this case, because a financial change will occur, you must use an Order Edit instead, which ensures the amendment is traceable for audit purposes.

To learn about which update actions are available for each method, see Overview of update actions for Cart, Order, and Order Edit.

Make non-financial changes

Use the Order update actions to directly modify an Order when no financial change occurs.

For example, changes to:

  • Order number, State, or locale
  • Customer's email address or billing address
  • Shipping information, such as addresses, that do not require recalculation of the Shipping Method and shipping rates
  • Payment information
  • Deliveries and returns
  • Any other change that does not affect the Order total

Make financial changes

Use Order Edits when you need to update an Order that results in a financial change.

For example, changes to:

  • Line Items and Custom Line Items (such as quantity)
  • Shipping information, such as addresses, that require recalculation of the Shipping Method and shipping rates
  • Discounts
  • Any other change that affects the Order total

In addition to any updates that financially impact the Order, you can also update non-financial properties within the same Order Edit. For example, you can update a Customer's email address alongside any changes that affect the total price of the Order.

The Order Edits API does not support tracking or reserving inventory. You can only create an Order Edit if the InventoryMode of the Order and its LineItems is None.

Shipping addresses, including country and state, are used to identify applicable Shipping Methods and shipping rates for an Order. You can update shipping addresses using direct updates or Order Edits, however, Composable Commerce only triggers reevaluation of Shipping Methods and shipping rates when changes are initiated through an Order Edit.

Intended flow

The basic flow for an Order Edit:

  1. Create an OrderEdit: use the Create OrderEdit endpoint to create an OrderEdit entity. Upon creation, the OrderEdit contains a reference to the target Order within the resource field, however, no data is copied between the entities. At this point, you can consider the OrderEdit to be a staging area to collect the proposed changes.

  2. Add the proposed changes: use the Update OrderEdit by ID endpoint to add the individual update actions to the OrderEdit. Immediately upon adding the update actions to the OrderEdit, the stagedActions are ran against the Order; a so-called dry run. As a response, the Order is returned as if the Order actions had been applied. This allows you to preview the changes and understand the impact on the total price of the Order. The response will also contain Messages which capture the changes applied to the Order. Dry runs are always performed against the latest version of the Order.

    If the Order endpoint contains API Extensions, they are also called during the dry run.

  3. Review the preview: ensure a careful review of the preview takes place due to the Order always respecting the current product catalog, price configuration, and promotions. For example, if an OrderEdit is made soon after Order placement, changes will likely be minimal. However, if more time has passed, price adjustments and expired promotions may significantly impact the Order.

    We recommend that you review the Messages within the preview to understand the proposed changes. For example, the OrderEditApplied Message details the before and after values of the net total, gross total, and tax portion.

  4. Apply the changes: use the Apply OrderEdit endpoint to apply your proposed changes to the original Order.

    When you apply an Order Edit, the Order is recalculated using the same criteria that is listed under Cart updates. During recalculation, Discounts that have become invalid or are otherwise no longer applicable are removed. Only Discounts that are valid at the time of the Order Edit application are applied to the updated Order.

    The Order (not the OrderEdit) triggers the OrderEditApplied Message which contains both the original and the newly applied values. At this point, you can delete the Order Edit (if desired). Deleting an Order Edit does not affect the related Order.

State

To check the state of an Order Edit, use the result field.

StateDescription
NotProcessedThe changes have not been applied to the Order. This status shows on results returned from the Query OrderEdits endpoint.
PreviewSuccessA preview of the modified Order including any available Messages is available for evaluation. This status can show after OrderEdit creation and when an OrderEdit is fetched or updated via key or ID.
PreviewFailureA preview could not be generated due to one or more invalid changes specified in the errors field. This status can show after OrderEdit creation and when an OrderEdit is fetched or updated via key or ID.
AppliedThe changes have been applied to the Order. This state contains information detailing the state before (excerptBeforeEdit) and after (excerptAfterEdit) the OrderEdit was applied. Once applied, any additional changes must be done in a new OrderEdit.

Overview of update actions for Cart, Order, and Order Edit

Update actionCartOrderOrder Edit
Set Key-
Set Order Number-
Set Purchase Order Number-
Set Customer ID
Set Customer Email
Set Customer Group-
Set Anonymous ID--
Set Business Unit--
Add LineItem-
Remove LineItem-
Add CustomLineItem-
Remove CustomLineItem-
Add Shopping List-
Set Cart Total Tax--
Set Order Total Tax--
Change TaxMode-
Change Tax RoundingMode-
Change Tax CalculationMode-
Add DiscountCode-
Remove DiscountCode-
Set DirectDiscounts-
Add Payment
Remove Payment
Change PaymentState-
Set Billing Address
Set Shipping Address
Add ItemShippingAddress
Remove ItemShippingAddress
Update ItemShippingAddress
Add ShippingMethod--
Add Custom ShippingMethod--
Remove Shipping Method--
Set ShippingMethod-
Set ShippingAddress and ShippingMethod--
Set ShippingAddress and Custom ShippingMethod--
Set Custom ShippingMethod-
Set ShippingRateInput-
Add Delivery-
Remove Delivery-
Change ShipmentState-
Add ReturnInfo-
Set ReturnInfo-
Set ReturnShipmentState-
Set ReturnPaymentState-
Change OrderState-
Transition State-
Update SyncInfo-
Set Locale
Set Country-
Set Store-
Set Custom Type
Set CustomField
Set DeleteDaysAfterLastModification--
Freeze Cart--
Unfreeze Cart--
Recalculate--

on LineItem

Update actionCartOrderOrder Edit
Change LineItem Quantity-
Set LineItem TaxRate-
Set LineItem TaxAmount-
Set LineItem Price-
Set LineItem TotalPrice-
Set LineItem DistributionChannel-
Set LineItem ShippingDetails
Apply DeltaToLineItemShippingDetailTargets--
Set LineItem Custom Type
Set LineItem CustomField
Set LineItem InventoryMode--
Set LineItem SupplyChannel--
Transition LineItem State-
Import LineItem State-

on CustomLineItem

Update actionCartOrderOrder Edit
Change CustomLineItem Quantity-
Set CustomLineItem TaxRate-
Set CustomLineItem TaxAmount-
Change CustomLineItem Money-
Change CustomLineItem Price Mode--
Set CustomLineItem ShippingDetails
Apply DeltaToCustomLineItemShippingDetailsTargets--
Set CustomLineItem Custom Type
Set CustomLineItem CustomField
Transition CustomLineItem State-
Import CustomLineItem State-

on Billing Address

Update actionCartOrderOrder Edit
Set Billing Address Custom Type
Set Billing Address CustomField

on Shipping Address

Update actionCartOrderOrder Edit
Set Shipping Address Custom Type
Set Shipping Address CustomField

on ItemShipping Address

Update actionCartOrderOrder Edit
Set ItemShipping Address Custom Type
Set ItemShipping Address CustomField

on ShippingMethod

Update actionCartOrderOrder Edit
Set ShippingMethod TaxAmount-
Set ShippingMethod TaxRate-
Set Shipping Custom Type--
Set Shipping CustomField--

on Delivery

Update actionCartOrderOrder Edit
Set Deliver Address-
Add Parcel to Delivery-
Remove Parcel from Delivery-
Set Delivery Items-
Set Delivery Custom Type-
Set Delivery CustomField-

on Delivery Address

Update actionCartOrderOrder Edit
Set Delivery Address Custom Type-
Set Delivery Address CustomField-

on Parcel

Update actionCartOrderOrder Edit
Set Parcel Measurements-
Set Parcel Tracking Data-
Set Parcel Items-
Set Parcel Custom Type-
Set Parcel CustomField-

on ReturnItem

Update actionCartOrderOrder Edit
Set ReturnItem Custom Type-
Set ReturnItem CustomField-