> ## Documentation Index
> Fetch the complete documentation index at: https://docs.scalev.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bangun storefront

> Panduan implementasi lengkap A-Z untuk membangun storefront custom dengan Storefront API v3.

Storefront API v3 membantu Anda membangun UI storefront sendiri, sementara Scalev menangani katalog, cart, checkout, akun customer, lokasi pengiriman, order, dan instruksi payment. Gunakan halaman ini sebagai panduan implementasi lengkap A-Z setelah Anda memahami [overview Storefront API](/id/storefront-api-id/introduction), [model auth](/id/storefront-api-id/auth), [alur checkout](/id/storefront-api-id/checkout-payments), dan [event konversi iklan](/id/storefront-api-id/advertising-conversion-events) opsional.

Storefront API v3 bisa di-host sebagai frontend statis, misalnya di Cloudflare Pages, Vercel, Netlify, atau CDN lain, tanpa membuat backend custom atau proxy same-origin. Frontend memanggil `https://api.scalev.com` secara langsung.

<Warning>
  **Panggil Storefront API v3 langsung dari browser**

  Storefront API v3 dirancang untuk dipanggil langsung dari browser atau client end-user. Jangan menaruh Storefront API v3 di balik backend proxy, reverse proxy, middleware, edge function, serverless function, atau pass-through API layer milik Anda sendiri.

  Rate limiter untuk Storefront API v3 sengaja dibuat ketat karena API ini dirancang untuk penggunaan client-side langsung. Jika request ini diproxy, banyak user dapat terlihat datang dari IP server yang sama, sehingga rate limiting, deteksi spam, proteksi order duplikat, dan logika validasi lain dapat terdampak negatif.

  Untuk deployment headless storefront, anggap browser sebagai caller yang dimaksud untuk Storefront API v3. Gunakan backend Anda sendiri hanya untuk hal non-Scalev seperti admin settings, custom business logic, atau private integrations.
</Warning>

## Arsitektur

Gunakan aturan ini untuk frontend storefront:

* Gunakan `unique_id` store di URL runtime storefront publik.
* Gunakan storefront public API key yang publishable di kode frontend.
* Kirim key sebagai `X-Scalev-Storefront-Api-Key` untuk endpoint `/public/*`.
* Kirim customer JWT sebagai `Authorization: Bearer <access>` untuk endpoint `/customers/me/*`.
* Simpan dan kirim ulang `X-Scalev-Guest-Token` untuk alur guest cart.
* Jangan expose business API key di kode frontend.
* Jangan membuat backend endpoint custom untuk katalog, cart, checkout, instruksi payment, pelacakan order, atau alur akun customer.

Semua path endpoint di bawah ini relatif terhadap:

```text theme={null}
https://api.scalev.com/v3/stores/{store_id}
```

## Checklist setup

1. Buat atau ambil storefront public API key.
2. Daftarkan origin storefront, misalnya `https://demo.example.com`, sebagai allowed origin.
3. Konfigurasikan frontend dengan:
   * `SCALEV_API_BASE=https://api.scalev.com`
   * `SCALEV_STORE_UNIQUE_ID=<store_unique_id>`
   * `SCALEV_STOREFRONT_API_KEY=<publishable_storefront_key>`
4. Serve frontend dari origin yang sudah didaftarkan.
5. Buat panggilan API langsung dari browser.
6. Jika storefront menjalankan iklan, pasang browser pixel dan event konversi server Scalev dengan event ID yang sama. Lihat [Event konversi iklan](/id/storefront-api-id/advertising-conversion-events).

Runtime Storefront memakai `unique_id` store. Endpoint setup Storefront API adalah endpoint bisnis terautentikasi. Endpoint setup bisa menerima numeric store ID lama untuk kompatibilitas, tetapi frontend storefront sebaiknya dikonfigurasi dengan `unique_id` store.

## Helper fetch minimal

Gunakan satu helper untuk route storefront publik anonim:

