# AGENTS.md — Proyek totti-commerce

## 📌 Info Project

| Item | Detail |
|---|---|
| Nama | totti-commerce |
| Stack | MedusaJS v2 (backend) + Next.js 15 (storefront) |
| Monorepo | Turborepo |
| Package Manager | **Yarn 4.12.0** (unifikasi) |

## 🏗️ Struktur Monorepo

```
medusa_totti/
├── apps/
│   ├── backend/          # MedusaJS API server
│   │   └── src/
│   │       ├── admin/       # Admin customization
│   │       ├── api/         # Custom API routes
│   │       │   └── store/rajaongkir/
│   │       │       ├── destination/route.ts  # GET — cari kota/kecamatan
│   │       │       └── cost/route.ts         # POST — hitung ongkir
│   │       ├── jobs/        # Scheduled jobs
│   │       ├── links/       # Module links
│   │       ├── migration-scripts/
│   │       │   ├── initial-data-seed.ts      # Seed awal (Europe)
│   │       │   └── seed-indonesia.ts         # Seed Indonesia (region IDR)
│   │       ├── modules/
│   │       │   ├── rajaongkir-fulfillment/   # Custom fulfillment module
│   │       │   │   ├── service.ts
│   │       │   │   ├── index.ts
│   │       │   │   └── package.json
│   │       │   └── xendit-payment/           # Xendit payment module
│   │       │       ├── service.ts
│   │       │       ├── index.ts
│   │       │       └── package.json
│   │       ├── hooks/                        # Webhook handlers
│   │       ├── subscribers/ # Event subscribers
│   │       └── workflows/   # Custom workflows
│   └── storefront/      # Next.js 15 storefront (yarn)
├── .yarnrc.yml
├── .yarn/releases/yarn-4.12.0.cjs
├── turbo.json
└── package.json
```

## ⚙️ Setup Awal

```bash
# 1. Setup environment
cp apps/backend/.env.template apps/backend/.env

# 2. Edit apps/backend/.env — isi DATABASE_URL
# Contoh: DATABASE_URL=postgres://user:password@localhost:5432/nama_database

# 3. Install dependencies (root + backend — workspace)
yarn install

# 4. Install storefront (terpisah karena React 18 vs 19)
cd apps/storefront && yarn install && cd ../..

# 5. Migrate database + seed (seed otomatis lewat migration-scripts)
yarn workspace @dtc/backend medusa db:migrate

# 6. Buat admin user
cd apps/backend && yarn medusa user -e admin@totti.com -p admin123

# 7. Jalankan backend
yarn backend:dev
```

## 🚀 Cara Menjalankan

### Backend ([http://localhost:9000](http://localhost:9000))
```bash
yarn backend:dev
```
- API: `http://localhost:9000`
- Admin Dashboard: `http://localhost:5173`

### Storefront ([http://localhost:8000](http://localhost:8000))
```bash
cd apps/storefront && yarn dev
```

## 🌐 Environment & Setup Status

### Progress Setup (26 Mei 2026)
| Langkah | Status |
|---|---|
| Environment (`apps/backend/.env`) | ✅ Sudah diisi |
| Migrasi npm → Yarn | ✅ Selesai |
| `yarn install` (root + backend) | ✅ Selesai |
| `yarn install` (storefront) | ✅ Selesai |
| PostgreSQL user `medusa_totti` | ✅ Sudah dibuat |
| Database `medusa_totti_backend` | ✅ Sudah dibuat |
| `medusa db:migrate` | ✅ Selesai |
| Seed data | ✅ Otomatis via migration-scripts |
| Backend running | ✅ Berjalan |
| Admin user dibuat | ✅ `admin@totti.com` / `admin123` |
| Region Indonesia (IDR) | ✅ Sudah di-seed |
| Module RajaOngkir Fulfillment | ✅ Fulfillment provider |
| Shipping options RajaOngkir (JNE, J&T, SiCepat, POS, TIKI) | ✅ 5 calculated options |
| API routes RajaOngkir | ✅ `/store/rajaongkir/destination` + `/cost` |
| RajaOngkir API test | ✅ Destination & cost berfungsi |
| `calculatePrice` metadata fix | ✅ Pakai `rajaongkir_subdistrict_id` dari metadata |
| Origin subdistrict ID | ✅ `17596` (Cempaka Putih Barat, Jakarta Pusat) |
| Storefront checkout ongkir | ⏳ Phase 2 |
| Module Xendit Payment | ✅ Phase 3 — Selesai |
| Webhook handler Xendit | ✅ Selesai |
| XENDIT_API_KEY di .env | ✅ Selesai |

