# Porting API transaksi ke CI4

Pembaruan: 16 Oktober 2026.

Tahap ini memindahkan tiga API transaksi inti. Controller `StoreApi` dan layanan `StoreSale` menggunakan koneksi, request, response JSON, dan logger CI4 tanpa include PHP dari folder `pos`.

| Endpoint di bawah `api/` | Method | Fungsi |
| --- | --- | --- |
| `api_create_sale` | POST | Menyimpan penjualan beserta detail, snapshot harga, dan mutasi stok |
| `api_check_stock` | GET/POST | Stok satu SKU, banyak SKU, atau seluruh produk aktif |
| `api_get_invoice` | GET/POST | Mencari invoice melalui invoice_number atau order_number |

Semua endpoint mempunyai alias `.php` dan mendukung preflight OPTIONS (204). Method lain mengembalikan JSON 405. Endpoint ini tidak menggunakan filter login/CSRF browser karena otentikasi memakai API key toko.

## Otentikasi dan kontrak

- Header `X-Api-Key` menjadi prioritas. Alternatif: `api_key` dalam body JSON/form. Endpoint stok/invoice juga menerima key melalui query. API create sale mengikuti sumber: tidak menerima key hanya melalui query.
- Key harus cocok dengan toko aktif pada database. ID toko selalu ditentukan oleh key, bukan sesi login atau field store_id dalam request.
- Respons selalu JSON dengan `success`. Status: 201 untuk penjualan tersimpan; 200 untuk pembacaan berhasil; 400 input tidak valid; 401 key ditolak; 404 data tidak ditemukan; 409 order_number ganda; 422 data produk/referensi tidak dapat digunakan; 500 kegagalan server.
- CORS mengikuti sumber: origin `*`, header `Content-Type, X-Api-Key`, tanpa kredensial cookie. Response memakai `Cache-Control: no-store`.
- Key, token, password, dan secret yang dikirim melalui field body/query disamarkan sebelum pencatatan API log. Log tidak menyalin header API key. Pesan kesalahan database mentah tidak dikembalikan ke caller.

Contoh body create sale:

```json
{
  "order_number": "ORDER-001",
  "customer_name": "Budi",
  "payment_method": "transfer",
  "payment_status": "paid",
  "items": [
    {"sku": "SKU-001", "quantity": 2},
    {"sku": "SKU-002", "quantity": 1, "price": 50000, "discount": 1000}
  ]
}
```

Kirim ke `POST /api/api_create_sale.php` relatif terhadap base URL CI4, dengan `X-Api-Key` toko. Hasil memuat `sale_id`, `invoice_number`, `order_number`, `grand_total`, dan `items_count`. `order_number` yang dikirim juga menjadi invoice_number; bila kosong, invoice API dibuat otomatis.

Field header lain yang didukung: `sale_date`, `customer_id`, `reseller_id`, `marketplace_id`, `shop_id`, `courier_id`, `tracking_number`, `salesman_id`, `discount`, `tax`, `amount_paid`, `source`, `notes`, dan `order_status`. `shop_id` mengalahkan marketplace_id dan harus cocok dengan marketplace aktif milik toko. Source menerima pos/online/reseller/api/pasar. Payment status menerima paid/unpaid/partial; jumlah pembayaran tidak otomatis mengubah status yang diberikan caller.

## Konsistensi transaksi

- Batas request 500 item; quantity wajib bilangan bulat positif. Tanggal harus valid. Harga, diskon, pajak, dan pembayaran wajib nominal nonnegatif; diskon tidak boleh menghasilkan subtotal/total negatif. Harga dan nominal dibulatkan hingga dua desimal, bukan dipotong ke integer seperti sumber.
- Produk dicari melalui SKU **atau barcode**, dibatasi toko dan status aktif. Hasil lebih dari satu produk ditolak; API tidak memilih produk secara acak. Referensi customer/reseller/marketplace/kurir/salesman milik toko lain ditolak 422, memperketat sumber yang mengosongkan beberapa referensi tidak valid.
- Penguncian toko dilakukan sebelum pengecekan nomor pesanan, sehingga dua request pada layanan API ini untuk toko/order yang sama tidak dapat sama-sama lolos. Ini bukan unique constraint global untuk semua writer aplikasi lama yang tidak memakai lock tersebut.
- Produk dikunci ketika dicari. Snapshot diambil dari baris produk yang dikunci, lalu header, detail, dan mutasi stok disimpan dalam satu transaksi. Kegagalan detail/stock menggulung balik keseluruhan penjualan.
- Stok kurang tetap **tidak menolak order**: stok boleh menjadi minus sesuai sumber. Produk pause tetap disimpan sebagai detail penjualan, tetapi tidak menggerakkan stok. Mutasi kurang stok dicatat pada audit dengan konteks toko dari key.
- Sesudah commit, log penjualan, notifikasi stok rendah, webhook stok, antrean Shopee, dan audit bersifat best effort. Kegagalan notifikasi tidak mengubah transaksi yang sudah commit menjadi respons gagal dan memicu retry duplikat.
- Penyesuaian schema kompatibilitas (order_number, tracking_number, source, snapshot, pause, antrean) dilakukan sebelum transaksi saldo/stok. Tabel inti aplikasi tetap menjadi prasyarat.

