s_skaidocsTài liệu
LINGO · ĐƠN HÀNG

Object orders

Render danh sách, chi tiết và tracking trực tiếp trong theme. get/find/first/paginate/count/exists chỉ truy vấn đơn của tài khoản đã xác thực. Riêng track tra cứu công khai theo mã đơn, không cần đăng nhập. Tất cả đều giới hạn theo website hiện tại, không phải toàn store.

get, count, exists

{% set orders = orders.get() %}
{% for order in orders %}
  <a href="/my-orders/{{ order.id }}">{{ order.number }}</a>
  {{ order.grand_total_amount }} {{ order.currency }}
{% endfor %}

{% set total = orders.count(status="open") %}
{% set has_orders = orders.exists() %}

Các ví dụ là độc lập: đặt biến orders = orders.get() sẽ che object cho phần template sau đó; nếu cần gọi tiếp count/exists, dùng tên rows hoặc listing cho danh sách. get mặc định 8 dòng, trả mảng summary, không có trả []. count trả số nguyên, exists trả boolean.

find và first — chi tiết

{% set order = orders.find(id=id) %}
{% if not order %}{% abort 404 %}{% endif %}
<h1>{{ order.number }}</h1>
{% for line in order.items %}
  {{ line.name }} · {{ line.quantity }} · {{ line.total_amount }} {{ order.currency }}
{% endfor %}

{% set by_number = orders.find(number="SK-000002") %}
{% set latest = orders.first() %}

Trong pages/my-orders/[id].html, id là biến route. find cần đúng một id (UUID) hoặc number, không nhận slug. Không tồn tại hoặc không thuộc account/channel trả null. Số đơn chỉ là bộ lọc, không phải mã bí mật cấp quyền. Nếu nhiều kết quả trùng number báo lỗi, không chọn ngẫu nhiên. first trả chi tiết đơn đầu tiên hoặc null.

paginate

{% set listing = orders.paginate(limit=20, page_key="page") %}
{% if not listing.authenticated %}
  <a href="/login?next=%2Fmy-orders">Đăng nhập để xem lịch sử tài khoản</a>
{% endif %}
{% for order in listing.items %}
  <a href="/my-orders/{{ order.id }}">{{ order.number }}</a>
{% endfor %}
{% if listing.pagination.has_previous %}
  <a href="{{ listing.pagination.previous_url }}">Trước</a>
{% endif %}
{% if listing.pagination.has_next %}
  <a href="{{ listing.pagination.next_url }}">Sau</a>
{% endif %}

SQL COUNT/LIMIT/OFFSET theo ownership, không tải toàn bộ store rồi lọc. items và records là cùng danh sách summary. pagination: page, per_page, total, total_pages, has_previous, has_next, previous_url/next_url khi có; link giữ query khác. Trang vượt cuối được đưa về trang cuối; rỗng page=1/total_pages=0.

Tham số get/find/first/paginate/count/exists

KeyQuy tắc
limit1–100, mặc định 8, chịu giới hạn runtime; first lấy 1.
id / numberLọc chính xác; find cần đúng một. number tối đa 100 bytes.
statusLọc order_status, không phải payment_status hay fulfillment_status; tối đa 100 bytes.
ordercreated_at desc mặc định; created_at asc/desc, id asc/desc; thứ tự phụ id.
page_keyChỉ paginate, mặc định page. Các bộ lọc khác phải truyền tường minh, không tự lấy từ URL.

Không nhận account_id/customer_id/workspace_id/store_id/channel_id/token hoặc SQL tùy ý. Không có q/term/type/slug. Tên đúng là paginate, không phải pageinate.

Kiểu dữ liệu

get/paginate trả summary: id, number, currency, order_status, payment_status, fulfillment_status, grand_total_amount, created_at. find/first/track trả thêm subtotal_amount, discount_amount, shipping_amount, tax_amount, shipping_address, items, payments, shipments.

  • shipping_address: recipient_name, phone, address_line, ward, district, city, province, postal_code, country_code.
  • items luôn là mảng: name, variant_name, sku, quantity, unit_amount, discount_amount, tax_amount, total_amount.
  • payments luôn là mảng: provider, status, currency, amount, created_at. Không có secret/payment provider payload.
  • shipments luôn là mảng: status, carrier, service, tracking_number, tracking_url, created_at, shipped_at, delivered_at. Hai timestamp cuối có thể null; URL chỉ http/https không chứa thông tin đăng nhập.
  • amount là integer minor unit theo currency. Timestamp là time.Time phía template; không tự tạo mốc thời gian giao hàng.

Tracking không cần API riêng

{% set order = orders.find(id=id) %}
{% if not order %}{% abort 404 %}{% endif %}
{% for shipment in order.shipments %}
  {{ shipment.carrier }} · {{ shipment.tracking_number }} · {{ shipment.status }}
  {% if shipment.shipped_at %}Đã gửi: {{ shipment.shipped_at }}{% endif %}
  {% if shipment.delivered_at %}Đã giao: {{ shipment.delivered_at }}{% endif %}
  {% if shipment.tracking_url %}
    <a href="{{ shipment.tracking_url }}" target="_blank" rel="noopener noreferrer">Tra cứu vận đơn</a>
  {% endif %}
{% endfor %}
{% if not order.shipments %}Chưa có vận đơn.{% endif %}

