Skip to content
  1. Extras
  2. MiniShop3
  3. Frontend interface
  4. Customer account
  5. Order history

Order history

Customer order history page. Shows all orders with status filter and pagination, plus full details for a single order.

Page structure

ComponentChunkPurpose
Base layouttpl.msCustomer.baseWrapper with sidebar
Sidebartpl.msCustomer.sidebarAccount navigation
Order listtpl.msCustomer.ordersOrders table
Order rowtpl.msCustomer.order.rowSingle order row
Order detailstpl.msCustomer.order.detailsFull order info

Snippet call

fenom
{'!msCustomer' | snippet: [
    'service' => 'orders',
    'limit' => 20
]}

Parameters

ParameterDefaultDescription
serviceService type (orders)
tpltpl.msCustomer.ordersOrder list chunk
orderTpltpl.msCustomer.order.rowOrder row chunk
detailTpltpl.msCustomer.order.detailsOrder details chunk
limit20Orders per page
unauthorizedTpltpl.msCustomer.unauthorizedChunk for guests
returntplFormat: tpl or data

Modes

URLModeDescription
/cabinet/orders/ListOrders table
/cabinet/orders/?order=550e8400-...DetailsOrder details by UUID
/cabinet/orders/?status=2FilterOrders with status 2
/cabinet/orders/?offset=20PaginationSecond page

UUID in order URLs

As of v1.6, order detail links use UUID instead of numeric ID. This is more secure: it does not expose order count and prevents guessing other customers' orders by ID.

URL format: ?order=550e8400-e29b-41d4-a716-446655440000

Customer order cancellation

The customer can cancel an order if its current status is in the allowed list (setting ms3_customer_cancel_allowed_statuses).

How it works

  1. The server provides placeholder {$can_cancel} for each order
  2. The "Cancel" button is shown only when {$can_cancel} = true
  3. On click — confirmation dialog via ms3Confirm
  4. API call POST /api/v1/customer/orders/{id}/cancel
  5. Page reloads after successful cancellation

Cancel button in chunk

fenom
{if $can_cancel}
<button type="button"
        class="btn btn-sm btn-outline-danger ms3-order-cancel"
        data-order-id="{$id}"
        data-confirm="{'ms3_customer_order_cancel_confirm' | lexicon}">
    {'ms3_customer_order_cancel' | lexicon}
</button>
{/if}

System settings

SettingDefaultDescription
ms3_customer_cancel_allowed_statuses2,3Status IDs for which cancellation is allowed
ms3_status_canceled0Status ID assigned to canceled orders

Order list placeholders

In tpl.msCustomer.orders

PlaceholderTypeDescription
{$orders}stringRendered order rows (HTML)
{$orders_count}intOrders on page
{$total}intTotal orders
{$statuses}arrayStatuses for filter
{$pagination}arrayPagination data
{$customer}arrayCustomer data

In tpl.msCustomer.order.row

