Skip to content

Order History

Meloncart does not require a dedicated order history component. Instead, customer orders are accessed through the user.orders relationship that Meloncart adds to the User model. Combined with the Session component for authentication, you can build complete order list and order detail pages using only Twig.

How It Works

When the Meloncart plugin is installed, it extends the RainLab.User model with a hasMany relationship to the Order model:

php
$user->hasMany['orders'] = [
    \Meloncart\Shop\Models\Order::class,
    'order' => 'id desc'
];

This means any page that has access to the authenticated user object (via the Session component) can access the customer's orders through user.orders.

Order Model Properties

Core Properties

PropertyTypeDescription
idintegerOrder ID
baseidstringURL-safe base identifier
orderNumberstringDisplay-formatted order number
ordered_atCarbonWhen the order was placed
status_updated_atCarbonWhen the status was last changed
user_notesstringCustomer notes submitted during checkout

Financial Properties

All prices are integers in cents.

PropertyTypeDescription
totalintegerGrand total including tax and shipping
subtotalintegerSubtotal before tax and shipping
discountintegerDiscount amount
total_taxintegerTotal tax amount
shipping_quoteintegerShipping cost
shipping_taxintegerTax on shipping

Status

PropertyTypeDescription
statusOrderStatusCurrent order status
status.namestringStatus display name (e.g., "Paid", "Shipped")
status.codestringStatus code (e.g., new, paid, shipped)
status.color_backgroundstringBackground color hex code for badges
status.color_foregroundstringText color hex code for badges

Payment

PropertyTypeDescription
is_payment_processedbooleanWhether payment has been confirmed
payment_processed_atCarbon|nullWhen payment was confirmed
payment_methodPaymentMethodPayment method used
invoiceInvoice|nullAssociated invoice record

Shipping Address

PropertyTypeDescription
shipping_first_namestringFirst name
shipping_last_namestringLast name
shipping_companystringCompany name
shipping_phonestringPhone number
shipping_address_line1stringStreet address line 1
shipping_address_line2stringStreet address line 2
shipping_citystringCity
shipping_zipstringPostal code
shipping_stateStateState/province relationship
shipping_countryCountryCountry relationship
shipping_methodShippingMethodShipping method selected

Billing Address

PropertyTypeDescription
billing_first_namestringFirst name
billing_last_namestringLast name
billing_companystringCompany name
billing_emailstringEmail address
billing_phonestringPhone number
billing_address_line1stringStreet address line 1
billing_address_line2stringStreet address line 2
billing_citystringCity
billing_zipstringPostal code
billing_stateStateState/province relationship
billing_countryCountryCountry relationship

Relationships

PropertyTypeDescription
itemsCollectionOrder line items
status_logCollectionStatus change history
tracking_codesCollectionShipment tracking codes
notesCollectionAdmin notes

Methods

MethodReturnDescription
isPaymentProcessed()booleanWhether payment is confirmed
getIsShipped()booleanWhether the order has tracking codes
getLatestTrackingCode()OrderTrackingCode|nullMost recent tracking code
getCustomerVisibleNotes()CollectionNotes visible to the customer

OrderItem Properties

Each item in order.items is an OrderItem with these properties:

PropertyTypeDescription
productProductThe product model
variantProductVariant|nullSelected variant, if applicable
quantityintegerNumber of units ordered
priceintegerUnit price
unit_priceintegerUnit price after discounts
unit_line_priceintegerunit_price × quantity
discountintegerDiscount amount
totalintegerLine total with tax
options_dataarraySelected product options
extras_dataarraySelected extras

OrderItem Methods

MethodReturnDescription
outputProductName()stringFormatted name with options and extras

OrderTrackingCode Properties

Each tracking code in order.tracking_codes has:

PropertyTypeDescription
carrierstringCarrier code (fedex, ups, usps, dhl, other)
tracking_numberstringTracking number
shipped_atCarbonDate shipped
tracking_urlstring|nullAuto-generated tracking URL for the carrier

Complete Examples

Order List Page

