Bab 13 · Developer API
API Penjualan (Sales)
Endpoint /sales beserta item dan pembayaran yang menyatu, cara membaca blok amounts, ledger pemasukan /sales/payments, dan pekerjaan faset /sales/facets.
Ini domain yang paling sering dipakai. Satu objek sale = satu transaksi penjualan, sudah lengkap dengan item dan pembayarannya — Anda tidak perlu request tambahan per transaksi.
GET /sales
Parameter lengkap ada di Referensi Endpoint. Yang paling sering dipakai:
# semua perubahan sejak polling terakhir, termasuk penghapusan
curl -H "Authorization: Bearer $KEY" \
"$BASE/sales?updated_since=2026-08-13T05:30:12.000Z&include_deleted=true&limit=200"
# backfill satu bulan untuk satu cabang
curl -H "Authorization: Bearer $KEY" \
"$BASE/sales?transaction_date_from=2026-07-31T17:00:00Z&transaction_date_to=2026-08-31T16:59:59Z&branch_id=2"
Contoh objek
{
"id": 18234,
"branch": { "id": 2, "code": "PST", "name": "Cabang Pusat" },
"sales_order_number": "SO-PST-1042",
"invoice_number": "INV-PST-987",
"is_point_of_sale": false,
"transaction_date": "2026-08-13T02:11:40.000Z",
"invoice_date": "2026-08-13T05:30:12.000Z",
"status": "COMPLETED",
"canceled_reason": null,
"customer": { "id": 511, "name": "Budi Santoso" },
"salesperson": { "id": 7, "name": "Dewi" },
"memo": null,
"amounts": {
"sub_total": 1500000,
"item_discount_total": 50000,
"sale_discount": 100000,
"total": 1350000,
"insurance_total": 300000,
"net_total": 1050000,
"paid_total": 1050000,
"outstanding": 0
},
"items": [
{
"id": 90101,
"item_id": 3321,
"name": "Frame CONTOH X-01 Black",
"brand": "CONTOH",
"category": "Frame",
"quantity": 1,
"unit_price": 1500000,
"discount": 50000,
"line_total": 1450000
}
],
"payments": [
{
"id": 44210,
"type": "ASSURANCE",
"amount": 300000,
"paid_at": "2026-08-13T02:11:40.000Z",
"bank": null,
"insurance": { "id": 3, "name": "BPJS Kesehatan", "card_number": "00012345" }
},
{
"id": 44211,
"type": "QRIS",
"amount": 1050000,
"paid_at": "2026-08-13T05:30:12.000Z",
"bank": { "id": 1, "name": "BCA" },
"insurance": null
}
],
"created_at": "2026-08-13T02:11:40.000Z",
"updated_at": "2026-08-13T05:30:12.000Z",
"deleted_at": null
}
Field header transaksi
| Field | Tipe | Arti |
|---|---|---|
id | int | Kunci utama. Pakai ini untuk upsert. |
branch | objek | Cabang tempat transaksi dibuat |
sales_order_number | string | Format SO-{kode cabang}-{urutan}; menjadi POS-... untuk transaksi kasir langsung |
invoice_number | string / null | Format INV-{kode cabang}-{urutan}; null selama belum difakturkan |
is_point_of_sale | bool | true untuk transaksi kasir langsung (tanpa pesanan) |
transaction_date | datetime | Tanggal order — dasar rekap penjualan |
invoice_date | datetime / null | Tanggal faktur terbit |
status | enum | OPEN, COMPLETED, atau CANCELED |
canceled_reason | string / null | Alasan pembatalan |
customer | objek / null | Pelanggan; null untuk transaksi tanpa member |
salesperson | objek | Pengguna Lensiro yang membuat transaksi |
memo | string / null | Catatan bebas dari kasir |
created_at / updated_at | datetime | Waktu dibuat / terakhir diubah |
deleted_at | datetime / null | Terisi berarti transaksi sudah dihapus |
Arti status:
| Nilai | Arti |
|---|---|
OPEN | Sudah dibuat, belum difakturkan — pesanan masih berjalan |
COMPLETED | Sudah difakturkan, invoice_number terisi |
CANCELED | Dibatalkan; alasannya di canceled_reason |
Field item
| Field | Arti |
|---|---|
id | Id baris item, bukan id barang |
item_id | Id barang di katalog — sambungkan ke /warehouse/items |
name, brand, category | Nama, merek, dan kategori barang |
quantity | Jumlah |
unit_price | Harga satuan |
discount | Diskon untuk seluruh baris, bukan per unit |
line_total | unit_price × quantity − discount |
Field pembayaran
| Field | Arti |
|---|---|
type | CASH, DEBIT, CREDIT, QRIS, ASSURANCE, POINT, REFUND |
amount | Nominal. REFUND bernilai negatif, jadi penjumlahan biasa sudah benar |
paid_at | Waktu pembayaran diterima |
bank | Bank untuk transaksi kartu/transfer, kalau ada |
insurance | Penjamin untuk tipe ASSURANCE, berikut card_number |
Membaca blok amounts
Semua angka sudah dihitung server memakai rumus yang sama persis dengan tampilan di aplikasi, jadi Anda tidak perlu menghitung ulang.
| Field | Rumus |
|---|---|
sub_total | Jumlah unit_price × quantity seluruh item — sebelum diskon apa pun |
item_discount_total | Jumlah discount seluruh item |
sale_discount | Diskon tingkat transaksi, bukan per item |
total | sub_total − item_discount_total − sale_discount |
insurance_total | Jumlah pembayaran bertipe ASSURANCE — inilah "potongan BPJS/asuransi" |
net_total | total − insurance_total — yang harus dibayar pelanggan sendiri |
paid_total | Jumlah seluruh pembayaran dikurangi insurance_total |
outstanding | net_total − paid_total — sisa tagihan |
Contoh angka dari objek di atas:
sub_total 1.500.000 (1 frame x Rp 1.500.000)
item_discount_total 50.000
sale_discount 100.000
-----------
total 1.350.000
insurance_total 300.000 (BPJS menanggung sebagian)
-----------
net_total 1.050.000 <- tagihan ke pelanggan
paid_total 1.050.000 (QRIS)
-----------
outstanding 0 <- lunas
Untuk laporan omzet, pakai
net_total(pendapatan yang benar-benar ditagihkan ke pelanggan) atautotal(nilai penjualan sebelum klaim asuransi) — pilih salah satu dan konsisten.outstandinglebih besar dari nol berarti piutang.
Sub-resource satu transaksi
| Endpoint | Isi |
|---|---|
GET /sales/{id} | Satu transaksi, bentuk persis sama dengan elemen list |
GET /sales/{id}/items | Hanya array items, dibungkus { "data": [...] } |
GET /sales/{id}/payments | Hanya array payments, dibungkus { "data": [...] } |
GET /sales/{id}/facets | Pekerjaan faset transaksi itu, berpaginasi |
Semuanya membalas 404 not_found kalau transaksi tidak ada atau sudah dihapus.
Endpoint per-id ditujukan untuk pencarian satu-satu, misalnya menampilkan detail di layar Anda. Untuk tarik data massal, selalu pakai list
/sales— item dan pembayaran sudah ikut di dalamnya, jadi menelusuri per id hanya memboroskan kuota rate limit.
GET /sales/payments — ledger pemasukan
Satu baris per pembayaran, bukan per transaksi. Ini padanan langsung Laporan Pemasukan di aplikasi, dan bentuk yang paling nyaman untuk rekap arus kas: cukup jumlahkan per type per hari di sisi Anda.
curl -H "Authorization: Bearer $KEY" \
"$BASE/sales/payments?paid_from=2026-08-01T17:00:00Z&type=QRIS"
{
"id": 44211,
"sale_id": 18234,
"sales_order_number": "SO-PST-1042",
"invoice_number": "INV-PST-987",
"branch": { "id": 2, "code": "PST", "name": "Cabang Pusat" },
"type": "QRIS",
"amount": 1050000,
"paid_at": "2026-08-13T05:30:12.000Z",
"cashier": { "id": 7, "name": "Dewi" },
"bank": { "id": 1, "name": "BCA" },
"insurance": null,
"created_at": "2026-08-13T05:30:12.000Z",
"updated_at": "2026-08-13T05:30:12.000Z",
"deleted_at": null
}
Bedanya dengan payments yang tertanam di objek sale: di sini setiap baris membawa konteks transaksinya (nomor SO, faktur, cabang) dan kasir yang menerima, serta bisa langsung difilter per tanggal bayar dan per metode.
Uang masuk bukan omzet. Satu transaksi bisa dibayar dicicil lintas hari, dan tanggal bayar bisa jauh berbeda dari tanggal order. Untuk laporan penjualan pakai
/sales; untuk laporan kas pakai/sales/payments. Ingat juga bahwa pembayaranASSURANCEbukan uang tunai yang masuk hari itu, danPOINTbukan uang sama sekali — pisahkan keduanya kalau Anda merekap kas.
GET /sales/facets — pekerjaan faset (Fasset)
Cerminan laporan Fasset: pekerjaan pemasangan dan pemotongan lensa ke frame.
curl -H "Authorization: Bearer $KEY" "$BASE/sales/facets?status=FAILED"
{
"id": 5521,
"sale_id": 18234,
"sales_order_number": "SO-PST-1042",
"invoice_number": "INV-PST-987",
"branch": { "id": 2, "code": "PST", "name": "Cabang Pusat" },
"status": "SUCCESS",
"order_date": "2026-08-13T02:11:40.000Z",
"worked_at": "2026-08-13T04:02:00.000Z",
"customer_name": "Budi Santoso",
"worker_name": "Agus",
"memo": null,
"created_at": "2026-08-13T04:02:00.000Z",
"updated_at": "2026-08-13T06:00:00.000Z",
"deleted_at": null
}
| Field | Arti |
|---|---|
status | WORKING (dikerjakan), SUCCESS (berhasil), FAILED (gagal atau pecah) |
order_date | Tanggal transaksi induk |
worked_at | Tanggal pekerjaan dicatat |
worker_name | Pengguna Lensiro yang mengerjakan |
memo | Catatan; untuk FAILED biasanya berisi penyebab kegagalan |
Nilai komisi pekerjaan sengaja tidak diekspos karena termasuk data penggajian internal.
Hal-hal yang sering menjebak
- Nomor SO bukan kunci penyimpanan. Untuk upsert pakai
id; nomor SO dan faktur untuk ditampilkan ke manusia. invoice_numberdaninvoice_datebernilainullsampai transaksi difakturkan. Kalau rekap Anda memerlukan nomor faktur, saringstatus = "COMPLETED".- Transaksi bisa berubah setelah pertama Anda lihat: pembayaran menyusul, faktur terbit belakangan, item diperbaiki, atau transaksi dibatalkan. Selalu upsert.
- Diskon per item adalah nilai seluruh baris, bukan per unit.
- Data HPP / harga modal dan resep kacamata sengaja tidak ada di v1 — lihat Batasan, Versi & Rencana.

