Skip to main content
All monetary values in the Store API are returned as decimal strings, never JSON numbers, written with exactly the decimal places of their currency: "29.99" in USD, "1.500" in Kuwaiti dinar, "1000" in yen. Unit prices may carry up to four decimals ("0.0125"). This preserves decimal precision and avoids the rounding errors of JSON numbers. A currency’s decimal places are its decimal_places (see below).

Response Format

Every monetary field is a decimal string, with its currency on the record or on the record it belongs to (an order’s line items, payments and fulfillments are in the order’s currency):
Format each amount for the customer reading it, in their language and in the currency the amount is in. An order or cart carries both (currency, locale); before checkout, use the current market’s (currency, default_locale) or the price’s own currency. See Working with Amounts for examples.
The display_* fields are deprecated and will be removed in Spree 6.1. Each monetary field still has a pre-formatted copy (display_total beside total), formatted in the request’s language. Format the amount beside it instead. The API reference marks these fields as deprecated, and the SDK types strike them through. Labels such as display_name are not affected.

Affected Types

This convention applies to all monetary fields across all resources: Rates, such as a tax line’s rate, are decimal strings too, without trailing zeros ("0.23").

Currency decimal places

Each currency the store sells in (GET /api/v3/store/currencies) carries decimal_places, from ISO 4217: 2 for USD and EUR, 0 for JPY, 3 for KWD. Every amount in that currency is written with that many decimals. The SDK knows the same table, so decimalPlaces(currency) answers without a request.

Money helpers

@spree/sdk exports helpers that work on the decimal strings directly. None of them turns an amount into a JavaScript number, so a sum or a comparison is exact.

Displaying

Calculating

Pass a currency to write the result with that currency’s decimal places. Without one, the result keeps only the decimals it needs.

Comparing and checking

The calculating and comparing helpers throw on a value that is not a canonical decimal string, such as "1,99" or "1e3", rather than guess what it means. Check input you did not get from the API with isDecimalString first.

Working with Amounts

Format an amount with the SDK’s formatMoney. It writes the currency’s own decimal places (none for yen, three for dinar) and never turns the amount into a JavaScript number. Without the SDK, pass the decimal string straight to Intl.NumberFormat, which reads it exactly.

Order and cart totals

An order carries its own currency and the locale the customer shopped in, so its receipt reads the same however the store changes later.

Product prices

Before there is a cart, prices are in the customer’s market. Resolve the market once and format every price with it; each price also carries its own currency.
Never convert an amount to a JavaScript number: 0.1 + 0.2 is 0.30000000000000004. Calculate and display with the money helpers instead.