# `PhoenixKitEcommerce.PriceDisplay`
[🔗](https://github.com/BeamLabEU/phoenix_kit_ecommerce/blob/0.3.0/lib/phoenix_kit_ecommerce/price_display.ex#L1)

How a price is written on the storefront: `From €40.00 /hour`.

Two independent decorations, both optional and both off by default:

  * a **unit** — free text per language (`"per hour"`, `"/m²"`, `"в час"`),
    so a service shop can state its pricing model. Free text rather than a
    vocabulary: nothing here needs to know what an hour is, and every shop
    invents units the next one has never heard of.

  * a **"From" prefix** — set explicitly by the admin, or implied when the
    product's options genuinely produce a price range.

## Storage

Both live under one reserved key in `Product.metadata`:

    %{"_price_display" => %{
        "unit" => %{"en" => "per hour", "ru" => "в час"},
        "from" => true
      }}

A single versioned namespace rather than two loose top-level keys: the
metadata map is an advertised extension point, so this module's data
stays in one place that can grow (and be recognised) without colliding
with whatever a host already stores there. `_`-prefixed matches the
existing `_option_values` / `_price_modifiers` convention, and option
keys may not begin with `_`, so no user-defined option can shadow it.

## Context is load-bearing

`render/4` takes a context because the same product means different
things on different pages:

  * `:catalog` — the product's *asking* price. May show "From", and the
    amount comes from the product's option-aware range.
  * `:selected` — the price for the options the shopper picked. Exact, so
    never "From"; keeps the unit.
  * `:cart` / `:order` — a SNAPSHOT of what was (or will be) charged.
    Exact, never "From", and the unit comes from the snapshot rather than
    the live product, so an edit or deletion cannot rewrite a line the
    customer already agreed to.

Absent data renders exactly what the module rendered before this existed.

# `any_line_on_request?`

Whether any line in a cart or order was agreed as "price on request".

Suppressing the amount per line leaves the TOTAL saying something the shop
does not mean: an on-request line snapshots 0, so a cart holding one reads
"Total 0.00" beside a "Proceed to Checkout" button, and a mixed cart quietly
omits the on-request item from a figure the shopper reads as the whole bill.
The totals themselves stay as they are — they are the amounts billing will
charge — so the pages that show one disclose what it leaves out.

# `build`

Builds the storable namespace map from admin form input.

Blank units are dropped so an untouched form does not persist empty
strings, and values are length-bounded — this text renders next to a
price on a public page, it is not a description field.

# `build`

# `line_on_request?`

Whether a stored LINE was agreed as "price on request".

This is the snapshot side of `on_request?/1`, and every page that renders a
line must ask it rather than reading the live product. It accepts both shapes
the storefront holds a line in: a `CartItem` (the flag lives under
`metadata`) and an order line item (a plain map, flag at the top level).

A helper rather than the inline `(item.metadata || %{})["price_on_request"] ==
true` this replaces: five templates need the answer — cart, checkout review,
the product page's "already in cart" notice, the confirmation and order
details — and the two that expressed it inline were the two that got it, while
the other three rendered `0.00` for a line with no price.

# `metadata_key`

The reserved metadata key. Consumers that copy metadata around (the CSV
upsert, the product form) use this to preserve the namespace.

# `on_request?`

Whether this product's price is negotiated rather than listed
("price on request" / "цена договорная").

Read the LINE's own snapshot for `:cart` and `:order`
(`metadata["price_on_request"]`), never the live product. A product can be edited or deleted after a line is
created (`product_uuid` is ON DELETE SET NULL), and a line that was agreed as
"on request" must not later render as a number, nor a priced line as "on
request".

# `render`

Renders a price for display.

## Options

  * `:amount` — the exact amount to render (required for `:selected`,
    `:cart` and `:order`; ignored for `:catalog`, which derives the
    product's range).
  * `:unit` — an explicit unit string, used by snapshot contexts so a
    cart line can render the unit it stored rather than the live one.
  * `:language` — the viewer's language, for unit resolution.
  * `:range_style` — `:from` (default) or `:range`, catalog only.

# `settings`

Reads the display settings out of a product (or a raw metadata map).

Returns `%{unit: %{lang => text}, from: boolean, on_request: boolean}` with
safe defaults.

# `unit_for`

The unit text for a language, with default-language fallback.

Returns nil when the product has no unit — callers render the plain price.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
