Skip to content

Checkout Process

Checkout Flow Architecture

The checkout is a multi-step process controlled by the sekce (section) variable. The main template kosik.tpl acts as a router, including the appropriate step partial based on the current section:

twig
{# kosik.tpl — checkout router #}
{% if kos %}
    {# nesouhlas = terms and conditions not accepted #}
    {% if nesouhlas %}
        <div class="stav_err">{% trans 'checkout.terms-and-conditions.error.message' %}</div>
    {% endif %}

    {% if not sekce or sekce == 'kosik' %}
        {% include "kosik_obsah.tpl" %}       {# cart content step #}
    {% elseif sekce == 'udaje' %}
        {% include "kosik_udaje.tpl" %}        {# address entry step #}
    {% elseif sekce == 'dodani' %}
        {% include "kosik_dodani.tpl" %}       {# delivery selection step #}
    {% endif %}
{% else %}
    {% include 'kosik_prazdny.tpl' %}          {# empty cart #}
{% endif %}

Step Templates

TemplatePurpose
kosik.tplRouter — includes step partials based on sekce
kosik_obsah.tplCart content — product list, quantities, discount codes
kosik_dodani.tplDelivery — transport and payment selection (see Delivery)
kosik_udaje.tplAddress — customer details entry
kosik_souhrn.tplOrder summary sidebar — included in every step
kosik_prazdny.tplEmpty cart fallback — shown when kos is falsy
kosik_kroky.tplStep indicator breadcrumb — included within each step
kosik_darky.tplGift selection — included when this.slaveConfig.enableGifts is true

The step flow is configured in config.json via the cartSteps setting (see Configuration), which maps the progression: content -> delivery -> address. Each step form contains a hidden toSekce field that tells the server which step to advance to on submit.

Cross-sell Products

After the checkout steps, kosik.tpl can display a cross-sell product carousel using a content grid slot:

twig
{# Show cross-sell products below checkout when on the cart content step #}
{% if kos and not sekce and this.contentGrid('checkout-select-products').section.product is not empty %}
    {% set section = this.contentGrid('checkout-select-products').section %}
    <i:list-container type="product" hide-empty collection="{{ section.product }}">
        <h3>{{ section.headline }}</h3>
        <div data-role="list" class="listing"></div>
    </i:list-container>
{% endif %}

Cart Items

The cart content step (kosik_obsah.tpl) displays products using the cart.catalogItems loop. The entire product list is wrapped in a form that posts to this.urlSave. The cart content area uses i:dynamic="cart, objednavka" so it reloads automatically when cart state changes (e.g., quantity updates). See Syntax - Dynamic Content Reload for more on i:dynamic.

Info Banner

An optional informational banner can be displayed above the cart items using a content grid slot:

twig
{% if this.contentGrid('checkout-content-info') %}
    {% set block = this.contentGrid('checkout-content-info').checkouttext %}
    <div style="color: {{ block.color }}; background: {{ block.bgcolor }};
                border: {{ block.bordercolor ? '1px solid ' ~ block.bordercolor }}">
        {{ block.text }}
    </div>
{% endif %}

Product List

twig
<form action="{{ this.urlSave }}" method="post">
    <h1>{% trans 'checkout.headline' %} ({{ cart.catalogItemTotalAmount }} {% trans 'template.qty' %})</h1>

    {% for orderItem in cart.catalogItems %}
        <div class="cartie {{ orderItem.gift ? 'cartie--gift' }}">
            {# Product image — use variant image if available #}
            <a href="{{ orderItem.product.url }}">
                {% if orderItem.variant.image %}
                    <i:img src="orderItem.variant.image" format="130x92" fill />
                {% else %}
                    <i:img src="orderItem.image" format="130x92" fill />
                {% endif %}
            </a>

            {# Product name and child items (e.g., bundle contents) #}
            <a href="{{ orderItem.product.url }}">
                <strong>{{ orderItem.item.name }}</strong>
            </a>
            {% if orderItem.children is not empty %}
                {% for item in orderItem.children %}
                    {{ item.name }} {{ item.variantName }}
                    {% if item.priceTotal.current.value != 0 %}
                        ({{ item.priceTotal.current.formatted }})
                    {% endif %}
                {% endfor %}
            {% endif %}

            {# Variant name #}
            {% if orderItem.variantName %}
                {% trans 'template.variant' %}: <strong>{{ orderItem.variantName }}</strong>
            {% endif %}

            {# Unit price with optional discount display #}
            {% if orderItem.item.price.discount %}
                <span class="u-decoration-through">{{ orderItem.item.price.beforeDiscount.formatted }}</span>
            {% endif %}
            <strong>{{ orderItem.item.price.current.formatted }}</strong>

            {# Quantity input — name uses ks[orderItem.id] pattern #}
            {# For gift items, show "1 qty" text instead of input #}
            {% if orderItem.gift %}
                1 {% trans 'template.qty' %}
            {% else %}
                <input type="tel" name="ks[{{ orderItem.id }}]"
                       value="{{ orderItem.amount }}" size="1" min="0" />
            {% endif %}

            {# Total price per item #}
            {% if orderItem.gift %}
                <strong>{% trans 'global.free' %}</strong>
            {% else %}
                <strong>{{ orderItem.priceTotal.current.formatted }}</strong>
            {% endif %}

            {# Remove link — uses ?del= query parameter #}
            {% if not orderItem.gift %}
                <a href="?del={{ orderItem.id }}">{% trans 'cart-popup.item.delete' %}</a>
            {% endif %}
        </div>
    {% endfor %}

    {# Hidden field to advance to delivery step on submit #}
    <input type="hidden" name="toSekce" value="dodani">
</form>

orderItem Properties

PropertyDescription
orderItem.product.urlURL to the product detail page
orderItem.imageProduct image
orderItem.variant.imageVariant-specific image (if available)
orderItem.item.nameProduct name
orderItem.variantNameSelected variant name
orderItem.item.price.current.formattedFormatted unit price
orderItem.item.price.beforeDiscount.formattedPrice before discount
orderItem.item.price.discountPercentDiscount percentage
orderItem.priceTotal.current.formattedTotal price for the line
orderItem.amountQuantity in cart
orderItem.idOrder item ID (used for quantity input names and removal)
orderItem.giftWhether the item is a gift (read-only quantity, free)
orderItem.childrenChild items for bundles/sets
orderItem.flagNewWhether the product is flagged as new
orderItem.labelsCustom product labels
orderItem.freeDeliveryWhether the item qualifies for free delivery
orderItem.item.availability.textAvailability text
orderItem.item.availability.hoursAvailability in hours
orderItem.availableUpsellsPer-item upsell products

Per-Item Upsells

Each cart item can offer upsell products (e.g., extended warranty, gift wrapping). These are rendered as checkboxes within the item row:

twig
{% if orderItem.availableUpsells is not empty and not orderItem.gift %}
    <strong>{% trans 'cart-dialog.upsells.headline' %}</strong>
    {% for upsell in orderItem.availableUpsells %}
        <input type="checkbox" name="upsell[{{ orderItem.id }}]"
               value="{{ orderItem.id }}-{{ upsell.id }}"
               if:checked="upsell.inCart">
        <label>
            {% if upsell.image %}
                <i:img src="upsell.image" format="40x40" fill />
            {% endif %}
            {{ upsell.name }}
            <span>{{ upsell.price.current.formatted }}</span>
        </label>
    {% endfor %}
{% endif %}

Loyalty Program Integration

When the loyalty program is enabled, customers can apply loyalty points to their order:

twig
{% if customer and this.slaveConfig.enableLoyaltyProgram and cart.useLoyaltyPointsManually %}
    <strong>{% trans 'checkout.points.text' %} {{ customer.userPointsAvailable }}
        {% trans %}user.credits-count{% plural customer.userPointsAvailable %}user.credits-count-plural{% endtrans %}:
    </strong>
    {# Hidden input ensures value submits even when checkbox is unchecked #}
    <input type="hidden" name="uplatnitbody" value="{{ cart.useLoyaltyPoints ? 1 : 0 }}" />
    <input type="checkbox" name="uplatnitBody"
           value="1"
           if:checked="cart.useLoyaltyPoints">
    <label>{% trans 'checkout.points.use' %}</label>
{% endif %}

The cart page also shows how many points the order will earn, and prompts unregistered users to sign up:

twig
{% if this.slaveConfig.enableLoyaltyProgram %}
    {% trans 'checkout.loyalty.you-get' %} {{ cart.loyaltyPoints }}
    {% trans %}user.credits-count{% plural cart.loyaltyPoints %}user.credits-count-plural{% endtrans %}
    {% trans 'checkout.loyalty.discount-to-next-purchase' %}.
    {% if not user %}
        {% trans 'checkout.loyalty.dont-forget' %}
        <a href="{{ iUrl('User/Registrace') }}">{% trans 'checkout.loyalty.register' %}</a>.
    {% endif %}
{% endif %}

Discount Codes

Discount items applied to the cart are displayed using the cart.discountItems loop. They appear after the product list in kosik_obsah.tpl:

twig
{% for orderDiscountCart in cart.discountItems %}
    <div class="cartie">
        {# Discount icon from SVG sprite #}
        <svg class="icon"><use xlink:href="{{ images('symbol-defs.svg#bi-Discount') }}"></use></svg>

        {# Discount name #}
        {{ orderDiscountCart.name }}

        {# Discount value — either percentage or fixed amount #}
        {% if orderDiscountCart.percent > 0 %}
            <strong>-{{ orderDiscountCart.percent }}%</strong>
        {% elseif orderDiscountCart.price.current.value > 0 %}
            <strong>{{ orderDiscountCart.price.current.formatted }}</strong>
        {% endif %}

        {# Remove discount link — uses ?delSleva= query parameter #}
        <a href="?delSleva={{ orderDiscountCart.id }}">
            {% trans 'cart-popup.item.delete' %}
        </a>
    </div>
{% endfor %}

The voucher code input is typically placed in the order summary sidebar (kosik_souhrn.tpl), sending the code via the slev_kod form field.

Gift Selection

When gift selection is enabled in the shop configuration, the cart content step includes a gift picker partial:

twig
{% if this.slaveConfig.enableGifts %}
    {% include 'kosik_darky.tpl' %}
{% endif %}

Order Summary Sidebar

Each checkout step includes the order summary sidebar by passing a checkout context variable that controls what the sidebar displays (totals, buttons, voucher input):

twig
{# In kosik_obsah.tpl (cart content step) #}
{% include 'kosik_souhrn.tpl' with {'checkout': 'products'} %}

{# In kosik_dodani.tpl (delivery step) #}
{% include 'kosik_souhrn.tpl' with {'checkout': 'delivery'} %}

The sidebar adapts its content based on the checkout variable — showing different submit buttons and information depending on the current step.

Variant Selection

Enabling variant selection in the cart detail requires three steps:

  1. In config.json, list the supported selection modes (dropdown, icons, ...)
  2. In the shop admin panel, define the steps (which attribute set uses which selection mode, labels, ...)
  3. In the template, implement i:variant-choice

i:variant-choice

The i:variant-choice tag is used inside the add-to-cart form. It handles rendering the required number of steps, progressive loading of steps, pre-selection based on URL or customer preferences, URL updates on selection, etc.

Important elements:

  • else branch - if multiple selection modes are supported, one (ideally the simplest, such as a dropdown) should be in the else branch. If the template only accounts for certain modes and the shop expects different ones, purchasing would be impossible -- the final else branch ensures the purchase can still proceed.
  • data-role="step" - wrapper defining individual steps; when loading choices based on the current selection, only the subsequent steps are replaced
  • reload-targets - classes (or CSS selector) of elements that should be updated on each selection change -- typically images, price, availability, etc. These elements must first be marked with i:dynamic in the template.
  • step.inputName, choice.inputValue - name/value for the form input. Whether a select or radio is used is up to the template.
  • choice.image - product or variant image (typically a product photo), not available everywhere
  • choice.icon - image uploaded for the attribute set (typically an icon)
  • choice.color - HEX color code set on the attribute value
twig
<i:variant-choice product="product" reload-targets=".js_var_change_example">
    {%for step in product.variantChoiceSteps%}
    <div data-role="step">
        {%if step.mode == 'icons'%}
            <h5>{{step.headline}}</h5>
            <ul>
                {%for choice in step.choices%}
                    <li>
                        <label for="color-{{choice.inputValue}}" title="{{choice.name}}" class="custom-tooltip" i:href="{{choice}}">
                            <input name="{{step.inputName}}" type="radio" value="{{choice.inputValue}}" required data-msg-required="{{step.messageEmpty}}" if:checked="choice.selected">
                            {%if choice.image%}
                                <i:img src="choice.image" format="50x50" fill lazyload/>
                            {%elseif choice.icon%}
                                <i:img src="choice.icon" format="50x50" fill vector lazyload/>
                            {%elseif choice.color%}
                                <span style="background-color: {{choice.color}}"/>
                            {%endif%}
                            {{choice.name}}
                        </label>
                    </li>
                {%endfor%}
            </ul>
        {%else%}
            <h5>{%if step.headline%}{{step.headline}}{%else%}{%trans%}Vyberte variantu{%endtrans%}{%endif%}</h5>
            <select name="{{step.inputName}}" required>
                <option value="">{{step.placeholder}}</option>
                {%for choice in step.choices%}
                    <option value="{{choice.inputValue}}" if:selected="choice.selected">
                        {{choice.name}}
                    </option>
                {%endfor%}
            </select>
        {%endif%}
    </div>
    {%endfor%}
</i:variant-choice>