Công thức — Giỏ hàng
Điều quan trọng nhất: giỏ hàng làm việc theo phiên bản
xStore.cart.addItem nhận variant_id, không phải product.id và cũng
không phải product.slug. Một sản phẩm có nhiều phiên bản (màu, dung lượng…),
và giỏ hàng phải biết chính xác khách mua phiên bản nào.
Trang chi tiết sản phẩm có sẵn biến variants để lấy giá trị này.
Thêm vào giỏ từ trang chi tiết sản phẩm
<select id="variant-select">
{% for variant in variants %}
<option value="{{ variant.id }}" {% unless variant.stock_quantity > 0 %}disabled{% endunless %}>
{{ variant.title }} — {{ variant.price | money }}
</option>
{% endfor %}
</select>
<input type="number" id="quantity" value="1" min="1">
<button type="button" id="add-to-cart">Thêm vào giỏ</button>
<script>
document.getElementById('add-to-cart').addEventListener('click', async (e) => {
const btn = e.currentTarget;
const variantId = document.getElementById('variant-select').value;
const quantity = Number(document.getElementById('quantity').value) || 1;
btn.disabled = true;
try {
await xStore.cart.addItem({ variant_id: variantId, quantity });
alert('Đã thêm vào giỏ.');
} catch (err) {
if (err.status === 401) {
window.location.href = '/dang-nhap?next=' + encodeURIComponent(location.pathname);
} else {
alert(err.message);
}
} finally {
btn.disabled = false;
}
});
</script>
Khoá nút trong lúc chờ (btn.disabled) để khách bấm nhiều lần không tạo ra
nhiều dòng hàng trùng.
Chọn phiên bản theo tuỳ chọn
Khi giao diện cho khách chọn từng tuỳ chọn riêng (màu, size) thay vì một danh
sách phẳng, dùng xStore.product.findVariant để suy ra phiên bản:
<script>
const variants = {{ variants | json }};
function currentVariant() {
const chosen = [...document.querySelectorAll('[data-option]')].map(el => el.value);
return xStore.product.findVariant(variants, chosen);
}
document.getElementById('add-to-cart').addEventListener('click', async () => {
const variant = currentVariant();
if (!variant || !xStore.product.isAvailable(variant)) {
alert('Phiên bản này tạm hết hàng.');
return;
}
await xStore.cart.addItem({ variant_id: variant.id, quantity: 1 });
});
</script>
Nút "thêm nhanh" trên thẻ sản phẩm
Danh sách sản phẩm không có sẵn variants, nên chỉ thêm nhanh được khi sản
phẩm có đúng một phiên bản. Trường hợp còn lại, đưa khách sang trang chi tiết:
{% if product.url %}
<a class="quick-add" href="{{ product.url }}">Xem & chọn</a>
{% endif %}
Cập nhật biểu tượng giỏ
Đừng tự cộng trừ số lượng — hãy nghe sự kiện. Giỏ còn thay đổi ở tab khác, hoặc sau khi gộp giỏ lúc đăng nhập:
<script>
xStore.on('cart:updated', (cart) => {
document.getElementById('cart-badge').textContent = cart.item_count;
});
</script>
Sửa số lượng và xoá dòng
<script>
// Đặt lại số lượng
await xStore.cart.updateItem({ variant_id: variantId, quantity: 3 });
// Xoá khỏi giỏ
await xStore.cart.removeItem(variantId);
</script>
Trong cart.html, mỗi dòng hàng có sẵn item.variant_id:
{% for item in cart.items %}
<button type="button" class="remove" data-variant="{{ item.variant_id }}">Xoá</button>
{% endfor %}
<script>
document.querySelectorAll('.remove').forEach((btn) => {
btn.addEventListener('click', async () => {
await xStore.cart.removeItem(btn.dataset.variant);
window.location.reload();
});
});
</script>
Áp mã giảm giá
<script>
try {
await xStore.cart.applyPromo('SALE10');
} catch (e) {
alert('Mã không hợp lệ hoặc đã hết hạn.');
}
// Gỡ mã đang áp
await xStore.cart.applyPromo(null);
</script>
applyPromo trả về { promo_code } chứ không phải giỏ hàng. Muốn lấy tổng
tiền mới, gọi lại xStore.cart.get() hoặc tải lại trang giỏ hàng.