Цены, количества и валюта
Почему в шаблоне ничего не считают, откуда берётся валюта и как правильно вывести сумму и дробное количество.
На этой странице
Правило одно, и оно короткое: в шаблоне ничего не считают. Числа приходят
готовыми, тег {money} только форматирует.
Почему нельзя считать в шаблоне
Итог заказа считает одно место магазина — то же самое, что печатает счёт, отправляет письмо покупателю и передаёт сумму платёжному шлюзу. В эту сумму входят скидки, промокод, бонусные баллы, надбавка за способ оплаты, стоимость доставки, доп-услуги и налог.
Своя формула в шаблоне повторяет часть этого списка и рано или поздно разъезжается с настоящим итогом: покупатель видит в корзине одну сумму, на странице оформления — другую, а в письме — третью. Такое уже случалось, причём разъезжалось не сразу, а после того, как владелец включал ещё одну возможность.
Поэтому складывать, умножать и вычитать суммы в шаблоне нельзя. Всё нужное уже посчитано:
{$cart.items_total} сумма позиций
{$cart.discount_total} скидки
{$cart.catalog_savings} выгода по каталожным акциям
{$cart.total} итог
{$item.total} сумма позиции
{$packaging.price} цена упаковки
{$service.price} стоимость услуги
Валюта
Каталог хранит цены в основной валюте магазина, а витрина показывает их в своей: один и тот же товар продаётся и в гривневом магазине, и в долларовом. Перевод делается на границе выдачи, до того, как данные попадут в шаблон.
Переключателя валют у покупателя нет — валюту задаёт витрина. Поэтому цена на странице определена однозначно, и гадать, в чём она, не нужно.
Что это значит на практике:
- цены товара, вариантов, упаковок, услуг и аксессуаров — уже в валюте витрины;
- процент скидки считается до перевода, поэтому от валюты не зависит;
- суммы корзины и оформления — в валюте корзины, она же валюта витрины;
- суммы заказа — в валюте заказа, зафиксированной при оформлении.
Последнее важно для кабинета покупателя и страницы «заказ принят». Заказ никогда не переоценивается: у него свой курс и свои цены. Заказ, сделанный месяц назад, показывается в тех же деньгах, в каких его оформили, даже если курс с тех пор изменился.
Как выводить суммы
Валюту берут из данных, а не из настроек темы:
{money amount=$product.price currency=$storefront.currency}
{money amount=$cart.total currency=$cart.currency}
{money amount=$order.total currency=$order.currency}
⚠️ Валюту передавайте всегда. По умолчанию {money} форматирует сумму
долларами. На тестовом магазине с валютой по умолчанию ошибку не видно — а в
гривневом магазине цена уедет с долларовым знаком, и заметит это покупатель.
Где копейки ломают вёрстку — сумма корзины в шапке, подпись на карточке — уберите их:
{money amount=$shop->cartTotal() currency=$storefront.currency precision=0}
Зачёркнутая цена и процент скидки
{money amount=$product.price currency=$storefront.currency}
{if $product.compare_price}
<s>{money amount=$product.compare_price currency=$storefront.currency}</s>
<span class="badge">−{$product.discount_percent}%</span>
{/if}
compare_price приходит пустым, когда зачёркивать нечего: старая цена не задана
или она не больше текущей. Считать процент самому не нужно — discount_percent
уже целое число.
Количества
Товары бывают дробными: метры, килограммы, площадь, литры. Количества выводят
тегом {qty}:
{qty value=$item.qty} {$item.unit} → 30,8 м²
Тег пишет число по-местному («30,8», а не «30.8»), отбрасывает хвостовые нули («2», а не «2,000») и не ставит разделитель разрядов. Последнее не мелочь: это же число подставляется в поле ввода количества, и «1 200» браузер прочитал бы как «1».
Шаг и минимум количества приходят вместе с товаром:
<input type="number" data-qty
value="{$product.qty_min}"
min="{$product.qty_min}"
step="{$product.qty_step}">
Если шаг не задан, оба равны единице — обычный штучный товар.
Пустая корзина отдаёт неполный набор
⚠️ Когда корзины ещё нет, в $cart приходят только count, qty, total,
discount_total, discounts, currency, items, in_cart — без
items_total и catalog_savings.
Поэтому весь блок итогов оборачивают проверкой:
{if $items}
<div class="cart-totals">
<div>Товары: {money amount=$cart.items_total currency=$cart.currency}</div>
{if $cart.discount_total}
<div>Скидка: −{money amount=$cart.discount_total currency=$cart.currency}</div>
{/if}
<div class="total">Итого: {money amount=$cart.total currency=$cart.currency}</div>
</div>
{else}
<p>В корзине пока пусто</p>
{/if}
Без этой проверки шаблон на пустой корзине полезет за несуществующим ключом.
Скидки: откуда взялась цена
У позиции корзины есть discount_reasons — почему цена ниже обычной:
{if $item.compare_price}
<s>{money amount=$item.compare_price currency=$cart.currency}</s>
{foreach $item.discount_reasons as $reason}
<span class="reason">{$reason}</span>
{/foreach}
{/if}
Без объяснения позиция со скидкой выглядит как ошибка в цене, и покупатель пишет в поддержку.
У корзины целиком то же самое лежит в $cart.discounts — список применённых
скидок с названиями и суммами.
Позиции, которые нельзя трогать
У позиции есть признак locked — её положил магазин, а не покупатель: подарок,
приз, товар с зафиксированной ценой. У такой позиции не показывают счётчик
количества и кнопку удаления, а подпись note объясняет, откуда она взялась:
{if $item.locked}
<span class="cart-item__note">{$item.note}</span>
{else}
<input type="number" data-cart-qty="{$item.id}" value="{$item.qty}">
<button data-cart-remove-item="{$item.id}">Удалить</button>
{/if}
Минимальная сумма и бесплатная доставка
{if $below_min}
<p class="warn">Минимальная сумма заказа —
{money amount=$min_order currency=$cart.currency}</p>
{/if}
{if $free_shipping_left}
<p>До бесплатной доставки осталось
{money amount=$free_shipping_left currency=$cart.currency}</p>
{/if}
free_shipping_left приходит пустым, если добирать уже не нужно или порога у
магазина нет.