# ADR 0005 — Each seller prices in one currency; RAW carries it, BASE stays dollars

- Status: accepted
- Date: 2026-10-25

## Context

Every product price was stored in US dollars (RAW) and turned into Syrian pounds by the
product's `exchange_type`. Sellers who price in pounds had to convert by hand, and their
prices moved with every rate change. The owner asked for:

- each seller priced in one currency, set by staff (any active currency);
- customers always seeing SYP, and a product typed in SYP shown exactly as typed (to the
  store's 5 SYP step), never moved by the rate;
- the seller panel and seller app (orders, invoice, statistics, balance) in the seller's
  currency, and nothing else changing: orders, reports and statistics as they were;
- partner-store sellers priced in their currency too.

## Decision

1. **The currency rides on the product.** `products.price_currency` (NULL = the system
   default currency, US dollars) says what the product's RAW columns are in. It always
   equals `shops.price_currency` of its owner. Pricing reads it from the row, so no join
   or extra query is needed. Only `App\Services\Currency` writes it: stamped at creation,
   kept on every Eloquent save, and changed only together with the prices.
2. **One hinge.** `product_syp_factor()` (PHP) and `raw_to_base_sql()` (SQL) are the only
   places that choose a product's rate. A SYP product's factor is 1. Another currency C
   divides the exchange-type factor by C per dollar. NULL is unchanged. While no product
   has a currency (the "in use" list is empty), the SQL is byte-identical to before.
3. **BASE stays US dollars.** Carts, orders, coupons, shipping, balances in reports and the
   order-date rate history are untouched. A SYP product's BASE is `pounds / rate`, and
   orders are shown at that same rate, so the order shows the same pounds.
4. **The seller's money is frozen in their currency.** An order line of a non-dollar
   seller stores `seller_currency` / `seller_amount`: its purchase price as the seller
   priced it, times the quantity, with no rate. The balance (`shops.admin_to_pay`),
   payouts, withdraw requests and commission history moves are in the seller's currency.
   `purchase_price` / `seller_earning` / `admin_commission` stay BASE for the reports.
5. **Switching is a one-time conversion by staff.** `SellerCurrencySwitch::run()` converts
   every product at its own rate, so the customer's SYP does not move. Into SYP, the
   selling prices become exactly the pounds that were shown. The balance, the lines'
   `seller_amount` and the commission history move at the day's exchange rate. Everything
   rewritten is backed up first (`seller-currency:restore`). It is refused while a
   withdraw request waits.

## Consequences

- A new query that prices products from a partial `select()` must include
  `price_currency`. Once a currency is in use, a `Product` model priced without it is
  reported (it throws in tests).
- A new sum or sort of RAW columns must go through `raw_to_base_sql()` /
  `order_by_product_price()`, never a bare `unit_price`.
- A new seller-facing amount goes through `SellerOrderAmounts::money()` / `mine()`, never
  `usd()` or `single_price()`.
- Exactness is to the store's 5 SYP step. BASE columns are `double(20,2)`, so a pound
  price round-trips to within about 0.74 SYP at a rate of about 147; the 5 SYP rounding
  hides that while the rate stays below about 500 SYP per dollar.
- Old payouts and withdraw requests keep the currency they were made in (NULL = dollars).
