Setelah pembeli yakin beli, mereka masih perlu tau gimana barangnya sampai. Pengiriman itu yang paling umum, tapi Shopify juga nyediain ambil di toko (local pickup), dan theme bisa nampilin ketersediaannya langsung di halaman produk.

Merchant bisa nyediain produk buat local pickup di lokasi-lokasi fisik mereka. Theme bisa nampilin apakah varian tertentu bisa diambil di toko mana, jadi pembeli tau tanpa harus masukin ke cart dan lanjut ke checkout buat lihat opsi pengirimannya. Buat toko dengan cabang fisik, ini fitur yang langsung ngaruh ke konversi: “Tersedia di Toko Bandung, siap dalam 2 jam” lebih meyakinkan dari “Stok tersedia”.

Syarat dan Object

Syarat: theme udah punya pemilih varian dari 39 Produk Variants dan High-variant Products, karena fungsi JavaScript pickup availability harus dipanggil waktu varian berganti.

Object yang dipakai, dari 14 Objects Produk dan Koleksi > Object Ambil di Toko:

  1. variant.store_availabilities, array store_availability buat varian itu.
  2. store_availability: available, location, pick_up_enabled, pick_up_time.
  3. location: name, address, latitude, longitude, metafields.

Satu catatan penting soal transfer antar toko: store_availability.available dan pick_up_time udah memperhitungkan store transfer. Varian bisa “tersedia” buat pickup di lokasi yang gak punya stoknya, kalau merchant ngatur transfer yang nyuplai dari lokasi lain. Di kasus itu, pick_up_time udah disesuaikan dengan waktu transfernya. Jadi logika tampilan kamu gak perlu diubah, nilainya udah termasuk ketersediaan berbasis transfer.

Tiga Komponen

Polanya sama dengan predictive search dan rekomendasi produk: section yang dirender lewat API dan disuntik ke wadah kosong.

  1. Section pickup availability: ngerender konten, berisi info tiap lokasi tempat varian sekarang bisa diambil.
  2. Wadah pickup availability: <div> kosong di halaman produk tempat konten section ditaruh.
  3. Fungsi JavaScript: ngerender section ke dalam wadah dan ngupdate-nya waktu varian dipilih.

Contoh di sini cuma ngilustrasiin pertimbangan dasar, implementasi lengkap tergantung theme. Contoh utuh Dawn: sections/pickup-availability.liquid, wadah di sections/main-product.liquid, tombol di snippets/buy-buttons.liquid, assets/pickup-availability.js, dan assets/component-pickup-availability.css.

Section Pickup Availability

Dua bagian utama: ringkasan ketersediaan dan modal ketersediaan. Section ini dirender di dalam wadah oleh JavaScript, dan cuma ngeluarin apa-apa kalau varian sekarang punya minimal satu lokasi dengan pickup aktif.

1. Ringkasan

Loop lokasi dari store_availabilities varian sekarang, saring yang pick_up_enabled true. Kalau ada, tampilin ketersediaan di lokasi pertama plus tombol buat buka modal.

2. Modal

Nampilin judul produk dan varian, dan tiap lokasi tempat varian bisa diambil, dengan ketersediaan dan alamat masing-masing.

3. Kodenya

