13.72. DD 72: Products Units#

13.72.1. Summary#

Introduce canonical unit_* metadata for merchant inventory so prices and stock levels can be expressed with fractional precision while retaining legacy integer fields for backwards compatibility. Provide guidance to wallets, PoS terminals, and merchant tooling to keep UX coherent across integrations.

13.72.2. Motivation#

Bug reports revealed two conflicting requirements:

  • Products sold by measurable attributes (for example potatoes by kilogram) need fractional support so customers can order 1.5 kg without hacks.

  • Discrete products (for example “pieces” of cheese) must remain integer-only; allowing 1.2 pc would break inventory management.

The existing API exposes only integer fields (quantity, total_stock, price). Simply switching to floating-point values would enable nonsensical orders and introduce rounding issues. After team discussion it was decided that explicit unit_* metadata can be introduced for overall cleanliness of the code base.

13.72.3. Requirements#

  • Preserve compatibility: accept and emit the legacy integer fields while marking them deprecated once unit_* alternatives exist. When both are supplied the backend must check that values match.

  • Use a predictable format: fixed-point decimal strings INTEGER[.FRACTION] with up to eight fractional digits; reject scientific notation and special floating-point tokens.

  • Provide backend-chosen defaults per unit identifier so new front-ends can present appropriate UI without manual configuration.

  • Allow merchants to override the default policy through explicit fields.

  • Update every affected endpoint (GET/POST/PATCH products, PoS inventory, lock, order creation, contract products) to expose and accept the new metadata.

  • Document expectations for merchant back-ends, PoS clients, and wallets to ensure consistent behaviour across the ecosystem.

13.72.4. Proposed Solution#

  1. Extend product schemas with optional metadata:

    • unit_allow_fraction (boolean)

    • unit_precision_level (integer 0–6)

    • unit_price (fixed-point decimal string)

    • unit_total_stock (fixed-point decimal string, -1 keeps the “infinite” semantics)

    Legacy price and total_stock remain, but become compatibility shims and must match the new values whenever present.

  2. Accept unit_quantity wherever clients submit quantities (inventory locks, inventory_products). The backend converts the decimal string into the legacy quantity and new quantity_frac pair for storage so existing clients keep working.

  3. Return both representations in all read APIs so integrators can migrate at their own pace.

  4. Default backend policies

    Any backend unit identifier that is not listed in the table below SHALL be treated as Custom: unit_allow_fraction = false, unit_precision_level = 0, and both default labels (long/short) echo the merchant-supplied string.

Default backend policies#

BackendStr

Type

Precision

Default label (long)

Default label (short)

Piece

int

0

piece

pc

Set

int

0

set

set

SizeUnitCm

float

1

centimetre

cm

SizeUnitDm

float

3

decimetre

dm

SizeUnitFoot

float

3

foot

ft

SizeUnitInch

float

2

inch

in

SizeUnitM

float

3

metre

m

SizeUnitMm

int

0

millimetre

mm

SurfaceUnitCm2

float

2

square centimetre

cm²

SurfaceUnitDm2

float

3

square decimetre

dm²

SurfaceUnitFoot2

float

3

square foot

ft²

SurfaceUnitInch2

float

4

square inch

in²

SurfaceUnitM2

float

4

square metre

SurfaceUnitMm2

float

1

square millimetre

mm²

TimeUnitDay

float

3

day

d

TimeUnitHour

float

2

hour

h

TimeUnitMinute

float

3

minute

min

TimeUnitMonth

float

2

month

mo

TimeUnitSecond

float

3

second

s

TimeUnitWeek

float

3

week

wk

TimeUnitYear

float

4

year

yr

VolumeUnitCm3

float

3

cubic centimetre

cm³

VolumeUnitDm3

float

5

cubic decimetre

dm³

VolumeUnitFoot3

float

5

cubic foot

ft³

VolumeUnitGallon

float

3

gallon

gal

VolumeUnitInch3

float

2

cubic inch

in³

VolumeUnitLitre

float

3

litre

L

