@spree/docs 0.1.303 → 0.1.304

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ sidebarTitle: "Monetary Amounts"
4
4
  description: "How the Admin API represents money as decimal strings, why JSON numbers are avoided, and how to send prices, costs, and amounts safely in requests."
5
5
  ---
6
6
 
7
- All monetary values in the Admin API are **canonical decimal strings** (e.g., `"29.99"`, `"0.0"` — period decimal, no thousands grouping), both in responses and in request bodies. Strings preserve decimal precision and avoid the floating-point rounding issues common with JSON numbers — critical when an admin is setting prices and costs. The format is the same regardless of locale; localized formatting is a presentation concern handled by the client (see [Canonical format](#canonical-format-not-localized)).
7
+ All monetary values in the Admin API are **canonical decimal strings** (period decimal, no thousands grouping), both in responses and in request bodies. Responses write each amount with exactly its currency's decimal places — `"29.99"` in USD, `"1.500"` in Kuwaiti dinar, `"1000"` in yen — and unit prices (`price`, `amount` on a price, `compare_at_amount`, `cost_price`, `unit_cost`) with up to four (`"0.0125"`). A currency's decimal places are its `decimal_places`, from ISO 4217. Strings preserve decimal precision and avoid the floating-point rounding issues common with JSON numbers — critical when an admin is setting prices and costs. The format is the same regardless of locale; localized formatting is a presentation concern handled by the client (see [Canonical format](#canonical-format-not-localized)).
8
8
 
9
9
  ## Reading
10
10
 
@@ -19,7 +19,7 @@ Every monetary field is returned as a string, alongside a `display_` companion t
19
19
  }
20
20
  ```
21
21
 
22
- Use `display_*` for rendering and the raw string fields for calculations (`parseFloat(price.amount)`).
22
+ Use `display_*` for rendering and the raw string fields for calculations, with a decimal library rather than `parseFloat`. The SDK exports `sumMoney`, `subtractMoney`, `multiplyMoney`, `compareMoney` and `isZeroMoney`, which work on the strings exactly.
23
23
 
24
24
  ## Writing
25
25
 
@@ -38,13 +38,25 @@ spree api post /products -d '{"name":"Classic Tee","prices":[{"currency":"USD","
38
38
  ```
39
39
 
40
40
 
41
- JSON numbers are also accepted (`"amount": 29.99`), but strings are recommended: they round-trip with what the API returns and sidestep float precision. A `null` clears the value.
41
+ Only strings are accepted. A JSON number (`"amount": 29.99`), a localized or grouped string (`"1.234,56"`, `"1,234.56"`), or more decimals than the field holds (`"19.999"` in USD, `"100.5"` in yen; unit prices allow four) is refused with `422` and the error code `invalid_money_format`, naming the field:
42
+
43
+ ```json
44
+ {
45
+ "error": {
46
+ "code": "invalid_money_format",
47
+ "message": "amount must be a decimal string like \"19.99\"",
48
+ "details": { "amount": ["must be a decimal string like \"19.99\""] }
49
+ }
50
+ }
51
+ ```
52
+
53
+ A `null` clears the value.
42
54
 
43
55
  ### Canonical format — not localized
44
56
 
45
57
  Amounts are **canonical**: a period decimal separator and no thousands grouping (`"1234.56"`), independent of any locale. The Admin API does **not** parse locale-specific formats — do not send `"1.234,56"` or `"1,234.56"`.
46
58
 
47
- If you are taking input from a merchant in a localized format, normalize it to canonical form **before** sending. The Spree dashboard does this in the browser: a EUR price typed as `1.234,56` becomes `1234.56` on the wire. This matches how other commerce APIs handle money (canonical decimal or minor-unit integers; localization is a presentation concern).
59
+ If you are taking input from a merchant in a localized format, normalize it to canonical form **before** sending. The Spree dashboard does this in the browser, in the number format of the person using it: an admin who writes German types `1.234,56`, in any currency, and `1234.56` goes on the wire. This matches how other commerce APIs handle money (canonical decimal or minor-unit integers; localization is a presentation concern).
48
60
 
49
61
  ## Affected types
50
62
 
@@ -75,4 +87,8 @@ Calculators and some promotion rules carry their amounts inside an untyped `pref
75
87
  | **Calculator** `flat_rate` (shipping) | `amount`, `minimum_item_total`, `maximum_item_total` |
76
88
  | **Promotion Rule** `item_total` | `amount_min`, `amount_max` |
77
89
 
78
- Percentage keys (`percent`, `flat_percent`, `base_percent`), weights (`minimum_weight`, `maximum_weight`), and quantities (`max_items`, `min_quantity`) are **not** money — those stay numbers. Promotion actions (`create_adjustment`, `create_item_adjustments`) hold their money inside a nested `calculator.preferences`, per the calculator rows above.
90
+ Percentage keys (`percent`, `flat_percent`, `base_percent`) are rates: decimal strings too (`"10"`), with no fixed number of decimals. Weights (`minimum_weight`, `maximum_weight`) and quantities (`max_items`, `min_quantity`) are not money and stay numbers. Promotion actions (`create_adjustment`, `create_item_adjustments`) hold their money inside a nested `calculator.preferences`, per the calculator rows above.
91
+
92
+ ## Rates and percentages
93
+
94
+ Rates and percentages are decimal strings without trailing zeros, on read and write. A field ending in `_percent` or `_percentage` holds 0–100 (`"23"`); a field named `rate` or ending in `_rate` holds a fraction (`"0.23"`). A tax rate has both: `rate` and `rate_percent`, and accepts either on write.
@@ -4,7 +4,7 @@ sidebarTitle: "Monetary Amounts"
4
4
  description: "How monetary values are represented in API responses"
5
5
  ---
6
6
 
7
- All monetary values in the Store API are returned as **strings** (e.g., `"29.99"`, `"0.0"`), not numbers. This preserves decimal precision and avoids floating-point rounding issues common with JSON numbers.
7
+ 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](#currency-decimal-places)).
8
8
 
9
9
  ## Response Format
10
10
 
@@ -35,7 +35,13 @@ This convention applies to all monetary fields across all resources:
35
35
  | **Payment** | `amount` |
36
36
  | **Gift Card** | `amount`, `amount_used`, `amount_authorized`, `amount_remaining` |
37
37
  | **Store Credit** | `amount`, `amount_used`, `amount_remaining` |
38
- | **Price** | `amount`, `compare_at_amount` |
38
+ | **Price** | `amount`, `compare_at_amount` (unit prices, up to four decimals) |
39
+
40
+ Rates, such as a tax line's `rate`, are decimal strings too, without trailing zeros (`"0.23"`).
41
+
42
+ ## Currency decimal places
43
+
44
+ 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.
39
45
 
40
46
  ## Working with Amounts
41
47
 
@@ -43,12 +49,15 @@ This convention applies to all monetary fields across all resources:
43
49
  ```typescript SDK
44
50
  const order = await client.orders.get('or_abc123', {}, { token });
45
51
 
52
+ import { compareMoney, sumMoney } from '@spree/sdk'
53
+
46
54
  // Raw string values
47
55
  order.total; // "129.99"
48
56
  order.display_total; // "$129.99"
49
57
 
50
- // Convert to number for calculations
51
- const total = parseFloat(order.total);
58
+ // Calculate on the strings, exactly
59
+ const subtotal = sumMoney(order.items.map((item) => item.total), order.currency); // "129.99"
60
+ const isFree = compareMoney(order.total, '0') === 0;
52
61
  ```
53
62
 
54
63
  ```javascript JavaScript
@@ -57,9 +66,9 @@ const data = await response.json();
57
66
  // Use display fields for rendering
58
67
  element.textContent = data.display_total; // "$129.99"
59
68
 
60
- // Use raw fields for calculations
61
- const total = parseFloat(data.total);
69
+ // Calculate on the raw strings with a decimal library, never parseFloat:
70
+ // 0.1 + 0.2 is 0.30000000000000004 as a JSON number.
62
71
  ```
63
72
 
64
73
 
65
- > **TIP:** Use `display_*` fields for rendering prices in the UI — they are pre-formatted with the correct currency symbol and decimal places based on the order's currency. Use the raw string fields when you need to perform calculations.
74
+ > **TIP:** Use `display_*` fields for rendering prices in the UI — they are pre-formatted with the correct currency symbol and decimal places based on the order's currency. Calculate on the raw strings with the SDK's `sumMoney`, `subtractMoney`, `multiplyMoney` and `compareMoney`, which work on decimal strings exactly.