### Koneksi Database
- **Host:** `localhost:5432`
- **User:** `medusa_totti`
- **Password:** `Medusa_totti2026!!!`
- **Database:** `medusa_totti_backend`
- **DATABASE_URL:**
  ```
  postgres://medusa_totti:Medusa_totti2026!!!@localhost:5432/medusa_totti_backend
  ```
- **PostgreSQL versi:** 18.3 (WSL Ubuntu)
- **Auth:** TCP via `localhost` (md5 password)

### Admin Akun
- **Email:** `admin@totti.com`
- **Password:** `admin123`
- **Dashboard:** `http://localhost:5173`
- Cara buat ulang: `cd apps/backend && yarn medusa user -e admin@totti.com -p admin123`

### Environment Variables (apps/backend/.env)
```
STORE_CORS=http://localhost:8000,https://docs.medusajs.com
ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com
AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com
JWT_SECRET=supersecret
COOKIE_SECRET=supersecret
DATABASE_URL=postgres://medusa_totti:Medusa_totti2026!!!@localhost:5432/medusa_totti_backend
DB_NAME=medusa_totti_backend
RAJAONGKIR_API_KEY=640f099bca5a28d5dc4e8c296bc61505
RAJAONGKIR_ORIGIN_CITY=17596
XENDIT_API_KEY=
XENDIT_WEBHOOK_SECRET=
```

### Catatan Penting
- **Redis tidak terinstall** — `REDIS_URL` dihapus dari `.env` agar Medusa tidak mencoba konek. Caching tidak aktif, tapi server tetap jalan.
- **`yarn.lock`** di-commit ke repo (file lock yarn).
- **Backend (React 18) dan Storefront (React 19)** — workspace terpisah karena beda versi React. `yarn install` dilakukan 2x.
- Semua perubahan konfigurasi backend via `apps/backend/medusa-config.ts`.
- Database: PostgreSQL 18 (WSL).
- **Proyek ada di filesystem Windows** (`/mnt/c/...`), dikerjakan dari **WSL terminal** (Linux).

### API Endpoints Key Notes (Verified 27 Mei 2026)
- Auth register: `POST /auth/customer/emailpass/register` (bukan `/store/auth/...`)
- Login: `POST /auth/customer/emailpass` → dapat JWT token
- Cart update: `POST /store/carts/:id` (bukan `PATCH`)
- Line items: `POST /store/carts/:id/line-items`
- Shipping methods: `POST /store/carts/:id/shipping-methods`
- Payment collection: dua step — `POST /store/payment-collections` lalu `POST /store/payment-collections/:id/payment-sessions`
- Complete order: `POST /store/carts/:id/complete`
- Payment provider ID untuk test: `pp_system_default`
- Payment provider Xendit: `pp_xendit_xendit`
- Payment session Xendit: `POST /store/payment-collections/:id/payment-sessions` dgn `{ "provider_id": "pp_xendit_xendit", "data": { "success_url": "...", "failure_url": "..." } }`
- Cart address metadata (`rajaongkir_subdistrict_id`) tersimpan di database meski tidak muncul di API response

## 🧪 Full Checkout Flow Simulation — Verified ✅ (27 Mei 2026)

Tested via `curl` against `http://localhost:9000` — all 11 steps successful end-to-end.

```
Publishable Key: pk_8ca23d425817bba8ad040fd7063cf847bb89ed0c92bcf92baf340d1e45c8fe60
Region Indonesia: reg_01KSE5694RPHMRDX9M1N2CMXQ1
Origin Subdistrict: 17596 (Cempaka Putih Barat, Jakarta Pusat)
```

