Commerce Commerce 1.x Orders Items
Order items are pretty self-explanatory. When adding a product to the cart, that is turned into an order item which holds a copy of the product information at that time (sku, name, price, etc) as well as the whole calculation of an item including extra fees, discounts, and taxes.
This document will go into some specifics of the item representation and the calculation, which is useful background when customising the cart templates or looking to import/export order data.
Table of Contents
Catalog links
When using a resource product, the item link is automatically set to the associated resource. The same is true when using a product matrix.
For catalogs using the Product List TV, that relation is not automatically available. To still allow your cart to link back to the appropriate place:
- Add a hidden input to your add to cart form with a name of
link
and the resource ID to link to as the value. For example:<input type="hidden" name="link" value="[[*id]]">
or in getResources/pdoResources template chunks:<input type="hidden" name="link" value="[[+id]]">
- Enable the ItemData module, and add
link
to its list of allowed fields. The module will now add the
Make sure to repeat step 1 for any add to cart form you have, which may include quick add to cart forms on category pages or upsell/related product blocks.
Calculation
How hard can calculating an item price be? Well, when you factor in sales, discounts, extra fees, and taxes, it starts getting more confusing.
The calculating of a specific item is performed in the following order:
-
First, the per-item
price
is determined. Based on configured Price Types the price may already be one thing or another based on currencies, available sales, item quantity, logged-in user, etc. If you’re using a custom add-to-cart script that enablesis_manual_price
, this is skipped. If a price type is used that gives a lower price than the configured regular price, theregular_price
is set to the regular price. (As of v1.3) -
The
price
is multiplied by thequantity
which gives thesubtotal_before_extra
. This is sort of the “raw” subtotal based on the current going rate for the product. -
Any extra price adjustments are calculated and added. The total extra fees are saved to
extra
, and the subtotal including extra fees is saved to thesubtotal
value. -
While not used by the core and not recommended, items may have an integer
shipping
value that is now added to the subtotal. Implementing this would require an extension to provide an extended comOrderItem object that extendsgetShipping
- not recommended. -
The discount for an item is now calculated by loading all discount price adjustments. Coupons and user(group) discounts for example will set these adjustments. The total discount is set to the
discount
field. Note that discounts coming from a price type (eg sale or quantity prices) are not reflected in the discount. -
The
total_before_tax
field is set to thesubtotal
(see #3) plusshipping
(see #4) minusdiscount
(see #5). This field holds the “true subtotal”, and is best to use for display, because it is what the customer will have to pay for this item before any taxes are applied. -
Next, we’ll calculate taxes. The actual inner workings of that with tax rates and rows is worth a closer look some other day, but what you need to know is that
comOrderItemTax
records will be created and/or updated and/or removed to ensure each unique tax rate applied to an item is properly accounted for, with its percentage, wether the before price was inclusive/exclusive, the taxed amount, and the totaltax_amount
that results in. Typically, each item has a singlecomOrderItemTax
but it may be 0 if you don’t have tax rules configured, or more than 1 if you use a tax integration that provides a breakdown of country/state taxes. -
The total of the final
comOrderItemTax
records is calculated, and saved to thetax
column. -
- If taxes are exclusive (taxes are added to the product price), the
total_ex_tax
is now set to the same value astotal_before_tax
. - If taxes are inclusive (taxes are already included in the product price),
total_ex_tax
is set tototal_before_tax
minustax
.
- If taxes are exclusive (taxes are added to the product price), the
-
The
total
is set tototal_ex_tax
+tax
.
When rendering any of these fields in the front-end, remember suffixing _formatted
(e.g. total_formatted
) will render the price according to the currency configuration used for the order.
All calculations happen with whole numbers (integers) representing the smallest denominator per the number of subunits configured on the currency. If any step of the calculation results in a floating value (especially in taxes or percentage adjustments), it is rounded “half up” meaning 3.45 becomes 3, but 3.55 becomes 4.
For the vast majority of currencies (but not all!) the integer value stored in a field represents a cent, or 1/100th of the currency single value.
Price Adjustments
Price adjustments are created by modules and indicate a change to the subtotal (subtotal_before_extra
, technically speaking).
There are 2 types of adjustments.
-
The Extra price adjustment means a surcharge is added to the item. These are non-optional (the user can’t opt out or remove them from the cart) extra fees. For example a mandatory insurance or gift wrapping (with some other way to add/remove it) would be a great use case for this. This is represented by the
comOrderItemDiscountAdjustment
class internally. -
The Discount price adjustment indicates a reduction in price, for example because the customer entered a coupon code or another discount was applied.
Important: discounts that come from a Price Type are NOT reflected with a price adjustment. Those are applied instead directly to the price field before the rest of the calculation takes place.
Price change options
Regardless of the type of price change, the adjustment can be created to be calculated in one of the following ways:
- A fixed-price adjustment regardless of the quantity has an integer
price_change
andprice_change_per_quantity
is disabled (default). - A fixed-price adjustment that is multiplied by the quantity has an integer
price_change
andprice_change_per_quantity
is enabled. - A percentage adjustment is set with a float
price_change_percentage
.
(Prior to 1.2.0-rc3, only the first option was available and the module setting the price adjustment had to account for quantity or percentages.)