# NexoraCloud Store API V1

Website ini berdiri sendiri dan memiliki database serta Owner Dashboard sendiri. Website **tidak** memanggil Telegram Bot API, tidak membutuhkan Telegram `initData`, `botRef`, dan tidak membutuhkan backend bot. Bot adalah aplikasi eksternal yang mengakses website ini sebagai API client menggunakan API key yang dibuat di Owner Dashboard.

- Base URL produksi: `https://webstore.nexoracloud.my.id`
- API base path: `/api/v1`
- Format: JSON UTF-8
- Mata uang saat ini: IDR
- Respons sukses: `{ "ok": true, "data": ... }`
- Respons gagal: `{ "ok": false, "code": "...", "message": "..." }`

## 1. API key

1. Login ke `https://webstore.nexoracloud.my.id/owner`.
2. Buka **API keys → Buat API key**.
3. Masukkan nama integrasi, masa berlaku, dan scopes.
4. Pilih seluruh scopes hanya jika bot memang perlu mengelola semua bagian store.
5. Salin secret saat dialog pertama kali muncul. Secret tidak dapat dilihat kembali setelah ditutup.
6. Simpan pada environment bot, misalnya `NEXORA_STORE_API_KEY`. Jangan taruh secret pada JavaScript/browser atau repository publik.

Semua request terautentikasi mengirim:

```http
Authorization: Bearer nx_live_YOUR_SECRET
Content-Type: application/json
```

API key dibuat dan dicabut oleh Owner Dashboard. Database hanya menyimpan SHA-256 hash dari key, bukan secret mentah. Key dapat memiliki masa berlaku hingga 365 hari dan dapat dicabut tanpa mengganti key lainnya.

Available scopes:

| Scope | Izin |
|---|---|
| `products:read` | Baca katalog lengkap, termasuk metadata dan produk nonaktif |
| `products:write` | Buat, edit, arsipkan produk |
| `inventory:read` | Baca seluruh ringkasan stok dan mutasi stok |
| `inventory:write` | Tambah atau kurangi stok |
| `orders:read` | Baca pesanan |
| `orders:write` | Buat pesanan dan mengubah status pesanan |
| `coupons:read` | Baca voucher |
| `coupons:write` | Buat, ubah, nonaktifkan voucher |
| `warranties:read` | Baca garansi dan klaim |
| `warranties:write` | Membuat klaim dan menyetujui/menolak klaim |
| `settings:read` | Membaca pengaturan toko non-rahasia |
| `settings:write` | Mengubah pengaturan toko non-rahasia |

## 2. Public storefront

| Method | Path | Auth | Fungsi |
|---|---|---|---|
| GET | `/healthz` | Public | Health check server dan database |
| GET | `/api/v1/storefront` | Public | Nama, deskripsi, maintenance, URL bantuan/ketentuan, mata uang |
| GET | `/api/v1/products` | Public | Katalog aktif tanpa metadata sensitif |

Contoh:

```bash
curl https://webstore.nexoracloud.my.id/api/v1/storefront
curl https://webstore.nexoracloud.my.id/api/v1/products
```

Untuk membaca seluruh produk, produk nonaktif, atau metadata produk, gunakan `products:read`:

```text
GET /api/v1/products?includeMetadata=true&includeInactive=true
```

Metadata dapat berisi informasi konfigurasi non-rahasia seperti `panelUrl`, `nodeId`, `eggId`, `locationId`, dan profil RAM/CPU/disk. Field credential seperti password, token, API key, authorization, atau private key ditolak oleh server.

## 3. Products

| Method | Path | Scope | Fungsi |
|---|---|---|---|
| GET | `/products` | Public; `products:read` untuk metadata/nonaktif | List katalog |
| GET | `/products/:id` | `products:read` | Detail produk |
| POST | `/products` | `products:write` | Buat produk |
| PATCH | `/products/:id` | `products:write` | Ubah produk |
| DELETE | `/products/:id` | `products:write` | Arsipkan produk, bukan hard-delete |

Contoh body:

