---
url: /cart.md
---
# 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

| Template | Purpose |
|---|---|
| `kosik.tpl` | Router — includes step partials based on `sekce` |
| `kosik_obsah.tpl` | Cart content — product list, quantities, discount codes |
| `kosik_dodani.tpl` | Delivery — transport and payment selection (see [Delivery](/delivery)) |
| `kosik_udaje.tpl` | Address — customer details entry |
| `kosik_souhrn.tpl` | Order summary sidebar — included in every step |
| `kosik_prazdny.tpl` | Empty cart fallback — shown when `kos` is falsy |
| `kosik_kroky.tpl` | Step indicator breadcrumb — included within each step |
| `kosik_darky.tpl` | Gift selection — included when `this.slaveConfig.enableGifts` is true |

The step flow is configured in `config.json` via the `cartSteps` setting (see [Configuration](/config)), 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.

### Address Step Fields

`kosik_udaje.tpl` posts the delivery address as `email`, `telefon`, `jmeno`, `prijmeni`, `ulice`, `mesto`, `psc`, `okres`, `stat` (plus `firma`, `ic`, `dic`), and the billing address with a `fakturacni_` prefix when `adresy=ruzne`. Two of them depend on the country:

**Region (`okres`)** — `this.countryRegions(countryCode)` returns `value => label` pairs for countries whose address is not deliverable without a region (US states, Romanian județe, Mexican estados) and an empty array for everyone else. Render a `<select name="okres">` from it; the stored value comes back as `cart.address.delivery.region`. US states are auto-filled from the ZIP when the select is skipped.

```twig
{% if this.countryRegions(cart.address.delivery.country.code) is not empty %}
    <select name="okres" required>
        <option value="">~ {% trans 'checkout.delivery.region.option' %} ~</option>
        {% for code, name in this.countryRegions(cart.address.delivery.country.code) %}
            <option value="{{ code }}" if:selected="cart.address.delivery.region == code">{{ name }}</option>
        {% endfor %}
    </select>
{% endif %}
```

**Second street line (`ulice2` / `fakturacni_ulice2`)** — for address formats with a line between the street and the postcode (the Mexican *colonia*, a US apartment). The contact has a single street column, so the server folds the line into it as `street, line` (re-submitting the folded street does not double the line). To pre-fill the field when the customer steps back, split the stored street at its last `, `:

```twig
{% set streetLine = cart.address.delivery.street %}
{% set secondStreetLine = '' %}
{% if ', ' in (cart.address.delivery.street ?? '') %}
    {% set streetParts = cart.address.delivery.street|split(', ') %}
    {% set secondStreetLine = streetParts|last %}
    {% set streetLine = streetParts|slice(0, -1)|join(', ') %}
{% endif %}
<input name="ulice" value="{{ streetLine }}" autocomplete="street-address" />
<input name="ulice2" value="{{ secondStreetLine }}" autocomplete="address-level3" />
```

### Cross-sell Products

After the checkout steps, `kosik.tpl` can display a cross-sell product carousel using a [content grid](/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](/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

| Property | Description |
|---|---|
| `orderItem.product.url` | URL to the product detail page |
| `orderItem.image` | Product image |
| `orderItem.variant.image` | Variant-specific image (if available) |
| `orderItem.item.name` | Product name |
| `orderItem.variantName` | Selected variant name |
| `orderItem.item.price.current.formatted` | Formatted unit price |
| `orderItem.item.price.beforeDiscount.formatted` | Price before discount |
| `orderItem.item.price.discountPercent` | Discount percentage |
| `orderItem.priceTotal.current.formatted` | Total price for the line |
| `orderItem.amount` | Quantity in cart |
| `orderItem.id` | Order item ID (used for quantity input names and removal) |
| `orderItem.gift` | Whether the item is a gift (read-only quantity, free) |
| `orderItem.children` | Child items for bundles/sets |
| `orderItem.flagNew` | Whether the product is flagged as new |
| `orderItem.labels` | Custom product labels |
| `orderItem.freeDelivery` | Whether the item qualifies for free delivery |
| `orderItem.item.availability.text` | Availability text |
| `orderItem.item.availability.hours` | Availability in hours |
| `orderItem.availableUpsells` | Per-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>
```