PlaceholderTypeDescription
{$id}intOrder ID
{$num}stringOrder number (MS-00015)
{$createdon_formatted}stringCreated date
{$cost_formatted}stringOrder total
{$status_id}intStatus ID
{$status_name}stringStatus name
{$status_color}stringStatus color (HEX without #)

Order list chunk

fenom
{* tpl.msCustomer.orders *}
{extends 'tpl.msCustomer.base'}

{block 'content'}
<div class="ms3-customer-orders" data-api-url="{$api_url|default:''}">
    <div class="card shadow-sm">
        <div class="card-header bg-primary text-white">
            <h5 class="mb-0">{'ms3_customer_orders_title' | lexicon}</h5>
        </div>
        <div class="card-body">
            {* Status filter *}
            {if $statuses}
            <form class="mb-4" method="get" action="">
                <div class="row align-items-end">
                    <div class="col-md-4">
                        <label for="status-filter" class="form-label">
                            {'ms3_customer_orders_filter_by_status' | lexicon}
                        </label>
                        <select class="form-select" id="status-filter" name="status"
                                onchange="this.form.submit()">
                            <option value="">
                                {'ms3_customer_orders_all_statuses' | lexicon}
                            </option>
                            {foreach $statuses as $status}
                            <option value="{$status.id}" {if $status.selected}selected{/if}>
                                {$status.name}
                            </option>
                            {/foreach}
                        </select>
                    </div>
                    <div class="col-md-2">
                        <button type="button" class="btn btn-secondary"
                                onclick="location.href='?'">
                            {'ms3_customer_orders_reset_filter' | lexicon}
                        </button>
                    </div>
                </div>
            </form>
            {/if}

            {* Order list *}
            {if $orders_count > 0}
            <div class="table-responsive">
                <table class="table table-hover align-middle ms3-order-table">
                    <thead class="table-light">
                        <tr>
                            <th>{'ms3_customer_order_num' | lexicon}</th>
                            <th>{'ms3_customer_order_date' | lexicon}</th>
                            <th>{'ms3_customer_order_status' | lexicon}</th>
                            <th class="text-end">{'ms3_customer_order_total' | lexicon}</th>
                            <th class="col-actions"></th>
                        </tr>
                    </thead>
                    <tbody>
                        {$orders}
                    </tbody>
                </table>
            </div>

            {* Pagination *}
            {if $pagination.total_pages > 1}
            <nav aria-label="{'ms3_customer_orders_pagination' | lexicon}">
                <ul class="pagination justify-content-center">
                    {if $pagination.has_prev}
                    <li class="page-item">
                        <a class="page-link" href="?offset={$pagination.prev_offset}">
                            {'ms3_customer_orders_prev' | lexicon}
                        </a>
                    </li>
                    {/if}

                    {foreach $pagination.pages as $page}
                    <li class="page-item {if $page.active}active{/if}">
                        <a class="page-link" href="?offset={$page.offset}">
                            {$page.num}
                        </a>
                    </li>
                    {/foreach}

                    {if $pagination.has_next}
                    <li class="page-item">
                        <a class="page-link" href="?offset={$pagination.next_offset}">
                            {'ms3_customer_orders_next' | lexicon}
                        </a>
                    </li>
                    {/if}
                </ul>
            </nav>
            {/if}

            <div class="text-muted small mt-3">
                {'ms3_customer_orders_total' | lexicon}: {$total}
            </div>
            {else}
            <div class="alert alert-info" role="alert">
                {'ms3_customer_orders_empty' | lexicon}
            </div>
            {/if}
        </div>
    </div>
</div>
{/block}

Order row chunk

fenom
{* tpl.msCustomer.order.row *}
<tr>
    <td>
        <a href="{'ms3_customer_orders_page_id' | option | url}?order={$uuid}"
           class="fw-semibold text-decoration-none">
{$num}
        </a>
    </td>
    <td class="text-nowrap">
        {$createdon_formatted}
    </td>
    <td>
        <span class="badge" style="background-color: {$status_color};">
            {$status_name}
        </span>
    </td>
    <td class="text-end text-nowrap fw-bold">
        {$cost_formatted} {'ms3_frontend_currency' | lexicon}
    </td>
    <td class="text-end">
        <div class="d-flex gap-2 justify-content-end" role="group">
            <a href="{'ms3_customer_orders_page_id' | option | url}?order={$uuid}"
               class="btn btn-sm btn-outline-primary"
               title="{'ms3_customer_order_view' | lexicon}">
                {'ms3_customer_order_view' | lexicon}
            </a>
            {if $can_cancel}
            <button type="button"
                    class="btn btn-sm btn-outline-danger ms3-order-cancel"
                    data-order-id="{$id}"
                    data-confirm="{'ms3_customer_order_cancel_confirm' | lexicon}"
                    title="{'ms3_customer_order_cancel' | lexicon}">
                {'ms3_customer_order_cancel' | lexicon}
            </button>
            {/if}
        </div>
    </td>
</tr>

Order details placeholders

In tpl.msCustomer.order.details

PlaceholderTypeDescription
{$order}arrayOrder data
{$order.id}intOrder ID
{$order.num}stringOrder number
{$order.status_name}stringStatus name
{$order.status_color}stringStatus color
{$order.createdon_formatted}stringCreated date
{$order.comment}stringOrder comment
{$order.can_cancel}boolWhether the order can be canceled
{$products}arrayOrder products
{$delivery}arrayDelivery method
{$payment}arrayPayment method
{$address}arrayDelivery address
{$total}arrayOrder totals
{$customer}arrayCustomer data

Product in order ({$products})

NameDescription
{$product.product_id}Product ID
{$product.pagetitle}Name
{$product.article}SKU
{$product.count}Quantity
{$product.price}Price (formatted)
{$product.old_price}Old price
{$product.cost}Total (formatted)
{$product.weight}Weight
{$product.options}Product options (array)

Totals ({$total})

NameDescription
{$total.cost}Total to pay
{$total.cart_cost}Products cost
{$total.delivery_cost}Delivery cost
{$total.weight}Total weight

Order details chunk

fenom
{* tpl.msCustomer.order.details *}
<div class="ms3-customer-order-details" data-api-url="{$api_url|default:''}">
    {* Back navigation *}
    <div class="mb-3">
        <a href="{'ms3_customer_orders_page_id' | option | url}"
           class="btn btn-sm btn-outline-secondary">
{'ms3_customer_orders_back' | lexicon}
        </a>
    </div>

    {* Order info *}
    <div class="card shadow-sm mb-4">
        <div class="card-header bg-primary text-white">
            <div class="d-flex justify-content-between align-items-center flex-wrap gap-2">
                <h5 class="mb-0">
                    {'ms3_customer_order_title' | lexicon}{$order.num}
                </h5>
                <div class="d-flex align-items-center gap-2">
                    {if $order.can_cancel}
                    <button type="button"
                            class="btn btn-sm btn-light ms3-order-cancel"
                            data-order-id="{$order.id}"
                            data-confirm="{'ms3_customer_order_cancel_confirm' | lexicon}">
                        {'ms3_customer_order_cancel' | lexicon}
                    </button>
                    {/if}
                    <span class="badge bg-light text-dark"
                          style="color: {$order.status_color} !important;">
                        {$order.status_name}
                    </span>
                </div>
            </div>
        </div>
        <div class="card-body">
            <p class="text-muted mb-2">
                <small>
                    {'ms3_customer_order_created' | lexicon}: {$order.createdon_formatted}
                </small>
            </p>

            {* Products table *}
            <div class="table-responsive">
                <table class="table table-hover align-middle">
                    <thead class="table-light">
                        <tr>
                            <th>{'ms3_cart_title' | lexicon}</th>
                            <th class="text-center col-count">{'ms3_cart_count' | lexicon}</th>
                            <th class="text-end col-price">{'ms3_cart_price' | lexicon}</th>
                            <th class="text-end col-cost">{'ms3_cart_cost' | lexicon}</th>
                        </tr>
                    </thead>
                    <tbody>
                        {foreach $products as $product}
                        <tr>
                            <td>
                                <div class="fw-semibold">{$product.pagetitle}</div>
                                {if $product.article}
                                <div class="small text-muted">
                                    {'ms3_frontend_article' | lexicon}: {$product.article}
                                </div>
                                {/if}
                                {if $product.options && count($product.options) > 0}
                                <div class="small text-muted mt-1">
                                    {foreach $product.options as $option => $value}
                                    {$option}: {$value}{if !$value@last}; {/if}
                                    {/foreach}
                                </div>
                                {/if}
                            </td>
                            <td class="text-center text-nowrap">
                                <span class="badge bg-secondary">
                                    {$product.count} {'ms3_frontend_count_unit' | lexicon}
                                </span>
                            </td>
                            <td class="text-end text-nowrap">
                                {if $product.old_price && $product.old_price > $product.price}
                                <div class="text-decoration-line-through text-muted small">
                                    {$product.old_price}
                                </div>
                                {/if}
                                <div class="fw-semibold">{$product.price}</div>
                                <small class="text-muted">{'ms3_frontend_currency' | lexicon}</small>
                            </td>
                            <td class="text-end text-nowrap fw-bold">
                                {$product.cost} {'ms3_frontend_currency' | lexicon}
                            </td>
                        </tr>
                        {/foreach}
                    </tbody>
                    <tfoot class="table-light">
                        <tr>
                            <td colspan="3" class="text-end fw-bold">
                                {'ms3_frontend_cart_total' | lexicon}:
                            </td>
                            <td class="text-end fw-bold">
                                {$total.cart_cost} {'ms3_frontend_currency' | lexicon}
                            </td>
                        </tr>
                        {if $total.delivery_cost}
                        <tr>
                            <td colspan="3" class="text-end">
                                {'ms3_frontend_delivery' | lexicon}:
                            </td>
                            <td class="text-end">
                                {$total.delivery_cost} {'ms3_frontend_currency' | lexicon}
                            </td>
                        </tr>
                        {/if}
                        <tr class="fw-bold">
                            <td colspan="3" class="text-end fs-5">
                                {'ms3_frontend_total' | lexicon}:
                            </td>
                            <td class="text-end fs-5">
                                {$total.cost} {'ms3_frontend_currency' | lexicon}
                            </td>
                        </tr>
                    </tfoot>
                </table>
            </div>
        </div>
    </div>

    {* Delivery address *}
    {if $address && count($address) > 0}
    <div class="card shadow-sm mb-4">
        <div class="card-header bg-light">
            <h6 class="mb-0">{'ms3_frontend_delivery_address' | lexicon}</h6>
        </div>
        <div class="card-body">
            <p class="mb-0">
                {if $address.index}{$address.index}, {/if}
                {$address.city}
                {if $address.region}, {$address.region}{/if}
                {if $address.country}, {$address.country}{/if}
                <br>
                {$address.street}
                {if $address.building}, bld. {$address.building}{/if}
                {if $address.entrance}, ent. {$address.entrance}{/if}
                {if $address.floor}, fl. {$address.floor}{/if}
                {if $address.room}, apt. {$address.room}{/if}
                {if $address.metro}
                <br>
                <small class="text-muted">{$address.metro}</small>
                {/if}
                {if $address.text_address}
                <br>
                <small class="fst-italic text-muted">{$address.text_address}</small>
                {/if}
            </p>
        </div>
    </div>
    {/if}

    {* Delivery and payment *}
    <div class="row">
        {if $delivery && count($delivery) > 0}
        <div class="col-md-6">
            <div class="card shadow-sm mb-4">
                <div class="card-header bg-light">
                    <h6 class="mb-0">{'ms3_frontend_delivery_method' | lexicon}</h6>
                </div>
                <div class="card-body">
                    <p class="mb-0">{$delivery.name | lexicon}</p>
                    {if $delivery.description}
                    <small class="text-muted">{$delivery.description}</small>
                    {/if}
                </div>
            </div>
        </div>
        {/if}

        {if $payment && count($payment) > 0}
        <div class="col-md-6">
            <div class="card shadow-sm mb-4">
                <div class="card-header bg-light">
                    <h6 class="mb-0">{'ms3_frontend_payment_method' | lexicon}</h6>
                </div>
                <div class="card-body">
                    <p class="mb-0">{$payment.name | lexicon}</p>
                    {if $payment.description}
                    <small class="text-muted">{$payment.description}</small>
                    {/if}
                </div>
            </div>
        </div>
        {/if}
    </div>

    {* Comment *}
    {if $order.comment}
    <div class="card shadow-sm mb-4">
        <div class="card-header bg-light">
            <h6 class="mb-0">{'ms3_frontend_comment' | lexicon}</h6>
        </div>
        <div class="card-body">
            <p class="mb-0">{$order.comment}</p>
        </div>
    </div>
    {/if}
</div>

Pagination structure

php
[
    'total' => 50,           // Total orders
    'total_pages' => 3,      // Total pages
    'current_page' => 1,     // Current page
    'limit' => 20,           // Per page
    'offset' => 0,           // Offset
    'pages' => [             // Page list
        ['num' => 1, 'offset' => 0, 'active' => true],
        ['num' => 2, 'offset' => 20, 'active' => false],
        ['num' => 3, 'offset' => 40, 'active' => false],
    ],
    'has_prev' => false,     // Has previous
    'has_next' => true,      // Has next
    'prev_offset' => 0,      // Previous offset
    'next_offset' => 20,     // Next offset
]

Status structure

php
[
    ['id' => 2, 'name' => 'Paid', 'color' => '008000', 'selected' => false],
    ['id' => 3, 'name' => 'Shipped', 'color' => '0000FF', 'selected' => true],
    ['id' => 4, 'name' => 'Delivered', 'color' => '28a745', 'selected' => false],
]

Status color

Color is stored in the DB without #. In CSS add #:

fenom
style="background-color: #{$status_color};"

System settings

SettingDescription
ms3_customer_orders_page_idOrders page ID
ms3_customer_cancel_allowed_statusesStatus IDs for which cancellation is allowed (comma-separated)
ms3_status_canceledCanceled order status ID

CSS classes

ClassElement
.ms3-customer-ordersOrder list container
.ms3-customer-order-detailsOrder details container
.ms3-order-tableOrders table
.ms3-order-cancelCancel order button