xStore.shipping
Phí vận chuyển theo gói dịch vụ, danh sách tỉnh/phường, và các điểm nhận hàng.
Phương thức
| Phương thức | Cú pháp | Mô tả | Từ phiên bản |
|---|---|---|---|
rates |
xStore.shipping.rates({ province_code, ward_code, subtotal }, { cartToken }): Promise<CursorPaginatedResponse<PublicTierRateRead>> |
Báo giá vận chuyển theo tier ('instant' | 'same_day' | 'standard' | 'pickup') cho một địa chỉ. LUÔN truyền cartToken: thiếu nó, báo giá bỏ qua bộ máy khuyến mãi và có thể lệch với mức phí thực sự ghi vào đơn khi đặt hàng. Ưu tiên province_code / ward_code (mã chính thức của nhà nước) — chỉ gửi province_name / ward_name khi không có mã, vì so khớp theo tên có thể không tìm ra vùng nào. Một dòng có fee_pending = true nghĩa là amount = "0" nhưng KHÔNG phải miễn phí — cửa hàng báo giá thủ công, đừng hiển thị là "Miễn phí". ⚠️ GIỚI HẠN TẦN SUẤT: 30 lượt/phút mỗi IP khách (cộng mức trần 600/phút mỗi tenant) — đây là điểm đọc DUY NHẤT trên SDK này bị giới hạn tần suất. Bên gọi PHẢI debounce theo mỗi lần đổi tỉnh/phường/tổng tiền, và xử lý 429 riêng với lỗi chung. | 1.0 |
provinces |
xStore.shipping.provinces(): Promise<CursorPaginatedResponse<ProvinceRead>> |
34 tỉnh/thành theo mô hình hai cấp từ 01/07/2025. Dữ liệu tham chiếu có giới hạn — next_cursor luôn null. | 1.0 |
wards |
xStore.shipping.wards(provinceCode): Promise<CursorPaginatedResponse<WardRead>> |
Phường/xã của một tỉnh. provinceCode là BẮT BUỘC — thiếu nó nhận lỗi 422, chứ không phải danh sách rỗng hay danh sách của mọi tỉnh. | 1.0 |
pickupLocations |
xStore.shipping.pickupLocations(): Promise<CursorPaginatedResponse<PickupLocationRead>> |
Cửa hàng khách có thể đến nhận — điểm đến của gói vận chuyển 'pickup'. Bắt buộc chọn một điểm (pickup_location_id) khi checkout.setShipping() dùng tier 'pickup'. | 1.0 |
Ví dụ đầy đủ
<script>
// Báo giá vận chuyển cho một địa chỉ.
//
// ⚠️ LUÔN truyền cartToken. Thiếu nó, báo giá bỏ qua bộ máy khuyến mãi
// và hiện một mức phí khác với mức thực sự ghi vào đơn hàng.
//
// ⚠️ GIỚI HẠN TẦN SUẤT: 30 lượt/phút cho mỗi IP khách (cộng thêm mức
// trần 600/phút cho mỗi tenant). Gọi hàm này ở mỗi lần gõ phím
// tỉnh/phường/tổng tiền sẽ tiêu hết hạn mức rất nhanh — PHẢI debounce
// (chờ khách ngừng thao tác vài trăm ms rồi mới gọi), và xử lý lỗi 429
// riêng biệt với lỗi chung (429 nghĩa là "đang thao tác quá nhanh",
// không phải "không có gói vận chuyển nào").
async function renderShippingOptions(provinceCode, wardCode, subtotal) {
const cartToken = xStore.cart.getToken();
const trang = await xStore.shipping.rates(
{ province_code: provinceCode, ward_code: wardCode, subtotal },
{ cartToken },
);
document.getElementById('ship').innerHTML = trang.items.map((r) => {
// ⚠️ fee_pending = true nghĩa là cửa hàng báo giá THỦ CÔNG:
// amount là "0" nhưng KHÔNG phải miễn phí. Hiển thị "Sẽ báo sau".
const phi = r.fee_pending
? 'Phí sẽ được báo sau'
: (Number(r.amount) === 0 ? 'Miễn phí' : xStore.utils.formatCurrency(Number(r.amount)));
// r.tier là giá trị gửi lại cho checkout.setShipping().
return `<label>
<input type="radio" name="tier" value="${r.tier}">
${r.label} — ${r.eta} — ${phi}
</label>`;
}).join('');
}
// Tỉnh/phường: mã CHÍNH THỨC của nhà nước, không phải mã của hãng vận chuyển.
async function renderProvinceSelect() {
const tinh = await xStore.shipping.provinces();
document.getElementById('province').innerHTML = tinh.items
.map((t) => `<option value="${t.code}">${t.name}</option>`)
.join('');
}
// wards() bắt buộc có province_code — thiếu là lỗi 422, không phải danh
// sách rỗng.
async function renderWardSelect(provinceCode) {
const phuong = await xStore.shipping.wards(provinceCode);
document.getElementById('ward').innerHTML = phuong.items
.map((p) => `<option value="${p.code}">${p.name}</option>`)
.join('');
}
// Điểm nhận hàng cho gói "pickup" — cần khi shipping_tier là 'pickup'.
async function renderPickupLocations() {
const diem = await xStore.shipping.pickupLocations();
document.getElementById('pickup').innerHTML = diem.items
.map((d) => `<option value="${d.id}">${d.title}</option>`)
.join('');
}
</script>
Thư viện gọi tới /api/... trên chính tên miền cửa hàng, nên không cần khai báo địa chỉ máy chủ ở bất kỳ đâu.
Xem thêm Sự kiện để giao diện tự cập nhật sau mỗi thao tác.