<div class="pickup-availability-container">
  {%- assign pick_up_availabilities = product_variant.store_availabilities | where: 'pick_up_enabled', true -%}
  {%- if pick_up_availabilities.size > 0 -%}
    <!-- Availability summary -->
    <div class="pickup-availability-information">
      {%- assign closest_location = pick_up_availabilities.first -%}
      {%- if closest_location.available -%}
        {% render 'icon-in-stock' %}
      {%- else -%}
        {% render 'icon-out-of-stock' %}
      {%- endif -%}
      <div class="pickup-availability-information-container">
        {%- if closest_location.available -%}
          <p class="pickup-availability-information__title">
            {{ 'pickup_availability.general.pick_up_available_at_html' | t: location_name: closest_location.location.name }}
          </p>
          <p class="pickup-availability-information__stock pickup-availability-small-text">
            {{ closest_location.pick_up_time }}
          </p>
          <button
            class="pickup-availability-information__button js-modal-open-pickup-availability-modal pickup-availability-small-text"
            data-pickup-availability-modal-open aria-haspopup="dialog"
          >
            {%- if pick_up_availabilities.size == 1 -%}
              {{ 'pickup_availability.general.view_store_info'  | t }}
            {%- else -%}
              {{ 'pickup_availability.general.check_other_stores'  | t }}
            {%- endif -%}
          </button>
        {%- else -%}
          <p class="pickup-availability-information__title">
            {{ 'pickup_availability.general.pick_up_unavailable_at_html' | t: location_name: closest_location.location.name }}
          </p>
          {%- if pick_up_availabilities.size > 1 -%}
            <button class="pickup-availability-information__button js-modal-open-pickup-availability-modal pickup-availability-small-text" data-pickup-availability-modal-open aria-haspopup="dialog">
              {{ 'pickup_availability.general.check_other_stores'  | t }}
            </button>
          {%- endif -%}
        {%- endif -%}
      </div>
    </div>
    <!-- Availability modal -->
    <div
      class="pickup-availabilities-modal modal"
      id="PickupAvailabilityModal"
      role="dialog"
      aria-modal="true"
      aria-labelledby="PickupAvailabilitiesModalProductTitle"
    >
      <div class="pickup-availabilities-modal__header">
        <div class="pickup-availabilities-modal__product-information">
          <h2
            id="PickupAvailabilitiesModalProductTitle"
            class="pickup-availabilities-modal__product-title"
            data-pickup-availability-modal-product-title
          >
          </h2>
          <p
            class="pickup-availabilities-modal__variant-title pickup-availability-small-text"
            data-pickup-availability-modal-variant-title
          >
            {{ product_variant.title }}
          </p>
        </div>
        <button
          type="button"
          class="pickup-availabilities-modal__close js-modal-close-pickup-availability-modal text-link"
          aria-label="{{ 'general.accessibility.close_modal'  | t }}"
        >
          {% render 'icon-close' %}
        </button>
      </div>
      <ul class="pickup-availabilities-list" role="list">
        {%- for availability in pick_up_availabilities -%}
          <li class="pickup-availability-list__item">
            <h3 class="pickup-availability-list__location">
              {{ availability.location.name }}
            </h3>
            <div class="pickup-availability-list__stock pickup-availability-small-text">
              {%- if availability.available -%}
                {% render 'icon-in-stock' %} {{ 'pickup_availability.general.pick_up_available'  | t }}, {{ availability.pick_up_time | downcase }}
              {%- else -%}
                {% render 'icon-out-of-stock' %} {{ 'pickup_availability.general.pick_up_currently_unavailable'  | t }}
              {%- endif -%}
            </div>
            {%- assign address = availability.location.address -%}
            <address class="pickup-availability-list__address">
              {{ address | format_address }}
            </address>
            {%- if address.phone.size > 0 -%}
              <p class="pickup-availability-list__phone pickup-availability-small-text">
                {{ address.phone }}<br />
              </p>
            {%- endif -%}
          </li>
        {%- endfor -%}
      </ul>
    </div>
  {%- endif -%}
</div>
{% schema %}
{
  "name": "Pickup availability",
  "settings": []
}
{% endschema %}

4. Bedah

  1. product_variant, bukan product atau variant. Ini object khusus yang tersedia waktu section dirender lewat URL /variants/[id]. Section ini gak punya akses ke object product, cuma ke varian yang diminta. Ini alasan kenapa judul produk harus dikirim lewat JavaScript (di bawah).
  2. where: 'pick_up_enabled', true nyaring cuma lokasi yang nyalain pickup, terus .first jadi “lokasi terdekat” (urutan lokasinya ditentuin Shopify).
  3. Ringkasan punya dua cabang: tersedia (ikon stok, “Pickup available at X”, pick_up_time, tombol “View store info” kalau satu lokasi atau “Check other stores” kalau lebih) dan gak tersedia (“Pickup currently unavailable at X”, tombol cek toko lain cuma kalau lokasinya lebih dari satu).
  4. Terjemahan pakai interpolasi location_name dan sufiks _html dari 33 Locales dan Translasi > 3. Interpolasi, Pluralisasi, dan _html, karena nama lokasi biasanya ditebelin.
  5. Modal pakai role="dialog", aria-modal="true", aria-labelledby ke judul produk, dan tombol tutup dengan aria-label. Pola modal aksesibel dari 55 Accessibility.
  6. <h2> judul produk kosong dengan data-pickup-availability-modal-product-title, diisi JavaScript. product_variant.title bisa langsung dirender.
  7. Tiap lokasi: nama, status dengan pick_up_time di-downcase (“Usually ready in 24 hours” jadi “usually ready in 24 hours” biar nyambung sama kalimat sebelumnya), alamat lewat format_address dari 20 Filters Money, Metafield, Cart, Customer, dan Localization > 1. format_address (yang ngeformat sesuai konvensi negara), dan telepon kalau ada.