```ts theme={null}
const API_BASE = "https://api.scalev.com";
const STORE_ID = "store_xxx";
const STOREFRONT_KEY = "sfpk_xxx";

async function storefrontFetch(path: string, init: RequestInit = {}) {
  const headers = new Headers(init.headers);
  headers.set("Accept", "application/json");
  headers.set("X-Scalev-Storefront-Api-Key", STOREFRONT_KEY);

  return fetch(`${API_BASE}/v3/stores/${STORE_ID}${path}`, {
    ...init,
    credentials: "omit",
    headers,
  });
}
```

Gunakan helper lain untuk route customer yang sudah login:

```ts theme={null}
async function customerFetch(
  path: string,
  accessToken: string,
  init: RequestInit = {}
) {
  const headers = new Headers(init.headers);
  headers.set("Accept", "application/json");
  headers.set("Authorization", `Bearer ${accessToken}`);

  return fetch(`${API_BASE}/v3/stores/${STORE_ID}${path}`, {
    ...init,
    credentials: "omit",
    headers,
  });
}
```

Simpan guest cart token dari response guest cart pertama:

```ts theme={null}
const response = await storefrontFetch("/public/cart");
const guestToken = response.headers.get("x-scalev-guest-token");

if (guestToken) {
  localStorage.setItem("scalev_guest_token", guestToken);
}
```

Kirim ulang guest token pada panggilan guest cart dan guest checkout berikutnya:

```ts theme={null}
const headers = new Headers({
  "Content-Type": "application/json",
});

const guestToken = localStorage.getItem("scalev_guest_token");

if (guestToken) {
  headers.set("X-Scalev-Guest-Token", guestToken);
}

await storefrontFetch("/public/cart/items", {
  method: "POST",
  headers,
  body: JSON.stringify({
    type: "variant",
    variant_id: 494535,
    quantity: 1,
  }),
});
```

## Alur katalog

Gunakan urutan katalog ini:

1. `GET /public/items/count`
2. `GET /public/items`
3. Gunakan `entity_type` untuk membedakan:
   * `product`
   * `bundle_price_option`
4. Untuk detail product, panggil `GET /public/products/{slug}`.
5. Untuk detail bundle price option, panggil `GET /public/bundle-price-options/{slug}`.
6. Untuk harga variant, panggil `GET /public/variants/pricing?ids=...`.
7. Untuk availability, panggil `GET /public/variants/{variant_id}/availability`.

Gunakan `variant_id` untuk product variant. Gunakan `bundle_price_option_id` untuk bundle price option. Keduanya bisa dimasukkan ke payload cart dan checkout dengan typed item union.

## Shape item checkout

Gunakan shape ini untuk product variant:

```json theme={null}
{
  "type": "variant",
  "variant_id": 494535,
  "quantity": 1
}
```

Gunakan shape ini untuk bundle price option:

```json theme={null}
{
  "type": "bundle_price_option",
  "bundle_price_option_id": 31925,
  "quantity": 1
}
```

## Alur guest cart dan guest checkout

Gunakan alur ini untuk buyer anonim:

1. `GET /public/cart`
   * simpan `x-scalev-guest-token`
2. `POST /public/cart/items`
3. `PATCH /public/cart/items/{item_id}`
4. `DELETE /public/cart/items/{item_id}`
5. `POST /public/checkout/shipping-options`
6. `POST /public/checkout/summary`
7. `POST /public/checkout`
8. `GET /public/orders/{secret_slug}`
9. `POST /public/orders/{secret_slug}/payment`

Perilaku sumber checkout:

* Jika `items` dikirim, order dibuat dari direct `items`.
* Jika `items` tidak dikirim, order dibuat dari guest cart yang direferensikan oleh `X-Scalev-Guest-Token`.
* Jika direct `items` dan `X-Scalev-Guest-Token` sama-sama dikirim, direct `items` menjadi sumber dan guest cart dikosongkan setelah order berhasil dibuat.