```json
{
  "sku": "LEGAL-2GB",
  "name": "Panel Legal 2GB",
  "category": "legal",
  "description": "Paket Panel Legal 2GB",
  "price": 5000,
  "stock": 8,
  "lowStockThreshold": 2,
  "isActive": true,
  "metadata": {
    "panelUrl": "https://panel-legal.example.com",
    "nodeId": 1,
    "eggId": 15,
    "locationId": 1,
    "ramMb": 2048,
    "diskMb": 10240,
    "cpuPercent": 100
  }
}
```

Kategori yang diterima: `legal`, `ilegal`, `admin_legal`, `admin_ilegal`. Harga harus berupa integer rupiah.

### Admin Panel products

- Buat dan aktifkan produk SKU `LEGAL-UNLIMITED` pada kategori `legal` terlebih dahulu.
- Buat dan aktifkan produk SKU `ILEGAL-UNLIMITED` pada kategori `ilegal` terlebih dahulu.
- Harga Admin Panel Legal dihitung server sebagai harga `LEGAL-UNLIMITED` × 115%, dibulatkan ke atas.
- Harga Admin Panel Ilegal dihitung server sebagai harga `ILEGAL-UNLIMITED` × 115%, dibulatkan ke atas.
- Perubahan harga produk Unlimited memperbarui harga produk Admin terkait secara otomatis.
- Produk Admin Panel hanya dapat diaktifkan jika metadata memuat `panelUrl` HTTPS, `isolated: true`, dan `restrictionsEnforced: true`.
- Nilai `isolated` dan `restrictionsEnforced` adalah pernyataan konfigurasi Owner, bukan pemeriksaan otomatis terhadap fork Pterodactyl. Aktifkan hanya setelah permission benar-benar diuji pada panel target.
- Maksimal dua order akun Admin yang masih aktif untuk satu `customer.id` pada satu `panelUrl` target.

Metadata produk akan disalin ke metadata order agar bot dapat mengambil konfigurasi target yang digunakan pada saat order dibuat.

## 4. Inventory / stock

| Method | Path | Scope | Fungsi |
|---|---|---|---|
| GET | `/inventory` | `inventory:read` | Ringkasan stok semua produk |
| GET | `/inventory/:productId` | `inventory:read` | Stok satu produk dan 100 mutasi terakhir |
| POST | `/inventory/:productId` | `inventory:write` | Tambah atau kurangi stok |

Body mutasi stok:

```json
{ "delta": 10, "reason": "Restock batch" }
```

Nilai `delta` negatif mengurangi stok. Stok tidak dapat menjadi negatif. Setiap mutasi menyimpan nilai sebelum/sesudah, alasan, waktu, dan aktor.

Contoh:

```bash
curl 'https://webstore.nexoracloud.my.id/api/v1/inventory' \
  -H "Authorization: Bearer $NEXORA_STORE_API_KEY"
```

## 5. Orders

| Method | Path | Scope | Fungsi |
|---|---|---|---|
| GET | `/orders` | `orders:read` | Daftar 300 order terakhir |
| GET | `/orders/:id` | `orders:read` | Detail order |
| POST | `/orders` | `orders:write` | Buat order dan reservasi stok |
| PATCH | `/orders/:id/status` | `orders:write` | Ubah status order |

Pembuatan order wajib menggunakan header `Idempotency-Key`. Key yang sama dan body sama mengembalikan order lama; key sama dengan body berbeda menghasilkan HTTP 409. Ini melindungi reservasi stok dari pengiriman ulang request yang sama.

```bash
curl -X POST 'https://webstore.nexoracloud.my.id/api/v1/orders' \
  -H "Authorization: Bearer $NEXORA_STORE_API_KEY" \
  -H 'Idempotency-Key: order-unik-001' \
  -H 'Content-Type: application/json' \
  -d '{
    "productId": "PRODUCT_UUID",
    "quantity": 1,
    "customer": { "id": "telegram:123456", "name": "Customer", "contact": "@customer" },
    "couponCode": "NEXORA10",
    "note": "Order melalui bot"
  }'
```