Wadah di Halaman Produk

<div> kosong tempat JavaScript naruh konten section, ditaruh di mana ringkasan mau tampil (biasanya di bawah tombol Add to cart):

<div class="product-single__store-availability-container"
data-store-availability-container
data-product-title="{{ product.title | escape }}"
data-has-only-default-variant="{{ product.has_only_default_variant }}"
data-base-url="{{ shop.url }}{{ routes.root_url }}"
>
</div>

Tiga atribut data ini jembatan buat info yang section-nya gak bisa akses:

  1. data-product-title, buat ngisi <h2> di modal.
  2. data-has-only-default-variant, kalau true, JavaScript nyembunyiin judul varian di modal (karena “Default Title” gak berguna buat pembeli).
  3. data-base-url, URL dasar sadar locale buat request.

Fungsi JavaScript

Konten section diambil lewat Section Rendering API (51 Ajax API dan Section Rendering API) dengan request yang diawali /variants/[variant-id], biar section-nya dirender dalam konteks varian itu dan product_variant terisi:

fetch(window.Shopify.routes.root + "variants/[variant-id]/?section_id=pickup-availability")
.then(response => response.text())
.then(text => {
  const container = document.querySelector('[data-store-availability-container]');
  const pickupAvailabilityHTML = new DOMParser()
    .parseFromString(text, 'text/html')
    .querySelector('.shopify-section');
  container.appendChild(pickupAvailabilityHTML);
})
.catch(e => {
  console.error(e);
});

[variant-id] diganti ID varian yang dipilih. Fungsi ini dipanggil dari JavaScript pemilih varian tiap kali varian berganti, dengan ID varian baru, dan isi wadah dikosongin dulu sebelum appendChild biar gak numpuk.

Ini bedanya sama request Section Rendering API lain: biasanya ?section_id=X di URL halaman sekarang, di sini di URL /variants/ID/. URL ini emang ada di Shopify (ngerender template produk buat varian itu), dan section_id bikin cuma section yang diminta yang dirender.

❌ request di URL produk, product_variant kosong, section gak ngeluarin apa-apa

fetch(`${window.location.pathname}?section_id=pickup-availability`)

✅ di URL varian

fetch(`${window.Shopify.routes.root}variants/${variantId}/?section_id=pickup-availability`)

Setelah konten masuk, JavaScript juga harus:

  1. Ngisi [data-pickup-availability-modal-product-title] dari data-product-title.
  2. Nyembunyiin [data-pickup-availability-modal-variant-title] kalau data-has-only-default-variant true.
  3. Masang handler buka/tutup modal di tombol [data-pickup-availability-modal-open] dan .js-modal-close-pickup-availability-modal, dengan fokus yang dipindah ke modal waktu buka dan balik ke tombol waktu tutup.

Versi Dawn ngebungkus semua ini di custom element <pickup-availability> yang nerima ID varian lewat atribut, dan pemilih varian tinggal ngeset atribut itu.

Ringkasan

  1. Pickup availability nampilin apakah varian bisa diambil di lokasi fisik merchant, langsung di halaman produk, butuh pemilih varian yang manggil fungsinya waktu varian berganti.
  2. Object: variant.store_availabilities → store_availability (available, pick_up_enabled, pick_up_time, location) → location (name, address). Nilai available/pick_up_time udah termasuk store transfer.
  3. Section pakai object khusus product_variant (gak ada akses product), saring where: 'pick_up_enabled', true, cuma output kalau ada hasil: ringkasan lokasi pertama + tombol, dan modal aksesibel dengan tiap lokasi, status, pick_up_time, format_address, telepon.
  4. Wadah <div> kosong dengan data-product-title, data-has-only-default-variant, data-base-url sebagai jembatan info produk.
  5. JavaScript fetch {root}variants/{id}/?section_id=pickup-availability, parse .shopify-section, suntik ke wadah, isi judul produk, sembunyiin judul varian default, pasang modal. Panggil ulang tiap varian berganti.

Bab selanjutnya kita bahas kepercayaan dan keamanan: CAPTCHA otomatis Shopify di form (kontak, login, komentar), cara kerjanya, cara nanganin halaman challenge, dan badge keamanan/pembayaran yang boleh ditampilin di theme.