Trang pages/my-orders/[id]/tracking.html dùng cùng find và ownership với detail. Tải lại trang để lấy trạng thái mới từ Console. COD pending/unpaid không phải đã trả tiền.

orders.track() — tra cứu công khai theo mã đơn

Không cần đăng nhập, không lọc theo tài khoản sở hữu đơn. Backend tra cứu chính xác number trong workspace/store/channel được xác định từ website hiện tại. Không cho truyền scope hoặc account_id để tra cứu sang website khác.

{# Truyền mã đơn trực tiếp #}
{% set result = orders.track(number="SK-000002") %}

{# Hoặc đọc query ?number=SK-000002 của request hiện tại #}
{% set result = orders.track() %}

Chỉ nhận tham số number kiểu chuỗi. Nếu không truyền, đọc query number; nếu truyền thì ưu tiên giá trị đã truyền, kể cả chuỗi rỗng. Bỏ khoảng trắng hai đầu; mã rỗng, dài hơn 128 bytes hoặc không tìm thấy trả null. Không nhận id, status, limit, page_key hay token. Trả object chi tiết cùng cấu trúc find, bao gồm thông tin người nhận, sản phẩm, thanh toán và vận chuyển.

Tạo trang pages/tracking.html

Đặt đoạn sau trong block content của layout theme. GET /tracking?number=... chỉ đọc dữ liệu, không cần CSRF token hoặc gọi API JavaScript.

{% set result = orders.track() %}
<form method="get" action="/tracking">
  <label for="number">Mã đơn hàng</label>
  <input id="number" name="number" required maxlength="128">
  <button type="submit">Tra cứu</button>
</form>
{% if result %}
  <h1>Đơn {{ result.number }}</h1>
  <p>{{ result.order_status }} · {{ result.payment_status }} · {{ result.fulfillment_status }}</p>
  <p>{{ result.shipping_address.recipient_name }} · {{ result.shipping_address.phone }}</p>
  <p>{{ result.shipping_address.address_line }}</p>
  {% for line in result.items %}
    <p>{{ line.name }} × {{ line.quantity }} — {{ line.total_amount }} {{ result.currency }}</p>
  {% endfor %}
  <p>Tổng: {{ result.grand_total_amount }} {{ result.currency }}</p>
  {% for shipment in result.shipments %}
    <p>{{ shipment.carrier }} · {{ shipment.tracking_number }} · {{ shipment.status }}</p>
  {% endfor %}
  {% if not result.shipments %}<p>Chưa có vận đơn.</p>{% endif %}
{% else %}
  <p>Nhập mã đơn chính xác. Nếu đã nhập nhưng không có kết quả, đơn không tồn tại trên website này.</p>
{% endif %}

Lưu ý bảo mật: track không xác minh quyền sở hữu, không tự che tên, điện thoại hoặc địa chỉ. Ai biết hoặc đoán được mã đơn đều có thể xem dữ liệu khi trang công khai gọi hàm này. Developer phải triển khai bảo vệ phía server trước khi gọi track nếu cần hạn chế truy cập; ẩn nút, kiểm tra JavaScript hoặc dùng mã đơn như mật khẩu không phải lớp bảo vệ. Không dùng track thay find trong trang đơn hàng riêng tư.

Khi có HTTP context, track đặt Cache-Control: private, no-store, max-age=0; Referrer-Policy: no-referrer; X-Robots-Tag: noindex, nofollow. Các header này không thay thế xác thực, không ngăn đoán mã và không xóa mã đơn khỏi lịch sử trình duyệt hoặc access log.

Account, guest và cache

Các hàm riêng tư get/find/first/paginate/count/exists lấy phiên từ cookie external account do server xác thực; không dùng cookie nhân viên Console hoặc token truyền qua template/query. Account, membership, expiry/revocation và quyền commerce.customer.self được kiểm tra; scope workspace/store/channel luôn ràng buộc website. Cookie chỉ dùng qua HTTPS trừ policy HTTP test tường minh. track không sử dụng cơ chế xác thực account này.

Không có phiên hợp lệ: get=[], find/first=null, count=0, exists=false, paginate.authenticated=false. Không tự nhận đơn guest qua email hoặc UUID. Khách vẫn mua hàng không cần tài khoản và có thể dùng trang gọi orders.track() để tra cứu theo mã đơn. Cơ chế guest lookup token chưa được triển khai; không có orders.find(token=...).

Response render phải private/no-store, không đưa HTML đơn hàng vào CDN/cache chung. Object đặt header riêng tư khi có HTTP context. Theme xóa DOM đơn ở pagehide/logout; pageshow từ bfcache phải tải lại để xác thực server. Không dùng raw/safe cho dữ liệu khách nhập.

Chuyển từ API cũ

Đã gỡ GET /_account/orders, GET /_account/orders/:id và GET /_account/orders/:id/tracking. Thay fetch bằng orders.get/paginate/find trong template; bỏ JavaScript tải JSON đơn. Các thao tác ghi dùng namespace /_post/; xem Website POST routes. Response checkout vẫn trả receipt.ID để điều hướng tới trang chi tiết.

Xem API account/cart/checkout, hướng dẫn website và products.