VolumeUnitM3

float

6

cubic metre

VolumeUnitMm3

float

1

cubic millimetre

mm³

VolumeUnitOunce

float

2

fluid ounce

fl oz

WeightUnitG

float

1

gram

g

WeightUnitKg

float

3

kilogram

kg

WeightUnitMg

int

0

milligram

mg

WeightUnitOunce

float

2

ounce

oz

WeightUnitPound

float

3

pound

lb

WeightUnitTon

float

3

metric tonne

t

Custom

int

0

merchant string

merchant string

  1. Quantity presentation in wallets and orders

    When displaying order details or cart lines, wallet and POS front-ends MUST use the short unit label from the table above, appended to the numeric value with a non-breaking thin space (U+202F). Trailing zeros up to the declared unit_precision_level MUST be trimmed, but the displayed precision MUST NOT exceed the declared level. Examples:

    1.500 kg → shown as 1.5 kg
    3.00 pc  → shown as 3 pc
    

    For precision 0 units the fractional part is omitted entirely.

  2. Locale-aware unit translation rules for wallets

    Wallets SHOULD offer users the option to view quantities in familiar measurement systems. The following guidance applies:

    • Detect the buyer locale using the platform-standard mechanism (e.g. navigator.language in browsers or OS locale on mobile). Only when the locale primary region is in the CLDR “IU-customary group” (US, LR, MM, GB) SHALL conversions default to imperial/US-customary, and vice-versa when the merchant lists imperial units but the buyer locale is SI-centred.

    • Supported automatic conversions and factors (SI -> US and US -> SI):

      Supported automatic conversions and factors#

      SI unit

      US/imperial unit

      factor

      kilogram (kg)

      pound (lb)

      2.20462

      gram (g)

      ounce (oz)

      0.035274

      litre (L)

      fluid ounce (fl oz)

      33.814

      metre (m)

      foot (ft)

      3.28084

      square metre ()

      square foot (ft²)

      10.7639

      cubic metre ()

      cubic foot (ft³)

      35.3147

    • Conversions MUST round to the wallet’s target unit_precision_level using bankers-rounding to minimise cumulative error.

    • When a converted value is displayed it SHOULD be prefixed with “ca.” (or symbol) and rendered in a visually subdued style (e.g. 60% opacity) to signal approximation; the merchant-provided unit remains the authoritative primary value.

    • The original backend value MUST be preserved in the contract terms; conversions are presentation-only.

    • Wallets SHOULD expose a global numeric-system setting in their preferences with the values off, automatic, SI, and imperial.

      • off – never perform unit conversions; display exactly the merchant-supplied units.

      • automatic – apply the locale heuristic described above (imperial for US, GB, LR, MM; SI otherwise).

      • SI – always display quantities in SI units (no conversion if the merchant already uses SI).

      • imperial – always display quantities converted to imperial/US-customary units (no conversion if the merchant already uses imperial).

13.72.5. Definition of Done#

(Only applicable to design documents that describe a new feature. While the DoD is not satisfied yet, a user-facing feature must be behind a feature flag or dev-mode flag.)

  • Merchant backend accepts and emits the new metadata for product CRUD, inventory locks, and order creation.

  • Merchant SPAA has updated screen for product creation, which facilitates the default units.

  • POS and wallet reference implementations render fractional quantities according to unit_allow_fraction / unit_precision_level.

  • Legacy clients continue to function using the integer fields, with automated tests ensuring that canonical and legacy values stay in sync.

  • Wallets follows points 5 and 6 of Proposed Solution.

13.72.6. Alternatives#

  • Replace integers with floating-point numbers. This was ruled out because it cannot prevent semantically invalid requests (for example 1.2 pieces) and reintroduces floating-point rounding issues into price calculations.

13.72.7. Drawbacks#

  • Payloads grow slightly because responses include both canonical decimal strings and legacy integers.

  • Integrations must update their tooling to emit and validate decimal strings, which adds complexity compared to sending plain integers.

13.72.8. Discussion / Q&A#