Precision Pricing#
Description#
In industries where products are sold at prices lower than one cent, accurate sub-penny pricing is essential, especially for high-quantity sales. To enable sub-penny pricing, Miva has enhanced various product and order fields to handle up to 8 decimal places of precision.
In version 10.11.00 price and cost fields for Product, Attribute, Option, and Variant support up to 8 digits of precision within the admin interface.
All rounding calculations follow Banker’s Rounding (round to the nearest even number). However, when a product’s price rounds to below $0.01, the minimum extended price will default to $0.01 to prevent items from unintentionally being sold at no charge.
For calculations, prices for items and options will be summed and multiplied by the quantity before rounding occurs at the line-item level. Even for fields where more than two decimal places are not typically used, such as in discounts or fields restricted to two decimal places, calculations will still accurately follow the quantity * price
formula to ensure correct pricing.
Basket, Order, and Quote Items#
To support pricing precision and accuracy, the retail
, base_price
, and price
fields for Basket, Order, and Quote items have been updated to support 8-digit precision. Additionally, a new total
field has been added to these item structures to represent the final price per item, including options and quantity. This total is rounded to two decimal places using specialized rounding rules, providing consistent pricing at the item level.
Note
Integrators and module developers should use the item-level totals when calculating overall basket, order, or quote totals to ensure accuracy.
Rounding Adjustments#
Rounding operations have been removed from the following discount functions:
BasketItemDiscount_Total_Line
BasketOptionDiscount_Total_Option
BasketOptionDiscount_Total_LineOption_ID
OrderItemDiscount_Total_Line
OrderOptionDiscount_Total_Option
OrderOptionDiscount_Total_LineOption_ID
Round_Item_Total( price )
, found in lib/util_public.mv
, will round the price to two decimal places, setting the total to $0.01 if the rounded value is zero but the original price is not.
Compatibility and Consistency Adjustments#
Where functions already interact with a related item (e.g., BasketOption_Insert
or OrderOption_Insert
), new versions of these functions are designed to pass both item and option records to the v10
functions (v10_BasketOption_Insert
, v10_BasketOption_Update
, etc.). This reduces duplicate record loads and ensures compatibility.
Existing functions are also being renamed for consistency and backward compatibility, such as renaming BasketOption_Insert
to BasketOption_Insert_LowLevel
. New v10
functions handle recalculation when inserting or updating options.
New Functions#
Summary Loading Functions#
The BasketItemList_Load_Basket_Summary(basket_id, basketitems var)
and OrderItemList_Load_Order_Summary(order_id, orderitems var)
functions output an array of items containing all raw data fields from the database, with additional fields:
unit_quantity
: The quantity of the itemunit_price
: The price of the item, including options, before roundingunit_name
: Adjusted to reflect quantity if needed (e.g.,"(Qty <quantity>) <original name>"
)
The unit_price
ensures that unit_quantity * unit_price
equals total
without further rounding. For high-precision prices where exact equality isn’t possible, unit_quantity
defaults to 1, and unit_price
is set to the total value.
Usage#
<mvt:do file="g.Module_Library_DB" name="l.void" value="BasketItemList_Load_Basket_Summary( g.basket:basket_id, l.settings:basketitems )" />
<mvt:do file="g.Module_Library_DB" name="l.void" value="OrderItemList_Load_Order_Summary( l.settings:order:id, l.settings:basketitems )" />
Example Structure#
This example is from BasketItemList_Load_Basket_Summary()
and the structure includes a child product with the type of core
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
|
BasketItem_Recalculate_Total(basketitem var)
#
This function recalculates the total for a given basket item by summing the base price and option prices, multiplying by quantity, and rounding the result. The total will be stored in the basket item structure but won’t update the database—that’s handled by the calling function.
OrderItem_Recalculate_Total(orderitem var)
#
Similar to the basket function, this recalculates the total for order items based on the combined item price, options, and quantity, then rounds the final amount. Like BasketItem_Recalculate_Total, it only updates the structure and not the database.
Validate_Price_Optional( value )
#
Validate_Price_Required( value )
#
Validate_Price_NonNegative_Optional( value )
#
Validate_Price_NonNegative_Required( value )
#
These function behave identical to the similarly named Validate_Currency
functions except up to 8 decimal digits should be permitted.
The following functions use a Validate_Currency
function for validation of price or cost and have been modified to use the new Validate_Price
functions instead:
- Product_Validate_Common
- JSON_Product_Update
Function Updates#
CurrencyModule_AddFormatting
#
The US Currency and Generic Currency modules have been modified in 10.11.00 to no longer explicitly ROUND 2
, instead they now use Price_Pad
which will ensure the value is output to at least 2 decimal digits while allowing more if necessary.
Example of rounding to 2 decimals with the CurrencyModule_AddFormatting
function:
<mvt:do file="g.Module_Store_Module_Currency" name="l.formatted_price" value="CurrencyModule_AddFormatting(g.Store:currency_mod, l.settings:example_value ROUND 2)" />
BasketItem_Insert
and BasketItem_Insert_LowLevel
#
BasketItem_Insert
will calculate a total
if not provided, applying the Round_Item_Total
function. If BasketItem_Insert_LowLevel
finds a null total
, it will set it to zero.
OrderItem_Insert
and OrderItem_Insert_LowLevel
#
OrderItem_Insert
will calculate a total
if it’s null, while OrderItem_Insert_LowLevel
will set the total to zero in such cases.
BasketItem_Update
and OrderItem_Update
#
Existing update functions have been renamed to *_LowLevel
and will only update records without recalculating totals. New update functions (v10_BasketItem_Update
and v10_OrderItem_Update
) will call the recalculation function if the price or quantity has changed.
Backward compatibility functions BasketItem_Update
and OrderItem_Update
have been created, which load the original items and pass them to the new update functions.
Basket_SubTotal_Taxable
#
Modified to ignore sNN_BasketOptions
and return SUM( sNN_BasketItems.total )
BasketItem_Total
#
Modified to return SUM( sNN_BasketItems.total )
. When combined with the modification to BasketOption_Total
(see below), this will retain backwards compatibility.
BasketOption_Total
#
Because of the way totals are calculated, it is no longer possible to calculate standalone cumulative option prices across multiple lines. This function has been deprecated, and modified to return a deprecation error. Since Error returns 0, and the only existing callers of this function in the software are functions such as Basket_SubTotal
that are using it in combination with BasketItem_Total
, the error return of 0
will be seen as an option total of 0.00
, and the resulting quantity will be correct even for outdated software.
OrderItem_Total_OrderShipment
#
Modified to return SUM( sNN_OrderItems.total )
. When combined with the modification to OrderOption_Total_OrderShipment
(see below), this will retain backwards compatibility.
OrderOption_Total_OrderShipment
#
Because of the way totals are calculated, it is no longer possible to calculate standalone cumulative option prices across multiple lines. This function has been deprecated, and modified to return a deprecation error. Since Error returns 0, and the only existing callers of this function in the software are functions such as OrderItem_Total_OrderShipment
that are using it in combination with OrderItem_Total_OrderShipment
, the error return of 0
will be seen as an option total of 0.00
, and the resulting quantity will be correct even for outdated software.
OrderItem_Total
#
Modified to return SUM( sNN_OrderItems.total )
. When combined with the modification to OrderOption_Total
(see below), this will retain backwards compatibility.
OrderOption_Total
#
Because of the way totals are calculated, it is no longer possible to calculate standalone cumulative option prices across multiple lines. This function has been deprecated, and modified to return a deprecation error. Since Error returns 0, and the only existing callers of this function in the software are functions such as Order_Total
that are using it in combination with OrderItem_Total
, the error return of 0
will be seen as an option total of 0.00
, and the resulting quantity will be correct even for outdated software.
BasketOption_Total_Line
#
This function, which was added in 10.07.00 has been deprecated. 1. It is currently used for level-II support where the software is calculating unit prices, which are restricted to 2 decimal digits. 2. It is generating a unit-level (quantity of 1) price, which is not guaranteed to be the same as the extended price 3. Callers of this function must be calculating a total price and rounding it at the end, rather than rounding each line and option individually.
Basket_SubTotal
#
Removed the call to BasketOption_Total
.
Basket_Total
#
Removed the call to BasketOption_Total
.
OrderShipment_SubTotal
#
Removed the call to OrderOption_Total_OrderShipment
.
Order_Total
#
Removed the call to OrderOption_Total
.
JSON Updates#
To support high-precision pricing up to eight decimal places, all JSON output fields related to pricing will now use encodejavascriptnumber
. This change affects the following functions:
JSON_AttributeTemplateAttribute
JSON_AttributeTemplateOption
JSON_AttributeTemplateAttributeList_Load_Query
JSON_ProductInventoryList_Load_ProductVariants
JSON_ProductInventoryList_Load_ProductVariants_Filter
JSON_ProductVariantPricing_Load
JSON_OrderItem_DetermineVariant
JSON_OrderItem_OnDemandColumns
JSON_Product_OnDemandColumns
JSON_Attribute_Load_Code
JSON_AttributeList
JSON_Option_Load_Code
JSON_OptionList_Load_CodeMatch
JSON_OptionList_Load_Attribute
JSON_AttributeTemplateOptionList_Load_Attribute
JSON_AttributeAndOptionList_Load_Product
JSON_ProductAttribute
JSON_ProductOption
JSON_ProductAttributeAndOptionList_Load_Query
JSON_Runtime_Product_AttributeAndOption
JSON_Possible_Output
JSON_Runtime_Product
JSON_ProductInventoryList_Load_ProductKit
JSON Output Updates#
Function JSON_OrderItem_OnDemandColumns
has been modified to use sNN_OrderItems.total
for its “total” output field instead of calculating it.
encodejavascriptnumber
has been implemented across JSON functions handling pricing data to ensure that values retain up to eight decimal places. This will be particularly important for cases where fractional pricing or small units (like grams or milliliters) require precise representation.
Functions updated:
JSON_AttributeTemplateAttribute
JSON_AttributeTemplateOption
JSON_AttributeTemplateAttributeList_Load_Query
JSON_ProductInventoryList_Load_ProductVariants
JSON_ProductInventoryList_Load_ProductVariants_Filter
JSON_ProductVariantPricing_Load
JSON_OrderItem_DetermineVariant
JSON_OrderItem_OnDemandColumns
JSON_Product_OnDemandColumns
JSON_Attribute_Load_Code
JSON_AttributeList
JSON_Option_Load_Code
JSON_OptionList_Load_CodeMatch
JSON_OptionList_Load_Attribute
JSON_AttributeTemplateOptionList_Load_Attribute
JSON_AttributeAndOptionList_Load_Product
JSON_ProductAttribute
JSON_ProductOption
JSON_ProductAttributeAndOptionList_Load_Query
JSON_Runtime_Product_AttributeAndOption
JSON_Possible_Output
JSON_Runtime_Product
JSON_ProductInventoryList_Load_ProductKit
JSON Input Validation#
JSON_Input_Price
#
- This function will allow entry of prices with up to eight decimal places. This level of precision ensures that products priced in fractions of a unit (e.g., per ounce, per milliliter) are captured accurately.
JSON_Input_Price
also rejects negative values, aligning with admin and database validations to prevent erroneous or invalid entries. This is especially valuable for e-commerce businesses handling complex pricing structures or promotions.