# Porting percakapan WhatsApp/CRM ke CI4

Pembaruan: 16 Oktober 2026.

Endpoint `api/whatsapp_chat_api` dan `api/crm_chat`, termasuk alias `.php`, sudah dipindahkan ke controller `ChatApi` dan layanan `ChatConversations`. Halaman `crm/chat` beserta alias `.php` juga tersedia melalui `CrmChat`, memakai tampilan terminal percakapan dari sumber. Tidak ada include runtime PHP dari folder `pos`.

## Akses dan aksi

API memerlukan sesi login serta toko aktif. Kegagalan autentikasi menghasilkan JSON 401; halaman CRM memakai filter auth. Semua respons API menggunakan Cache-Control: no-store dan menyertakan csrf_token terbaru.

| Aksi | Method | Keterangan |
| --- | --- | --- |
| contacts | GET | Daftar kontak, pencarian q |
| messages | GET | Riwayat percakapan berdasarkan customer_id atau phone |
| new_messages | GET | Polling incremental dengan since_id |
| send | POST | Kirim pesan dengan customer_id/phone dan message |
| mark_read | POST | Tandai pesan masuk sebagai dibaca |

POST menerima form maupun JSON dan wajib melewati verifikasi CSRF CI4. Token dapat dikirim melalui header dari csrf_header(), dengan nilai csrf_token respons atau csrf_hash() halaman. Jika token dirotasi sesudah POST, gunakan token terbaru dari respons berikutnya. Origin yang dikirim harus cocok persis dengan origin base URL aplikasi, termasuk scheme dan port. Pastikan base URL sesuai alamat browser/deployment.

Halaman CRM sudah mengirim header CSRF dan memperbarui token dari respons pengiriman. Kegagalan provider atau HTTP ditampilkan, dan teks pesan dikembalikan ke input. Pengecekan GET tidak lagi mengubah is_read; client yang membutuhkan tanda baca harus menggunakan mark_read dengan POST/CSRF. Ini mengubah perilaku WhatsApp API lama yang menandai baca saat mengambil riwayat.

API send menolak GET, sehingga request URL saja tidak bisa mengirim pesan. Kegagalan input menghasilkan 400, CSRF/origin/toko tidak aktif 403, customer tidak ditemukan 404, pengiriman grup tidak didukung 422, dan kesalahan server 500 tanpa rincian SQL.

## Isolasi toko dan kontak

- Toko selalu berasal dari sesi/getStoreId, bukan parameter request. Device ID diambil dari konfigurasi toko aktif.
- Query pesan mensyaratkan store_id cocok **dan** device_id cocok atau null untuk histori milik toko yang sama. Perangkat bersama tidak membuka pesan toko lain. Baris lama store_id null tidak ditampilkan melalui fallback perangkat.
- Customer dan foto/tags dimuat hanya dari toko aktif, dengan pencocokan variasi nomor 62/0/nomor nasional. Customer ambigu tidak dipilih secara acak.
- Customer_id yang tidak ditemukan di toko ditolak 404; phone tambahan tidak dipakai untuk melewati pemeriksaan tersebut.
- Kontak WhatsApp mempertahankan field phone/name/profile_image/last_message/last_time. Kontak CRM memakai id/customer_name/phone/tags/profile_image/last_message/last_message_at dan menyertakan customer aktif yang belum memiliki percakapan.
- Pesan terakhir diambil dari baris MAX(id), tanpa memotong pesan melalui GROUP_CONCAT atau delimiter dalam isi pesan.
- Daftar percakapan terbaru dibatasi 200 nomor; riwayat penuh 500 pesan dan polling 100 pesan per request. Customer aktif tanpa percakapan tetap dapat muncul pada daftar CRM. Tidak ada pagination riwayat lama pada UI tahap ini.
- Device ID yang belum dikonfigurasi menghasilkan daftar kosong dengan informasi konfigurasi; halaman CRM menautkan ke Pengaturan WhatsApp.

## Pengiriman

Nomor dinormalisasi ke bentuk 62xxx dan pesan dikirim melalui helper sendWhatsappMessageWithFallback yang sudah ada, dengan ID toko dan konfigurasi milik toko bila lengkap. Fallback gateway mengikuti perilaku helper sumber; tidak ada kredensial dari body request yang dijadikan konfigurasi pengiriman.

Respons provider status:error/failed tidak dianggap sukses hanya karena string tersebut terisi. Respons API mengembalikan hasil publik dan tidak menyalin payload provider atau kredensial mentah kepada browser. result kini berisi object success minimal; client yang sebelumnya membaca detail payload provider perlu menyesuaikan.

Pengiriman dilakukan langsung sesuai sumber, bukan melalui worker antrean. Tidak ada kunci idempotensi send: caller tidak boleh mengulang kirim otomatis hanya karena respons jaringan tidak terlihat. Pengiriman ke grup belum didukung. Riwayat/media tetap disediakan oleh API, tetapi halaman CRM sumber menampilkan bubble teks; composer upload media belum ditambahkan.

WablasSchema juga menyiapkan kolom is_read pada whatsapp_messages sebelum pembaruan mark_read. Tabel inti toko/customer yang sudah diporting tetap menjadi prasyarat.

## Verifikasi

- PHPUnit seluruh aplikasi: **246 tes, 2.050 assertion**, lulus.
- Termasuk **16 tes chat** untuk route, JSON autentikasi, CSRF/origin, pembatasan toko/perangkat, customer lintas toko, kontrak kontak, delimiter pesan, pencarian tags, mark_read, pengiriman dengan callback tiruan, respons provider gagal, penolakan grup, serta render halaman CRM.
- Lint **318 file PHP**: tanpa kesalahan sintaks. JavaScript halaman CRM juga diperiksa dengan node --check.
- Tidak ada pengiriman WhatsApp nyata atau perubahan data produksi. Tes memakai koneksi database dan pengirim tiruan; uji browser, MySQL nyata, serta gateway staging masih diperlukan.

## Berikutnya

Sebelas dari dua belas file API sumber sekarang mempunyai route CI4. Endpoint yang tersisa adalah qris. Area CRM/admin lain, pengelolaan rule auto-reply, cron, dan worker tetap perlu diaudit sebagai tahap terpisah.