| Step | Endpoint | Result |
|---|---|---|
| Register | `POST /auth/customer/emailpass/register` + `POST /store/customers` | ✅ |
| Login | `POST /auth/customer/emailpass` → JWT token | ✅ |
| List products | `GET /store/products` (4 products) | ✅ |
| Create cart (IDR) | `POST /store/carts` with `region_id` Indonesia | ✅ |
| Add line item | `POST /store/carts/:id/line-items` (Sweatshirt Rp 350.000) | ✅ |
| Set shipping address | `POST /store/carts/:id` with `shipping_address.metadata.rajaongkir_subdistrict_id` | ✅ |
| List shipping options | `GET /store/shipping-options?cart_id=...` (5 RajaOngkir) | ✅ |
| **Add shipping method** | **`POST /store/carts/:id/shipping-methods` → calculated price Rp 10.000!** | ✅ |
| Payment collection | `POST /store/payment-collections` | ✅ |
| Payment session | `POST /store/payment-collections/:id/payment-sessions` (provider: `pp_system_default`) | ✅ |
| **Complete order** | **`POST /store/carts/:id/complete` → Order created!** | ✅ |

**Order Result:**
| Field | Value |
|---|---|
| Order ID | `order_01KSN5KM2ZE86JC5GMAP91AM8S` |
| Item | Medusa Sweatshirt (S) × 1 — Rp 350.000 |
| Shipping | **JNE — Rp 10.000** ✅ calculated via RajaOngkir API |
| Total | **Rp 360.000** |
| Shipping Address | Jl. Contoh No. 1, Jakarta Pusat |
| Payment | `pp_system_default` (manual) |

**Key Findings:**
- `calculatePrice` di `rajaongkir-fulfillment/service.ts` **dipanggil sukses** oleh Medusa engine saat `POST /store/carts/:id/shipping-methods`
- RajaOngkir API `calculate/domestic-cost` return ongkos kirim **Rp 10.000** (400g, Jakarta Pusat → Jakarta Pusat, JNE)
- Metadata `rajaongkir_subdistrict_id` tersimpan di `cart_address.metadata` (jsonb) di database ✅
- `calculatePrice` reads `optionData.courier` (dari `shipping_option.data.courier`) dan `context.shipping_address.metadata.rajaongkir_subdistrict_id` (dari DB) ✅
- API endpoint auth: `/auth/customer/emailpass/register` (bukan `/store/auth/register/...`)
- Cart update: `POST /store/carts/:id` (bukan `PATCH`)
- Payment collection: dua step — `POST /store/payment-collections` lalu `POST /store/payment-collections/:id/payment-sessions`
- Xendit `initiatePayment` simpan `session_id` di Xendit invoice `metadata.session_id` — digunakan webhook untuk capture
- Event bus lokal (in-memory) — webhook process dengan delay 5s + 3 retry
- `authorizePayment` return `authorized` untuk PENDING invoice (auto-authorize) — order tetap ter-create sebelum user bayar
- `getWebhookActionAndData` return `action: "captured"` + `{ session_id, amount }` untuk PAID

## 🗂️ File Paths Penting

### Backend — Konfigurasi
| Path | Fungsi |
|---|---|
| `apps/backend/medusa-config.ts` | Konfigurasi utama Medusa (module, CORS, dll) |
| `apps/backend/.env` | Environment variables (database, API keys) |
| `apps/backend/package.json` | Dependencies backend |

### Backend — Custom Module
| Path | Fungsi |
|---|---|
| `src/modules/rajaongkir-fulfillment/service.ts` | Service RajaOngkir (calculatePrice, getFulfillmentOptions) |
| `src/modules/rajaongkir-fulfillment/index.ts` | Module definition (ModuleProvider) |
| `src/modules/rajaongkir-fulfillment/package.json` | Module package config |
| `src/modules/xendit-payment/service.ts` | Service Xendit (Xendit API payment integration) |
| `src/modules/xendit-payment/index.ts` | Module definition (ModuleProvider) |
| `src/modules/xendit-payment/package.json` | Module package config |