## Alur auth customer

Panggil:

```text theme={null}
POST /public/auth/login
```

Jika response berisi `access` dan `refresh`, login selesai.

Jika response adalah OTP challenge, tampilkan input OTP dan panggil:

```text theme={null}
POST /public/auth/otp/verify
```

Penanganan token:

* Simpan `access` untuk panggilan customer terautentikasi.
* Simpan `refresh` jika Anda membuat sesi persisten.
* Refresh token hanya bisa dipakai satu kali.
* Setelah memanggil refresh, buang refresh token lama dan simpan refresh token baru.
* Logout memakai `POST /public/auth/jwt/blacklist`.

Blacklist token customer dengan:

```json theme={null}
{
  "tokens": ["<access>", "<refresh>"]
}
```

## Alur akun customer

Gunakan endpoint customer ini sesuai urutan UI akun:

1. `GET /customers/me`
2. `PATCH /customers/me`
3. `POST /customers/me/set-new-password`
4. `GET /customers/me/addresses`
5. `POST /customers/me/addresses`
6. `GET /customers/me/addresses/{address_id}`
7. `PATCH /customers/me/addresses/{address_id}`
8. `DELETE /customers/me/addresses/{address_id}`

## Alur customer cart dan checkout

Gunakan alur ini untuk customer yang login:

1. `GET /customers/me/cart`
2. `POST /customers/me/cart/items`
3. `PATCH /customers/me/cart/items/{item_id}`
4. `DELETE /customers/me/cart/items/{item_id}`
5. `GET /customers/me/checkout/addresses`
6. `GET /customers/me/checkout/payment-methods`
7. `POST /customers/me/checkout/shipping-options`
8. `POST /customers/me/checkout/summary`
9. `POST /customers/me/checkout`
10. `GET /customers/me/orders`
11. `GET /customers/me/orders/{id}`

Perilaku sumber customer checkout:

* Jika `items` dikirim, order dibuat dari direct `items`.
* Jika `items` tidak dikirim, `cart_id` memilih sumber customer cart.
* Jika `items` dan `cart_id` sama-sama dikirim, direct `items` menjadi sumber dan cart yang direferensikan dikosongkan setelah order berhasil dibuat.

## Alur lokasi

Gunakan salah satu pola pemilihan lokasi yang didukung.

Pola A:

1. Pilih provinsi: `GET /public/locations/provinces`
2. Pilih kota: `GET /public/locations/cities?province_id=...`
3. Pilih subdistrict: `GET /public/locations/subdistricts?city_id=...`
4. Kode pos: `GET /public/locations/{location_id}/postal-codes`

Pola B:

1. Search langsung: `GET /public/locations?search=...`
2. Gunakan location atau subdistrict yang dikembalikan.
3. Kode pos: `GET /public/locations/{location_id}/postal-codes`

Response lokasi hanya menyertakan field yang dibutuhkan untuk tampilan dan request berikutnya:

```json theme={null}
{
  "province_id": 31,
  "province_name": "DKI Jakarta"
}
```

```json theme={null}
{
  "city_id": 3171,
  "city_name": "Kota Jakarta Pusat"
}
```

```json theme={null}
{
  "id": 9090,
  "subdistrict_name": "Gambir",
  "city_name": "Kota Jakarta Pusat",
  "province_name": "DKI Jakarta",
  "display": "Gambir, Kota Jakarta Pusat, DKI Jakarta"
}
```

```json theme={null}
{
  "postal_code": "10110"
}
```

## Alur reset password

Implementasikan reset password sebagai alur link di browser:

1. User memasukkan email.
2. Frontend memanggil `POST /public/auth/forget-password`.
3. Scalev mengirim link email ke `/reset-password?token=<reset-token>`.
4. Frontend membaca `token` dari URL.
5. User memasukkan password baru dan konfirmasi.
6. Frontend memanggil `POST /public/auth/save-password`.