## Pembacaan stok dan invoice

Stok menerima `sku` sebagai string atau array hingga 500 nilai. Satu SKU menghasilkan object `data`, atau 404. Banyak SKU menghasilkan array `data` serta `not_found` bila ada. Tanpa SKU, respons memuat `total` dan array produk aktif. Lookup sekarang mendukung kolom SKU asli maupun barcode; sumber API stok hanya mencari barcode. Identitas output untuk pencarian mengikuti kode yang diminta; untuk daftar seluruh produk, SKU kosong memakai barcode.

Angka stok tetap merupakan `products.stock`, sesuai kontrak lama; bukan stok efektif bundle, stok yang tersedia setelah reservasi, atau stok marketplace yang sudah disesuaikan. Harga output API stok tetap integer sesuai sumber.

Pencarian invoice dibatasi toko dari key dan mengembalikan invoice_number, order_number, grand_total, status. Jika schema lama belum mempunyai order_number, pencarian berdasarkan invoice tetap bekerja dan pencarian order yang tidak didukung menghasilkan 404.

## Batasan penting

- API create sale ini merupakan importer item sederhana. **Produk bundle ditolak 422 secara eksplisit**, agar stok virtual tidak dikurangi tanpa komponen seperti perilaku sumber. Gunakan transaksi POS yang mendukung bundle, atau kirim komponen sebagai item eksplisit. Ekspansi bundle otomatis melalui API belum diimplementasikan.
- Sesuai sumber, API ini belum membuat alokasi batch FEFO, transaksi deposit, shift kas, maupun reservasi stok pending; jangan menganggapnya setara seluruh alur kasir interaktif.
- Tidak ada webhook inbound baru yang dinyatakan selesai pada tahap ini. Pengiriman webhook stok outbound memakai helper yang sudah tersedia.

## Verifikasi

- PHPUnit seluruh aplikasi: **162 tes, 1.747 assertion**, lulus.
- Termasuk **28 tes API**: route/alias, method/CORS, key header/body/query/form, sesi tidak menggantikan key, JSON rusak, stok minus/pause, SKU ambigu, referensi toko lain, order ganda, rollback, snapshot, shop_id, item berulang, stok/invoice, penyamaran key, dan kegagalan notifikasi setelah commit.
- Lint **303 file PHP**: tanpa kesalahan sintaks.
- Seluruh pengujian memakai koneksi database tiruan; tidak membuat transaksi produksi atau menghubungi marketplace/WhatsApp/webhook nyata. Penguncian diuji melalui query dan simulasi hasil; uji konkurensi dengan MySQL nyata dan uji HTTP/browser staging belum dilakukan.

## Status API lain

`api/notifications` dan `api/switch-outlet` sudah tersedia sebelumnya. Dengan tiga endpoint ini, lima dari dua belas file API sumber mempunyai route CI4. Yang masih perlu dipindahkan: `webhook_shopee_order`, `webhook_jaknot_sync`, `wablas_inbox`, `wablas_tracking`, `whatsapp_chat_api`, `crm_chat`, dan `qris`. Prioritas berikutnya adalah webhook pesanan Shopee, termasuk idempotensi event dan perubahan status pesanan.

## Pembaruan webhook Shopee

Webhook pesanan Shopee sekarang tersedia; lihat `PORTING_WEBHOOK_SHOPEE.md` untuk kontrak, konfigurasi Secret Key wajib, pembatalan, dan hasil verifikasi terbaru. Jumlah route API sumber menjadi enam dari dua belas; daftar tujuh endpoint pada tahap awal di atas berkurang satu.

## Pembaruan webhook Jaknot

Webhook Jaknot sekarang tersedia; lihat `PORTING_WEBHOOK_JAKNOT.md` untuk kontrak token outlet dan aturan snapshot stok/harga modal. Jumlah API sumber dengan route CI4 menjadi tujuh dari dua belas; berikutnya integrasi WhatsApp/Wablas.

## Pembaruan callback Wablas

Inbox dan tracking Wablas sekarang tersedia; lihat `PORTING_WEBHOOK_WABLAS.md` untuk secret wajib, deduplikasi, aturan auto-reply, serta tracking status. Jumlah API sumber dengan route CI4 menjadi sembilan dari dua belas. Tersisa whatsapp_chat_api, crm_chat, dan qris.

## Pembaruan API percakapan

API WhatsApp/CRM dan halaman crm/chat sekarang tersedia; lihat `PORTING_CHAT.md` untuk CSRF, isolasi toko, serta batas pengiriman/riwayat. Jumlah API sumber dengan route CI4 menjadi sebelas dari dua belas. Endpoint tersisa: qris.

## Pembaruan QRIS dan cakupan akhir folder API

API QRIS sekarang tersedia; lihat `PORTING_QRIS.md` untuk validasi payload, nominal, integrasi kasir, dan hasil verifikasi terbaru. Seluruh dua belas file sumber pos/api memiliki route CI4 beserta alias .php. Integrasi nyata dan bagian aplikasi di luar folder API tetap mengikuti batasan masing-masing dokumen.