### Backend — API Routes
| Path | Method | Fungsi |
|---|---|---|
| `src/api/store/rajaongkir/provinces/route.ts` | GET | Daftar provinsi Indonesia (hierarkis) |
| `src/api/store/rajaongkir/cities/[province_id]/route.ts` | GET | Daftar kota/kabupaten by province_id |
| `src/api/store/rajaongkir/districts/[city_id]/route.ts` | GET | Daftar kecamatan by city_id |
| `src/api/store/rajaongkir/subdistricts/[district_id]/route.ts` | GET | Daftar kelurahan by district_id |
| `src/api/store/rajaongkir/cost/route.ts` | POST | Hitung ongkir (origin, destination, weight, courier) |
| `src/api/hooks/payment/route.ts` | POST | Webhook handler untuk Xendit callback |

### Backend — Migration Scripts
| Path | Fungsi |
|---|---|
| `src/migration-scripts/initial-data-seed.ts` | Seed awal: Europe region, produk, shipping |
| `src/migration-scripts/seed-indonesia.ts` | Seed Indonesia: region IDR, stock location Jakarta |
| `src/migration-scripts/seed-shipping-options.ts` | Seed 5 shipping options rajaongkir (calculated) |

### Storefront — Checkout
| Path | Fungsi |
|---|---|
| `apps/storefront/src/modules/checkout/components/shipping-address/index.tsx` | Form alamat pengiriman (dgn cascading dropdown untuk ID) |
| `apps/storefront/src/modules/checkout/components/rajaongkir-address-selector/index.tsx` | Cascading dropdown province→city→district→subdistrict |
| `apps/storefront/src/modules/checkout/components/shipping/index.tsx` | Pilihan shipping method |
| `apps/storefront/src/lib/data/fulfillment.ts` | Fungsi fetch ongkir & shipping (standar Medusa) |
| `apps/storefront/src/lib/data/rajaongkir.ts` | Fungsi fetch RajaOngkir destination & cost |
| `apps/storefront/src/lib/data/cart.ts` | Cart actions (setAddresses dgn metadata subdistrict ID) |
| `apps/storefront/src/lib/data/payment.ts` | Fungsi listCartPaymentMethods |
| `apps/storefront/src/lib/data/cart.ts` | Cart actions (setAddresses dgn metadata subdistrict ID) |
| `apps/storefront/src/lib/constants.tsx` | PaymentInfoMap, isStripeLike, isManual |
| `apps/storefront/src/modules/checkout/components/payment/index.tsx` | UI step pembayaran |
| `apps/storefront/src/modules/checkout/components/payment-container/index.tsx` | Radio item untuk payment method |
| `apps/storefront/src/modules/checkout/components/payment-button/index.tsx` | Tombol Place Order |
| `apps/storefront/src/modules/checkout/components/payment-wrapper/index.tsx` | Wrapper Stripe/Non-Stripe |
| `apps/storefront/src/modules/checkout/components/review/index.tsx` | Review step sebelum order |
| `apps/storefront/src/modules/checkout/components/payment-button/index.tsx` | Tombol Place Order (termasuk XenditPaymentButton) |
| `apps/storefront/src/modules/checkout/components/payment-wrapper/index.tsx` | Wrapper Stripe/Non-Stripe |
| `apps/storefront/src/app/[countryCode]/payment/success/page.tsx` | Halaman setelah redirect Xendit — panggil placeOrder |

## 🏗️ Planned Features

### [Phase 1] Ongkir — RajaOngkir ✅ Selesai (26-27 Mei 2026)
| Komponen | Status |
|---|---|
| Region Indonesia (IDR) | ✅ Selesai |
| Module RajaOngkir Fulfillment | ✅ Selesai |
| API route `/store/rajaongkir/cost` | ✅ Selesai |
| Shipping options (JNE, J&T, SiCepat, POS, TIKI) | ✅ 5 calculated options |
| `calculatePrice` fix (subdistrict ID dari metadata) | ✅ Selesai |
| Origin subdistrict ID (`17596`) | ✅ Selesai |
| Full checkout flow test via API | ✅ Verified — 11 steps end-to-end |
| Storefront checkout ongkir | ⏳ Phase 2 |

