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.