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

Ціни, кількості та валюта

Чому в шаблоні нічого не рахують, звідки береться валюта і як правильно вивести суму та дробову кількість.

На цій сторінці

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