### [Phase 2] Ongkir — Storefront Checkout ✅ Selesai (3 Juli 2026) + Cascading Fix

**Perbaikan cascading dropdown (3 Juli 2026):**

| Perubahan | Detail | Files |
|---|---|---|
| **Rewrite cascade** ganti `searchDestination` broken → hierarchical API by ID | `searchDestination` pake keyword search — nyampur data antar provinsi (contoh: search "Jawa Barat" balikin "Lombok Barat" dari NTB). Fix: pake API hierarkis `province → city/{id} → district/{id} → sub-district/{id}` | `rajaongkir.ts`, `rajaongkir-constants.ts`, `rajaongkir-address-selector/index.tsx` |
| **Backend: 4 API route baru** | `GET /store/rajaongkir/provinces`, `cities/:id`, `districts/:id`, `subdistricts/:id` | `provinces/route.ts`, `cities/[province_id]/route.ts`, `districts/[city_id]/route.ts`, `subdistricts/[district_id]/route.ts` |
| **Backend: hapus route lama** | `destination/route.ts` (search) dihapus — tidak akurat | `destination/route.ts` dihapus |
| **Hapus field Company** di semua form address | Shipping address, Add/Edit address modal, Profile billing | `shipping-address/index.tsx`, `add-address.tsx`, `edit-address-modal.tsx`, `profile-billing-address/index.tsx` |
| **Fix hidden inputs profile** | City/province/postal_code hidden inputs sekarang terupdate saat pilih subdistrict | `add-address.tsx`, `edit-address-modal.tsx`, `profile-billing-address/index.tsx` |
| **Hapus `INDONESIAN_PROVINCES` statis** | Daftar provinsi di-fetch live dari RajaOngkir API | `rajaongkir-constants.ts` |

### [Phase 3] Payment — Xendit ✅ Selesai (2 Juni 2026)
| Komponen | Status |
|---|---|
| Module Xendit Payment (`xendit-payment/service.ts`) | ✅ Selesai |
| Webhook handler (`hooks/payment/route.ts`) — emit ke event bus | ✅ Selesai |
| `XENDIT_API_KEY` di `.env` | ✅ Selesai |
| Register payment module di `medusa-config.ts` | ✅ Selesai |
| Seed — `pp_xendit` di region Indonesia | ✅ Selesai |
| Storefront — `paymentInfoMap` untuk Xendit | ✅ Selesai |
| `XenditPaymentButton` — redirect ke Xendit checkout | ✅ Selesai |
| `initiatePaymentSession` dengan `success_url` + `failure_url` | ✅ Selesai |
| `payment/success` page — placeOrder setelah redirect Xendit | ✅ Selesai |
| Full checkout flow + webhook capture | ✅ **Verified** (curl) |

### [Phase 4] Product Detail — Image Carousel ✅ Selesai (4 Juli 2026)

**Masalah:** Gambar di halaman detail produk tampil vertical kebawah (`flex flex-col`). Tidak ada carousel/slider.

**Rencana Eksekusi (4 steps):**

| Step | Task | Detail | Status |
|---|---|---|---|
| **4a** | Install Embla Carousel | `embla-carousel-react@^8.6.0` sudah di `package.json` | ✅ |
| **4b** | Rewrite `ImageGallery` | Carousel + thumbnail strip, arrow prev/next, dot navigation, `object-contain` | ✅ |
| **4c** | Update template layout | ImageGallery sudah di-render di template dengan grid layout | ✅ |
| **4d** | Test visual | Buka halaman detail produk dengan ≥2 gambar | ⏳ Test manual |

### [Phase 5] Admin — Image Upload + URL Paste ✅ Selesai (4 Juli 2026)

**Masalah:** Default `@medusajs/dashboard` cuma support file upload (drag-drop/browse). Tidak ada opsi paste URL gambar.

**Rencana Eksekusi (3 steps):**