Harga dibaca dari database website, bukan dari nilai yang dikirim bot. Stok direservasi saat order dibuat. Status yang diizinkan:

- `awaiting_payment` → `paid` atau `cancelled`
- `paid` → `processing`, `cancelled`, atau `refunded`
- `processing` → `completed`, `cancelled`, atau `refunded`
- `completed` → `refunded`

Pembatalan/refund mengembalikan stok tepat satu kali. Status `completed` menerbitkan kode garansi sesuai kategori produk. Ketika `maintenance_mode=true`, pembuatan order baru ditolak dengan HTTP 503 `STORE_MAINTENANCE`.

## 6. Coupons

| Method | Path | Scope | Fungsi |
|---|---|---|---|
| GET | `/coupons` | `coupons:read` | List voucher |
| POST | `/coupons` | `coupons:write` | Buat voucher |
| PATCH | `/coupons/:id` | `coupons:write` | Ubah voucher |
| DELETE | `/coupons/:id` | `coupons:write` | Nonaktifkan voucher |

Body contoh:

```json
{
  "code": "NEXORA10",
  "discountType": "percent",
  "discountValue": 10,
  "maxRedemptions": 50,
  "minSubtotal": 0,
  "category": "legal",
  "expiresAt": null
}
```

`discountType` adalah `percent` (1–100) atau `fixed` (rupiah). Kategori dapat berupa `all` atau salah satu kategori produk. Website memvalidasi tanggal, minimum subtotal, kategori, dan kuota dalam transaksi pembuatan order.

## 7. Warranties

| Method | Path | Scope | Fungsi |
|---|---|---|---|
| GET | `/warranties` | `warranties:read` | List garansi |
| GET | `/warranties/claims` | `warranties:read` | List klaim |
| POST | `/warranties/:code/claims` | `warranties:write` | Ajukan klaim |
| PATCH | `/warranties/claims/:id` | `warranties:write` | Setujui/tolak klaim |

Garansi diterbitkan saat order selesai. Setiap pengajuan memakai `requestKey` unik dan idempoten. Klaim pending/approved menggunakan kuota; klaim rejected melepaskan kuota. Identitas pelanggan divalidasi bila garansi terikat ke customer.

## 8. Store settings

| Method | Path | Auth | Fungsi |
|---|---|---|---|
| GET | `/api/v1/settings` | `settings:read` | Baca pengaturan toko |
| PUT | `/api/v1/settings` | `settings:write` | Ubah pengaturan toko |

Pengaturan yang diizinkan: `store_name`, `store_description`, `maintenance_mode`, `support_url`, `terms_url`, dan `default_currency`. Saat ini mata uang yang didukung hanya `IDR`. `support_url` dan `terms_url` harus berupa HTTPS. Secret/credential tidak pernah dapat disimpan melalui Settings API.

Owner Dashboard juga menyediakan pengaturan yang sama di tab **Pengaturan**.

## 9. Bot integration notes

- Bot harus memanggil API dari proses server-side, bukan langsung dari browser pelanggan.
- API key website hanya mengizinkan akses ke database/store sesuai scopes. API key website **bukan** PLTA/PLTC Pterodactyl.
- Website tidak menyimpan atau membutuhkan token bot Telegram, PLTA/PLTC, atau password panel.
- Jika bot bertugas membuat user/server Pterodactyl, bot tetap menggunakan credential Pterodactyl yang disimpan aman di host bot/provisioner. API website menyediakan produk, metadata target, order, harga, stok, voucher, garansi, dan status.
- Proses pembayaran provider dan provisioning server dijalankan oleh bot/layanan terpisah, kemudian bot memperbarui status order melalui API website.
- Gunakan HTTPS. Simpan secret API key di environment/secrets manager bot. Cabut key dari Owner Dashboard bila bocor.

Rate limit API key: 300 request/menit per key. Login Owner: 8 percobaan/menit per IP. Kegagalan memberikan kode error yang stabil; jangan melakukan retry order dengan Idempotency-Key baru untuk intent yang sama.
