s_skaidocsTài liệu
THEME OBJECT · GUEST SESSION

cart

Giỏ hiện tại của trình duyệt, do backend xác định qua cookie session. Guest không cần đăng nhập; developer không tạo UUID, không gửi cart_id và không cần gọi cart/get trước khi thêm hàng.

Website POST routes · Hướng dẫn dựng website

Thuộc tính

FieldGiá trị
cart.itemsDanh sách object dòng sản phẩm, rỗng là [].
cart.item_countTổng số lượng các dòng.
cart.line_countSố dòng/biến thể trong giỏ.
cart.emptyBoolean giỏ không có dòng nào.
cart.total_priceTạm tính tiền hàng theo giá hiện tại, minor units; null nếu không thể xác định đầy đủ giá. Chưa gồm thuế/phí giao hàng/giảm giá checkout.
cart.currencyMã tiền tệ; có thể rỗng nếu chưa có session giỏ.

Mỗi item có variant_id, name/title (tên sản phẩm), variant_title, quantity, available, price (object amount/currency hoặc null), line_price (minor units hoặc null). Không có token, session ID, account ID hoặc cart ID trong object.

Render giỏ

{% if cart.empty %}
  <p>Giỏ hàng trống</p>
{% else %}
  {% for item in cart.items %}
    <p>{{ item.name }} × {{ item.quantity }}</p>
    {% if not item.available %}<p>Sản phẩm hiện không khả dụng.</p>{% endif %}
  {% endfor %}
  <p>{{ cart.item_count }} sản phẩm — {{ cart.total_price }} {{ cart.currency }}</p>
{% endif %}

Thêm hàng không cần JavaScript

<form method="post" action="/_post/cart/add">
  <input type="hidden" name="_token" value="{{ csrf_token }}">
  <input type="hidden" name="variant_id" value="{{ variant.id }}">
  <input type="number" name="quantity" min="1" max="999" value="1">
  <button>Thêm vào giỏ</button>
</form>

variant là biến thể đang hiển thị từ products. Add cộng số lượng; /_post/cart/update đặt quantity tuyệt đối; /_post/cart/remove chỉ nhận variant_id. Backend kiểm tra sản phẩm, kênh bán, giá và giới hạn; không tin giá client. Form thành công chuyển 303 về /cart.

AJAX

const response = await fetch("/_post/cart/add", {
  method: "POST", credentials: "same-origin",
  headers: {"Accept":"application/json", "Content-Type":"application/json",
    "X-CSRF-Token":document.querySelector('[name="_token"]').value},
  body: JSON.stringify({variant_id: variantID, quantity: 1})
});
const result = await response.json();
if (!response.ok) throw new Error(result.error?.message || "Không thể thêm hàng");
// result.Items, result.ItemCount, result.Subtotal, result.Currency
// Object theme dùng snake_case; DTO AJAX hiện dùng PascalCase.

Không tự retry add sau timeout vì có thể cộng hai lần; kiểm tra lại /_post/cart/get. Các POST vẫn dùng CSRF chung. Đọc cart trong theme không tạo bản ghi giỏ trong DB, không làm tăng số lượng và không giữ tồn kho.

Vòng đời và giới hạn hiện tại

  • Giỏ lưu Redis phía server, cookie chỉ giữ mã ngẫu nhiên HttpOnly, host-only, Secure trên HTTPS. Thời hạn cố định 30 ngày từ khi tạo giỏ, đọc/cập nhật không gia hạn.
  • Login/logout không đổi giỏ trình duyệt. Trình duyệt khác hoặc website/store/channel khác không dùng chung giỏ.
  • Restart backend không mất giỏ. Nhiều node dùng chung Redis, không cần sticky routing. Redis cần bật persistence AOF/RDB và tránh eviction để giữ giỏ khi chính Redis khởi động lại. Tối đa 100 dòng/giỏ, 999 đơn vị/biến thể. Redis lỗi sẽ báo lỗi, không fallback memory.
  • Giá được đọc lại từ DB; dòng ngừng bán vẫn hiển thị là không khả dụng và có thể xóa. Checkout tính lại giá và tồn kho, không tin total_price từ client.
  • Checkout nhận snapshot session, không ghi bảng carts/cart_items. Đơn hàng, thanh toán COD và idempotency vẫn lưu DB; đặt thành công làm trống giỏ. Giữ cùng idempotency_key/payload khi retry.
  • Guest nhận receipt; lịch sử my-orders vẫn cần account. Không tự gắn đơn guest vào account theo email/số điện thoại khi đăng nhập.
  • Không hỗ trợ properties, coupon hoặc đồng bộ giỏ giữa thiết bị trong phiên bản này. Giỏ DB cũ không được nhập vào session và không bị xóa.