twig
{# pages/account/orders.htm #}
##
url = "/account/orders"
layout = "account"
title = "Orders"

[session]
security = "user"
redirect = "account/login"
==
<div class="page-account">
    <h2 class="mb-4">My Orders</h2>

    {% set orders = user.orders %}
    {% do orders.load('status') %}
    {% if orders is not empty %}
        <div class="table-responsive border-0">
            <table class="table mb-0 text-nowrap table-centered">
                <thead>
                    <tr>
                        <th>Order</th>
                        <th>Date</th>
                        <th>Status</th>
                        <th>Total</th>
                        <th></th>
                    </tr>
                </thead>
                <tbody>
                    {% for order in orders %}
                        {% set url = 'account/order'|page({ id: order.id }) %}
                        <tr>
                            <td class="align-middle border-top-0">
                                <a href="{{ url }}">#{{ order.orderNumber }}</a>
                            </td>
                            <td class="align-middle border-top-0">
                                {{ order.ordered_at|date('j M Y') }}
                            </td>
                            <td class="align-middle border-top-0">
                                <span class="badge"
                                    style="background:{{ order.status.color_background }}">
                                    {{ order.status.name }}
                                </span>
                            </td>
                            <td class="align-middle border-top-0">
                                {{ order.total|currency }}
                            </td>
                            <td class="text-muted align-middle border-top-0">
                                <a href="{{ url }}" class="text-inherit">
                                    <i class="bi bi-eye"></i>
                                </a>
                            </td>
                        </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
    {% else %}
        <div class="text-center">
            <p>No orders yet</p>
            <a href="{{ 'shop/index'|page }}" class="btn btn-outline-primary mt-2">
                Continue shopping
            </a>
        </div>
    {% endif %}
</div>

TIP

Use {% do orders.load('status') %} to eager-load the status relationship and avoid N+1 queries when iterating over orders.

Order Detail Page

twig
{# pages/account/order.htm #}
##
url = "/account/order/:id"
layout = "account"
title = "Order"

[session]
security = "user"
redirect = "account/login"
==
{% set order = user.orders().find(this.param.id) %}
{% if not order %}
    {% do abort(404) %}
{% endif %}

<div class="mb-3">
    <a href="{{ 'account/orders'|page }}" class="btn btn-link ps-0 text-reset text-decoration-none">
        <i class="bi bi-chevron-left"></i> Back to Orders
    </a>
</div>

<div class="page-account">
    <div class="row align-items-center mb-3">
        <div class="col-4">
            <h2 class="m-0">Order #{{ order.orderNumber }}</h2>
        </div>
        <div class="col-8 text-end">
            <p class="text-muted m-0">
                {{ order.ordered_at.format('F n, Y') }}
                at {{ order.ordered_at.format('h:ia') }}
            </p>
        </div>
    </div>

    <div class="py-3">
        {% partial 'shop/order-items' items=order.items %}
    </div>

    {% if not order.is_payment_processed %}
        <div class="card mb-2">
            <div class="card-body py-2">
                <div class="d-flex align-items-center">
                    <div class="pe-4">
                        <i class="bi bi-bag-x text-danger" style="font-size:32px"></i>
                    </div>
                    <div>
                        <h5 class="cart-title m-0">This Order is Unpaid</h5>
                    </div>
                    <div class="ms-auto">
                        {% if order.invoice %}
                            <a
                                href="{{ 'shop/payment'|page({ hash: order.invoice.hash }) }}"
                                class="btn btn-dark">
                                Pay Now
                            </a>
                        {% endif %}
                    </div>
                </div>
            </div>
        </div>
    {% endif %}

    <div class="card mb-2">
        <div class="card-body pb-0">
            <div class="row">
                <div class="col-6">
                    <h5 class="fw-bold">Shipping Address</h5>
                    <p>
                        {{ order.shipping_first_name }} {{ order.shipping_last_name }}<br />
                        {{ order.shipping_address_line1 }}<br />
                        {% if order.shipping_address_line2 %}
                            {{ order.shipping_address_line2 }}<br />
                        {% endif %}
                        {{ order.shipping_city }}
                        {{ order.shipping_state.code }}
                        {{ order.shipping_zip }}<br />
                        {{ order.shipping_country.name }}<br />
                        {{ order.shipping_phone }}
                    </p>
                </div>
                <div class="col-6">
                    <h5 class="fw-bold">Billing Address</h5>
                    <p>
                        {{ order.billing_first_name }} {{ order.billing_last_name }}<br />
                        {{ order.billing_address_line1 }}<br />
                        {% if order.billing_address_line2 %}
                            {{ order.billing_address_line2 }}<br />
                        {% endif %}
                        {{ order.billing_city }}
                        {{ order.billing_state.code }}
                        {{ order.billing_zip }}<br />
                        {{ order.billing_country.name }}<br />
                        {{ order.billing_phone }}
                    </p>
                </div>
            </div>
        </div>
    </div>
</div>

WARNING

The order detail page uses user.orders().find(this.param.id) to look up the order. This ensures the customer can only access their own orders — not orders belonging to other users.

Order Items Partial

A reusable partial for displaying order line items with product images and totals:

twig
{# partials/shop/order-items.htm #}
<ul class="list-group list-group-flush">
    <li class="list-group-item">
        <div class="row align-items-center">
            <div class="col-7"><span>Product</span></div>
            <div class="col-3 text-center"><span>Quantity</span></div>
            <div class="col-2 text-end"><span>Total</span></div>
        </div>
    </li>
    {% for item in items %}
        {% set product = item.product %}
        <li class="list-group-item py-3 border-top">
            <div class="row align-items-center py-3">
                <div class="col-3 col-md-2">
                    <a href="{{ product.pageUrl('shop/product') }}">
                        {% if product.images is not empty %}
                            <img class="img-fluid"
                                src="{{ product.images.first|resize(0, 100, { mode: 'auto' }) }}"
                                alt="{{ product.name }}" />
                        {% endif %}
                    </a>
                </div>
                <div class="col-4 col-md-5">
                    <a href="{{ product.pageUrl('shop/product') }}" class="text-inherit">
                        <h5 class="mb-2">{{ product.name }}</h5>
                    </a>
                    <span class="text-success small">
                        {{ item.unit_price|currency }}
                    </span>
                    {% if item.discount %}
                        <span class="text-decoration-line-through text-muted small">
                            {{ item.price|currency }}
                        </span>
                    {% endif %}
                </div>
                <div class="col-3 text-center">
                    {{ item.quantity }}
                </div>
                <div class="col-2 text-end">
                    <span>{{ item.unit_line_price|currency }}</span>
                </div>
            </div>
        </li>
    {% endfor %}
    <li class="list-group-item py-3 border-top">
        <div class="row align-items-center">
            <div class="col-8 text-end">
                <span>Total</span>
            </div>
            <div class="col-4 text-end">
                <span class="fw-bold fs-5">
                    {{ order.total|currency({ format: 'long' }) }}
                </span>
            </div>
        </div>
    </li>
</ul>

Tracking Information

You can display shipment tracking on the order detail page:

twig
{% if order.is_shipped %}
    <div class="card mb-2">
        <div class="card-body">
            <h5 class="fw-bold">Shipment Tracking</h5>
            {% for code in order.tracking_codes %}
                <div class="d-flex justify-content-between align-items-center mb-2">
                    <div>
                        <strong>{{ code.carrier|upper }}</strong>:
                        {{ code.tracking_number }}
                        <br />
                        <small class="text-muted">
                            Shipped {{ code.shipped_at|date('j M Y') }}
                        </small>
                    </div>
                    {% if code.tracking_url %}
                        <a href="{{ code.tracking_url }}" target="_blank"
                            class="btn btn-outline-primary btn-sm">
                            Track Package
                        </a>
                    {% endif %}
                </div>
            {% endfor %}
        </div>
    </div>
{% endif %}

Status History

Display the order's status change history:

twig
{% set statusLog = order.status_log %}
{% if statusLog is not empty %}
    <div class="card mb-2">
        <div class="card-body">
            <h5 class="fw-bold">Order History</h5>
            <ul class="list-group list-group-flush">
                {% for log in statusLog %}
                    <li class="list-group-item px-0">
                        <div class="d-flex justify-content-between">
                            <span class="badge"
                                style="background:{{ log.status.color_background }}">
                                {{ log.status.name }}
                            </span>
                            <small class="text-muted">
                                {{ log.created_at|date('j M Y, h:ia') }}
                            </small>
                        </div>
                        {% if log.comment %}
                            <p class="mb-0 mt-1 small">{{ log.comment }}</p>
                        {% endif %}
                    </li>
                {% endfor %}
            </ul>
        </div>
    </div>
{% endif %}

If an order is unpaid and has an associated invoice, you can link the customer to the payment page:

twig
{% if not order.is_payment_processed and order.invoice %}
    <a href="{{ 'shop/payment'|page({ hash: order.invoice.hash }) }}"
        class="btn btn-dark">
        Pay Now
    </a>
{% endif %}

This redirects to the Checkout payment page where the customer can complete payment using the invoice hash.