| Step | Task | Detail | Status |
|---|---|---|---|
| **5a** | Konfigurasi file storage | Tambah `@medusajs/file-local` di `medusa-config.ts` | ✅ |
| **5b** | Custom admin widget — Paste URL | `src/admin/widgets/product-image-url.tsx` — input text + Add Image, list current images + remove | ✅ |
| **5c** | Test flow | Login admin → buka product edit → paste URL → verify | ⏳ Test manual |

## ⏱️ Timeout & Perintah Manual

Beberapa perintah butuh waktu lama dan bisa melebihi timeout agent (default 2 menit). Jika terjadi timeout, jalankan manual di terminal WSL terpisah:

| Perintah | Estimasi Waktu | Keterangan |
|---|---|---|
| `yarn install` (root) | 5-45 menit | Install dependencies backend |
| `yarn install` (storefront) | 5-15 menit | Install dependencies storefront |
| `yarn workspace @dtc/backend medusa db:migrate` | 2-10 menit | Migrasi database + seed otomatis |
| `yarn backend:dev` | Persistent | Jalankan backend — jalan terus |
| `yarn medusa build` | 2-5 menit | Build production |
| `yarn workspace @dtc/storefront add embla-carousel-react` | 1-2 menit | Install library carousel |

Cara menjalankan di terminal terpisah:
```bash
cd /mnt/c/xampp/htdocs/medusa_totti
yarn backend:dev
```

## 🧪 Scripts Berguna

| Perintah | Fungsi |
|---|---|
| `yarn backend:dev` | Jalankan backend (dev mode) |
| `yarn workspace @dtc/backend medusa db:migrate` | Migrasi database + seed |
| `yarn workspace @dtc/backend medusa db:create` | Buat database |
| `cd apps/backend && yarn medusa user -e <email> -p <password>` | Buat admin user |
| `curl -H "x-publishable-api-key: KEY" http://localhost:9000/store/rajaongkir/provinces` | Test API daftar provinsi |
| `curl -H "x-publishable-api-key: KEY" http://localhost:9000/store/rajaongkir/cities/5` | Test API kota by province_id (5=Jawa Barat) |
| `curl -H "x-publishable-api-key: KEY" http://localhost:9000/store/rajaongkir/districts/137` | Test API kecamatan by city_id (137=Jakarta Pusat) |
| `curl -H "x-publishable-api-key: KEY" http://localhost:9000/store/rajaongkir/subdistricts/1340` | Test API kelurahan by district_id (1340=Cempaka Putih) |
| `curl -X POST -H "Content-Type: application/json" -H "x-publishable-api-key: KEY" -d '{"origin":"17596","destination":"17473","weight":1000,"courier":"jne"}' http://localhost:9000/store/rajaongkir/cost` | Test API cost |
| Lihat AGENTS.md "Full Checkout Flow Test" | Full flow: register → login → cart → shipping → payment → order |
| `yarn workspace @dtc/storefront add embla-carousel-react` | Install Embla Carousel untuk product detail |

### Test API RajaOngkir (Hierarkis — by ID)
```bash
# 1. Daftar provinsi
curl "http://localhost:9000/store/rajaongkir/provinces" \
  -H "x-publishable-api-key: pk_test_..."

# 2. Kota by province_id (contoh: DKI Jakarta → id=10)
curl "http://localhost:9000/store/rajaongkir/cities/10" \
  -H "x-publishable-api-key: pk_test_..."

# 3. Kecamatan by city_id (contoh: Jakarta Pusat → id=137)
curl "http://localhost:9000/store/rajaongkir/districts/137" \
  -H "x-publishable-api-key: pk_test_..."

# 4. Kelurahan by district_id (contoh: Cempaka Putih → id=1340)
curl "http://localhost:9000/store/rajaongkir/subdistricts/1340" \
  -H "x-publishable-api-key: pk_test_..."
```