Jangan tampilkan input token reset manual. Frontend tidak boleh meminta user copy dan paste reset token. Hostname reset dipilih dari registered allowed origin saat request menyertakan `Origin`.

## Rendering payment

Gunakan field order ini untuk merender halaman payment:

* `product_price`
* `product_discount`
* `shipping_cost`
* `shipping_discount`
* `other_income`
* `gross_revenue`
* `payment_method`
* `sub_payment_method`
* `payment_account_holder`
* `payment_account_number`
* `payment_url`
* `pg_payment_info`
* `store.payment_accounts`
* `handler_phone`
* `chat_message`

Manual bank transfer bisa memakai `store.payment_accounts` saat tidak ada akun spesifik pada order. Metode hosted atau provider-backed bisa memakai `payment_url` atau `pg_payment_info`. `gross_revenue` adalah jumlah yang dibayar buyer.

Lihat [Checkout dan Payment](/id/storefront-api-id/checkout-payments) untuk aturan rendering per metode.

## Subscription customer dan course

Untuk halaman akun customer, gunakan:

* `GET /customers/me/subscriptions`
* `GET /customers/me/subscriptions/{id}`
* `POST /customers/me/subscriptions/{id}/cancel`
* `POST /customers/me/subscriptions/{id}/resume`
* `GET /customers/me/subscription-items/{id}/upgrade`
* `POST /customers/me/subscription-items/{id}/upgrade`
* `GET /customers/me/subscription-items/{id}/downgrade`
* `POST /customers/me/subscription-items/{id}/downgrade`
* `GET /customers/me/variants/{uuid}/course`
* `GET /customers/me/course-sections/{uuid}`
* `GET /customers/me/course-contents/{uuid}`
* `PATCH /customers/me/course-contents/{uuid}`

Endpoint course bergantung pada entitlement. Response `403` atau `404` bisa berarti customer tidak memiliki variant course tersebut, bukan berarti integrasi storefront rusak.

## Payload checkout lengkap minimal

Gunakan payload checkout variant lengkap seperti:

```json theme={null}
{
  "items": [
    {
      "type": "variant",
      "variant_id": 494535,
      "quantity": 1
    }
  ],
  "customer_name": "Demo Customer",
  "customer_email": "demo.customer@example.com",
  "customer_phone": "6281234567890",
  "shipping_address": "Jl. Demo Storefront API No. 3",
  "shipping_province": "DKI Jakarta",
  "shipping_city": "Kota Jakarta Pusat",
  "shipping_subdistrict": "Cempaka Putih",
  "shipping_postal_code": "10510",
  "shipping_location_id": 9089,
  "payment_method": "bank_transfer"
}
```

Gunakan payload checkout bundle price option seperti:

```json theme={null}
{
  "items": [
    {
      "type": "bundle_price_option",
      "bundle_price_option_id": 31925,
      "quantity": 1
    }
  ],
  "customer_name": "Demo Customer",
  "customer_email": "demo.customer@example.com",
  "payment_method": "bank_transfer"
}
```

## Checklist agent

Implementasi lengkap saat bisa:

* menampilkan daftar item katalog
* menampilkan detail product
* menampilkan detail bundle price option
* mengecek pricing dan availability
* menyimpan guest cart dengan `X-Scalev-Guest-Token`
* menambah, mengubah, dan menghapus item cart
* memilih lokasi pengiriman
* menghitung shipping options dan checkout summary
* membuat public checkout order
* menampilkan status public order dan instruksi payment
* membuat atau mengambil instruksi payment
* login customer dengan branch direct-token atau OTP
* refresh dan blacklist JWT
* mengelola profil dan alamat customer
* memakai customer cart
* membuat authenticated checkout order
* menampilkan daftar dan detail authenticated order
* menampilkan subscription dan course entitlement-gated saat ada
* melakukan semua hal di atas dari frontend tanpa backend custom
