Документация

Цены, количества и валюта

Почему в шаблоне ничего не считают, откуда берётся валюта и как правильно вывести сумму и дробное количество.

На этой странице

Правило одно, и оно короткое: в шаблоне ничего не считают. Числа приходят готовыми, тег {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 приходит пустым, если добирать уже не нужно или порога у магазина нет.

Обновлено 24 августа 2026