### Full Checkout Flow Test (via curl)
```bash
# 1. Register
curl -X POST http://localhost:9000/auth/customer/emailpass/register \
  -H "Content-Type: application/json" \
  -d '{"email":"test@ex.com","password":"Test123!"}'

# 2. Login → simpan TOKEN
TOKEN=$(curl -s -X POST http://localhost:9000/auth/customer/emailpass \
  -H "Content-Type: application/json" \
  -d '{"email":"test@ex.com","password":"Test123!"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")

# 3. Buat cart (region Indonesia → reg_01KSE5694RPHMRDX9M1N2CMXQ1)
curl -X POST http://localhost:9000/store/carts \
  -H "x-publishable-api-key: $PK" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"region_id":"reg_01KSE5694RPHMRDX9M1N2CMXQ1"}'

# 4. Add item (ganti CART_ID)
curl -X POST http://localhost:9000/store/carts/{CART_ID}/line-items \
  -H "x-publishable-api-key: $PK" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"variant_id":"variant_...","quantity":1}'

# 5. Set alamat dgn subdistrict_id
curl -X POST http://localhost:9000/store/carts/{CART_ID} \
  -H "x-publishable-api-key: $PK" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"shipping_address":{"first_name":"Test","last_name":"User","address_1":"Jl. Contoh","city":"Jakarta Pusat","country_code":"id","postal_code":"10110","phone":"08123456789","metadata":{"rajaongkir_subdistrict_id":"17596"}}}'

# 6. Add shipping method (JNE)
curl -X POST http://localhost:9000/store/carts/{CART_ID}/shipping-methods \
  -H "x-publishable-api-key: $PK" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"option_id":"so_...jne..."}'

# 7. Payment collection
PC=$(curl -s -X POST http://localhost:9000/store/payment-collections \
  -H "x-publishable-api-key: $PK" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"cart_id":"{CART_ID}"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['payment_collection']['id'])")

# 8. Payment session
curl -X POST http://localhost:9000/store/payment-collections/{PC_ID}/payment-sessions \
  -H "x-publishable-api-key: $PK" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"provider_id":"pp_system_default","data":{}}'

# 9. Complete order
curl -X POST http://localhost:9000/store/carts/{CART_ID}/complete \
  -H "x-publishable-api-key: $PK" \
  -H "Authorization: Bearer $TOKEN"
```

## 🔗 Referensi

- [MedusaJS Docs](https://docs.medusajs.com/)
- [Next.js Docs](https://nextjs.org/docs)
- [RajaOngkir API Docs](https://rajaongkir.com/docs)
- [Xendit API Docs](https://docs.xendit.co/)
- [Xendit API Reference](https://developers.xendit.co/api-reference/)

---

### 📦 Archived: Komerce Payment API (dipakai jika nanti migrasi dari Xendit)

Komerce Payment API adalah payment gateway dari platform yang sama dengan RajaOngkir (komerce.id).  
Docs: https://rajaongkir.com/docs/payment-api/getting-started/getting-started

| Item | Detail |
|---|---|
| **Base URL (Sandbox)** | `https://api-sandbox.collaborator.komerce.id/user` |
| **Base URL (Production)** | `https://api.collaborator.komerce.id/user` |
| **Auth** | `x-api-key` — **sama dengan API key RajaOngkir** |
| **Payment Methods** | VA (BCA, BNI, BRI, Mandiri, Permata, CIMB, BSI, BJB) + QRIS |
| **Pricing** | VA: Rp 4.440 flat/trx, QRIS: 0.99% |
| **SDK Node.js** | Tidak ada (pakai raw HTTP) |

**Endpoints:**
| Method | Endpoint | Fungsi |
|---|---|---|
| GET | `/api/v1/user/methods` | Daftar metode pembayaran |
| POST | `/api/v1/user/payment/create` | Buat transaksi VA atau QRIS |
| GET | `/api/v1/user/payment/status/{payment_id}` | Cek status pembayaran |
| POST | `/api/v1/user/payment/cancel` | Batalkan pembayaran pending |
| POST | `/callback` | Webhook callback dari payment gateway |

**Alur:**
1. `GET /methods` → tampilkan pilihan bank/QRIS
2. `POST /payment/create` → dapat VA number atau QR string
3. Customer bayar via bank transfer / scan QRIS
4. `GET /status/{id}` atau webhook → konfirmasi pembayaran
