diff --git a/.gitignore b/.gitignore
index c0a210e..73bb7b3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,4 @@ agen/profile/
payment/paydisini/api_control.php
config/connect.php
config/whatsapp_api.php
-whapi_webhook/
+whapi_webhook/*.log
diff --git a/config/cronjob_tagihan.log b/config/cronjob_tagihan.log
index b43e605..7d89c7a 100644
--- a/config/cronjob_tagihan.log
+++ b/config/cronjob_tagihan.log
@@ -1 +1 @@
-NO ACTION => 2 <-> cc.net Limit_Tgl_5
NO ACTION => 3 <-> nsn utama Limit_Tgl_10
NO ACTION => 3 <-> ciangsana Limit_Tgl_15
NO ACTION => 4 <-> fast.net Limit_Tgl_12
NO ACTION => 5 <-> bima net Limit Tagihan Tgl 15
NO ACTION => 6 <-> kingnet Limit Tgl 15
NO ACTION => 8 <-> diamond.net Limit_Tgl_15
NO ACTION => 9 <-> 22 p.net Limit_Tgl_15
NO ACTION => 10 <-> nettizen Tagihan Limit tgl 15
NO ACTION => 12 <-> Tagihan_Limit_Tgl5
NO ACTION => 15 <-> Tag_NTA2 Limit Tgl 5
NO ACTION => 16 <-> Tagihan Limit Tgl 5
BUAT TAGIHAN => 13 <-> Limit Tanggal 26 (Infinnet) Start : 2025-07-04 - Last : 2025-07-09
REMAND => 18 <-> BUM-Limit-Tgl-5
NO ACTION => 14 <-> Kh_Limit Tanggal 25
KIRIM TAGIHAN => 17 <-> Tagihan Limit Tgl 10
NO ACTION => 17 <-> Tagihan Limit Tgl 15
NO ACTION => 20 <-> IVVI-10
NO ACTION => 20 <-> IVVI-20
NO ACTION => 20 <-> IVVI-30
BUAT TAGIHAN => 21 <-> QONNET Start : 2025-07-02 - Last : 2025-07-05
NO ACTION => 21 <-> QONNET2
NO ACTION => 25 <-> Tagihan Limit Tgl 5
REMAND => 23 <-> Limit Tanggal 5
NO ACTION => 26 <-> Limit Tanggal 10
NO ACTION => 24 <-> Tagihan30
BUAT TAGIHAN => 23 <-> limit tanggal 19 Start : 2025-06-27 - Last : 2025-07-14
KIRIM TAGIHAN => 31 <-> 23-ISOLIR
NO ACTION => 33 <-> usolir-10
NO ACTION => 37 <-> paket 140 ribu
REMAND => 28 <-> PAKET-100K
REMAND => 28 <-> PAKET-150K
REMAND => 28 <-> PAKET-200K
REMAND => 28 <-> PAKET-300K
KIRIM TAGIHAN => 42 <-> meldina-20
BUAT TAGIHAN => 41 <-> lippy-20 Start : 2025-07-04 - Last : 2025-07-09
BUAT TAGIHAN => 43 <-> Tagihan_Limit_tgl_13 Start : 2025-07-01 - Last : 2025-07-06
REMAND => 46 <-> Limit_Tgl_5
NO ACTION => 35 <-> PAKET 1 DEVICE
NO ACTION => 35 <-> PAKET 2 DEVICE
NO ACTION => 35 <-> PAKET 3 DEVICE
NO ACTION => 35 <-> PAKET 4 DEVICE
NO ACTION => 35 <-> PAKET 5 DEVICE [1/80]
NO ACTION => 35 <-> PAKET 5 DEVICE [1/70]
NO ACTION => 35 <-> PAKET 6 DEVICE [1/80]
NO ACTION => 35 <-> PAKET 6 DEVICE [1/70]
NO ACTION => 35 <-> PAKET 7 DEVICE
NO ACTION => 35 <-> PAKET 8 DEVICE
BUAT TAGIHAN => 44 <-> Isolir_20 Start : 2025-07-04 - Last : 2025-07-07
BUAT TAGIHAN => 53 <-> PAKET BRONZE Start : 2025-07-01 - Last : 2025-07-06
KIRIM TAGIHAN => 54 <-> Muhamad rouf
BUAT TAGIHAN => 53 <-> PAKET PLATINUM 5MBs Start : 2025-07-01 - Last : 2025-07-06
BUAT TAGIHAN => 53 <-> PAKET GLOD 7 MBs Start : 2025-07-01 - Last : 2025-07-06
BUAT TAGIHAN => 53 <-> PAKET GLOD+ 10 MBs Start : 2025-07-01 - Last : 2025-07-06
NO ACTION => 59 <-> Isolir_21
BUAT TAGIHAN => 46 <-> Limit tgl 30 Start : 2025-06-29 - Last : 2025-07-24
NO ACTION => 61 <-> Isolir_20
BUAT TAGIHAN => 55 <-> Dcn_CO_Isolir Start : 2025-06-28 - Last : 2025-07-24
REMAND => 62 <-> limit tanggal 5
NO ACTION => 21 <-> VIP-BAGUS
REMAND => 55 <-> Dcn_GB_Isolir
BUAT TAGIHAN => 49 <-> TAGIHAN TANGGAL 20 Start : 2025-06-29 - Last : 2025-07-18
\ No newline at end of file
+NO ACTION => 2 <-> cc.net Limit_Tgl_5
NO ACTION => 3 <-> nsn utama Limit_Tgl_10
NO ACTION => 3 <-> ciangsana Limit_Tgl_15
NO ACTION => 4 <-> fast.net Limit_Tgl_12
NO ACTION => 5 <-> bima net Limit Tagihan Tgl 15
NO ACTION => 6 <-> kingnet Limit Tgl 15
NO ACTION => 8 <-> diamond.net Limit_Tgl_15
NO ACTION => 9 <-> 22 p.net Limit_Tgl_15
NO ACTION => 10 <-> nettizen Tagihan Limit tgl 15
NO ACTION => 12 <-> Tagihan_Limit_Tgl5
NO ACTION => 15 <-> Tag_NTA2 Limit Tgl 5
NO ACTION => 16 <-> Tagihan Limit Tgl 5
BUAT TAGIHAN => 13 <-> Limit Tanggal 26 (Infinnet) Start : 2025-07-04 - Last : 2025-07-09
REMAND => 18 <-> BUM-Limit-Tgl-5
NO ACTION => 14 <-> Kh_Limit Tanggal 25
KIRIM TAGIHAN => 17 <-> Tagihan Limit Tgl 10
NO ACTION => 17 <-> Tagihan Limit Tgl 15
NO ACTION => 20 <-> IVVI-10
NO ACTION => 20 <-> IVVI-20
NO ACTION => 20 <-> IVVI-30
BUAT TAGIHAN => 21 <-> QONNET Start : 2025-07-02 - Last : 2025-07-05
NO ACTION => 21 <-> QONNET2
NO ACTION => 25 <-> Tagihan Limit Tgl 5
REMAND => 23 <-> Limit Tanggal 5
NO ACTION => 26 <-> Limit Tanggal 10
NO ACTION => 24 <-> Tagihan30
BUAT TAGIHAN => 23 <-> limit tanggal 19 Start : 2025-06-27 - Last : 2025-07-14
KIRIM TAGIHAN => 31 <-> 23-ISOLIR
NO ACTION => 33 <-> usolir-10
NO ACTION => 37 <-> paket 140 ribu
REMAND => 28 <-> PAKET-100K
REMAND => 28 <-> PAKET-150K
REMAND => 28 <-> PAKET-200K
REMAND => 28 <-> PAKET-300K
KIRIM TAGIHAN => 42 <-> meldina-20
BUAT TAGIHAN => 41 <-> lippy-20 Start : 2025-07-04 - Last : 2025-07-09
BUAT TAGIHAN => 43 <-> Tagihan_Limit_tgl_13 Start : 2025-07-01 - Last : 2025-07-06
REMAND => 46 <-> Limit_Tgl_5
NO ACTION => 35 <-> PAKET 1 DEVICE
NO ACTION => 35 <-> PAKET 2 DEVICE
NO ACTION => 35 <-> PAKET 3 DEVICE
NO ACTION => 35 <-> PAKET 4 DEVICE
NO ACTION => 35 <-> PAKET 5 DEVICE [1/80]
NO ACTION => 35 <-> PAKET 5 DEVICE [1/70]
NO ACTION => 35 <-> PAKET 6 DEVICE [1/80]
NO ACTION => 35 <-> PAKET 6 DEVICE [1/70]
NO ACTION => 35 <-> PAKET 7 DEVICE
NO ACTION => 35 <-> PAKET 8 DEVICE
BUAT TAGIHAN => 44 <-> Isolir_20 Start : 2025-07-04 - Last : 2025-07-07
BUAT TAGIHAN => 53 <-> PAKET BRONZE Start : 2025-07-01 - Last : 2025-07-06
KIRIM TAGIHAN => 54 <-> Muhamad rouf
BUAT TAGIHAN => 53 <-> PAKET PLATINUM 5MBs Start : 2025-07-01 - Last : 2025-07-06
BUAT TAGIHAN => 53 <-> PAKET GLOD 7 MBs Start : 2025-07-01 - Last : 2025-07-06
BUAT TAGIHAN => 53 <-> PAKET GLOD+ 10 MBs Start : 2025-07-01 - Last : 2025-07-06
NO ACTION => 59 <-> Isolir_21
BUAT TAGIHAN => 46 <-> Limit tgl 30 Start : 2025-06-29 - Last : 2025-07-24
\ No newline at end of file
diff --git a/load/koneksi_off.log b/load/koneksi_off.log
index db0e45a..d680bfc 100644
--- a/load/koneksi_off.log
+++ b/load/koneksi_off.log
@@ -1,23 +1,23 @@
SV_Cikembar_Utama Data pelanggan tidak ada
-2025-07-05 15:29:01SV_BojongKerta Data pelanggan tidak ada
-2025-07-05 15:29:01SV_Permata_Sampora Data pelanggan tidak ada
-2025-07-05 15:29:01SV_Kingnet_Cibadak Data pelanggan tidak ada
-2025-07-05 15:29:01SV_CC-Net_Cipicung Data pelanggan tidak ada
-2025-07-05 15:29:01SV_Nusaina_Cibadak Data pelanggan tidak ada
-2025-07-05 15:29:01SV_Diamond Net Status koneksi tidak aktif atau interval tidak tersedia.
+2025-07-05 15:44:01SV_BojongKerta Data pelanggan tidak ada
+2025-07-05 15:44:01SV_Permata_Sampora Data pelanggan tidak ada
+2025-07-05 15:44:01SV_Kingnet_Cibadak Data pelanggan tidak ada
+2025-07-05 15:44:01SV_CC-Net_Cipicung Data pelanggan tidak ada
+2025-07-05 15:44:01SV_Nusaina_Cibadak Data pelanggan tidak ada
+2025-07-05 15:44:01SV_Diamond Net Status koneksi tidak aktif atau interval tidak tersedia.
SV_22P Net Cibadak Data pelanggan tidak ada
-2025-07-05 15:29:01SV_Nettizen_Segaranten Data pelanggan tidak ada
-2025-07-05 14:59:01SV-DNY-Net-1 Data pelanggan tidak ada
-2025-07-05 15:39:01SV_NTA_2_Pasawahan Status koneksi tidak aktif atau interval tidak tersedia.
+2025-07-05 15:44:01SV_Nettizen_Segaranten Data pelanggan tidak ada
+2025-07-05 15:14:01SV-DNY-Net-1 Data pelanggan tidak ada
+2025-07-05 15:54:01SV_NTA_2_Pasawahan Status koneksi tidak aktif atau interval tidak tersedia.
SV_NTA_1 Status koneksi tidak aktif atau interval tidak tersedia.
SV-Cicukang Status koneksi tidak aktif atau interval tidak tersedia.
RO-Bumdes-Mekarmanah Status koneksi tidak aktif atau interval tidak tersedia.
RO-Pak-Kholiq Data pelanggan tidak ada
-2025-07-05 15:44:01RO-Dendrit-Net Status koneksi tidak aktif atau interval tidak tersedia.
+2025-07-05 15:59:01RO-Dendrit-Net Status koneksi tidak aktif atau interval tidak tersedia.
LJN.NS.CSR.NET Status koneksi tidak aktif atau interval tidak tersedia.
IVVINET-CBD Data pelanggan tidak ada
-2025-07-05 15:29:01QONNET Data pelanggan tidak ada
-2025-07-05 15:29:01Mikrotik_X86 Status koneksi tidak aktif atau interval tidak tersedia.
+2025-07-05 15:44:01QONNET Data pelanggan tidak ada
+2025-07-05 15:44:01Mikrotik_X86 Status koneksi tidak aktif atau interval tidak tersedia.
Mikrotik_BSW_1 Status koneksi tidak aktif atau interval tidak tersedia.
Mikrotik_Alea Status koneksi tidak aktif atau interval tidak tersedia.
STANG Status koneksi tidak aktif atau interval tidak tersedia.
@@ -37,14 +37,14 @@ RO_Wargasari Status koneksi tidak aktif atau interval tidak tersedia.
LJN_AGUNG Status koneksi tidak aktif atau interval tidak tersedia.
RNET-CMS Status koneksi tidak aktif atau interval tidak tersedia.
DCN-GB-CCR Data pelanggan tidak ada
-2025-07-05 15:29:01coba Status koneksi tidak aktif atau interval tidak tersedia.
+2025-07-05 15:44:01coba Status koneksi tidak aktif atau interval tidak tersedia.
412NET Status koneksi tidak aktif atau interval tidak tersedia.
brf Status koneksi tidak aktif atau interval tidak tersedia.
BMSNET Status koneksi tidak aktif atau interval tidak tersedia.
purabaya Status koneksi tidak aktif atau interval tidak tersedia.
DCN-CO-GX4 Data pelanggan tidak ada
-2025-07-05 15:29:01MIKROTIK GX4 Status koneksi tidak aktif atau interval tidak tersedia.
+2025-07-05 15:44:01MIKROTIK GX4 Status koneksi tidak aktif atau interval tidak tersedia.
RO - Netborhood Wifi Status koneksi tidak aktif atau interval tidak tersedia.
Neglasari Status koneksi tidak aktif atau interval tidak tersedia.
SV-DNY-Net-2 Data pelanggan tidak ada
-2025-07-05 15:39:01DNY-Neglasari Status koneksi tidak aktif atau interval tidak tersedia.
+2025-07-05 15:54:01DNY-Neglasari Status koneksi tidak aktif atau interval tidak tersedia.
diff --git a/load/load_multi_cek.log b/load/load_multi_cek.log
index c30400b..b1cb5c9 100644
--- a/load/load_multi_cek.log
+++ b/load/load_multi_cek.log
@@ -1,4 +1,4 @@
+Successfully started background process for IP: 103.26.176.93
Successfully started background process for IP: tunnel.manjapro.net
Successfully started background process for IP: 103.242.104.69
-Successfully started background process for IP: 103.26.176.93
-Started parallel processes for checking vouchers. => 2025-07-05 15:57:01
\ No newline at end of file
+Started parallel processes for checking vouchers. => 2025-07-05 16:12:02
\ No newline at end of file
diff --git a/load/pppoe_update.log b/load/pppoe_update.log
index 4f2b3f5..7511230 100644
--- a/load/pppoe_update.log
+++ b/load/pppoe_update.log
@@ -18,3 +18,31 @@ LJN.NS.CSR.NET =>
IVVINET-CBD =>
QONNET =>
Mikrotik_X86 =>
+Mikrotik_BSW_1 =>
+Mikrotik_Alea =>
+STANG =>
+UNIX-NET =>
+SV_NTA_2_TBL =>
+MULYANAJAYA =>
+PERWASIM.NET =>
+RO_MELDINA =>
+RO_LIPPY =>
+RO_Asep =>
+BATUNUNGGAL =>
+PADINET =>
+RAGILNET =>
+RO_Sukasari =>
+RO_Wargasari =>
+LJN_AGUNG =>
+RNET-CMS =>
+DCN-GB-CCR =>
+412NET =>
+brf =>
+BMSNET =>
+purabaya =>
+DCN-CO-GX4 =>
+MIKROTIK GX4 =>
+RO - Netborhood Wifi =>
+Neglasari =>
+SV-DNY-Net-2 =>
+DNY-Neglasari =>
diff --git a/whapi_webhook/ai/error.log b/whapi_webhook/ai/error.log
new file mode 100644
index 0000000..5ceb07b
--- /dev/null
+++ b/whapi_webhook/ai/error.log
@@ -0,0 +1,8 @@
+2025-05-25 09:22:06 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
+2025-05-25 09:22:16 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
+2025-05-25 09:22:26 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
+2025-05-25 09:22:36 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
+2025-05-30 22:04:51 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
+2025-05-30 22:05:01 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
+2025-05-30 22:05:11 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
+2025-05-30 22:05:21 Exception ResponAI: SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'nomor_whatsapp' at row 1
diff --git a/whapi_webhook/ai/index.php b/whapi_webhook/ai/index.php
new file mode 100644
index 0000000..8cf5b23
--- /dev/null
+++ b/whapi_webhook/ai/index.php
@@ -0,0 +1,111 @@
+ $sender,
+ 'body' => $body,
+ 'apiKey' => $apiKey
+ ];
+
+ $log_file = __DIR__ . "/" . $apiKey . ".log";
+ $log_entry = "[" . date('Y-m-d H:i:s') . "] Masuk: " . print_r($filtered, true);
+
+ // Pastikan kelas ResponAI ada
+ if (!class_exists('ResponAI')) {
+ file_put_contents(__DIR__ . "/error.log", date('Y-m-d H:i:s') . " Class ResponAI tidak ditemukan!\n", FILE_APPEND);
+ http_response_code(500);
+ exit("Server error: ResponAI class missing");
+ }
+
+ $ResAI = new ResponAI();
+
+ // Tangkap exception saat proses respon_chat
+ try {
+ $Respon = $ResAI->respon_chat($body, $sender, $apiKey);
+ } catch (Exception $e) {
+ file_put_contents(__DIR__ . "/error.log", date('Y-m-d H:i:s') . " Exception ResponAI: " . $e->getMessage() . "\n", FILE_APPEND);
+ http_response_code(500);
+ exit("Server error: gagal proses AI");
+ }
+
+ // file_put_contents(__DIR__ . "/step2_respon_ai.log", date('Y-m-d H:i:s') . " " . print_r($Respon, true), FILE_APPEND);
+
+ if (!empty($Respon['id']) && !empty($Respon['nomor'])) {
+ if (!class_exists('PesanWA')) {
+ file_put_contents(__DIR__ . "/error.log", date('Y-m-d H:i:s') . " Class PesanWA tidak ditemukan!\n", FILE_APPEND);
+ http_response_code(500);
+ exit("Server error: PesanWA class missing");
+ }
+
+ $API_WA = new PesanWA();
+
+ try {
+ $send_result = $API_WA->Send_Pesan($Respon['id'], 'sendMessage', array($Respon['nomor']), $Respon['respon']);
+ } catch (Exception $e) {
+ file_put_contents(__DIR__ . "/error.log", date('Y-m-d H:i:s') . " Exception Send_Pesan: " . $e->getMessage() . "\n", FILE_APPEND);
+ http_response_code(500);
+ exit("Server error: gagal kirim pesan");
+ }
+
+ // file_put_contents(__DIR__ . "/step3_send_pesan.log", date('Y-m-d H:i:s') . " Kirim pesan ke: {$Respon['nomor']}\nResponse Send_Pesan: " . print_r($send_result, true), FILE_APPEND);
+
+ $log_entry .= "[" . date('Y-m-d H:i:s') . "] Balasan AI: " . print_r($Respon, true) . PHP_EOL;
+ // file_put_contents($log_file, $log_entry, FILE_APPEND);
+
+ http_response_code(200);
+ exit;
+
+ } else {
+ // file_put_contents(__DIR__ . "/error_send_pesan.log", date('Y-m-d H:i:s') . " Gagal kirim pesan, id atau nomor kosong: " . print_r($Respon, true) . "\n", FILE_APPEND);
+ http_response_code(500);
+ echo "Gagal mengirim pesan: data tidak lengkap.";
+ exit;
+ }
+ } else {
+ http_response_code(400);
+ echo "Data tidak valid atau bukan pesan yang diinginkan.";
+ exit;
+ }
+} catch (Throwable $t) {
+ // Tangkap semua error fatal / throwable
+ file_put_contents(__DIR__ . "/error.log", date('Y-m-d H:i:s') . " Fatal error: " . $t->getMessage() . "\n", FILE_APPEND);
+ http_response_code(500);
+ echo "Server error: " . $t->getMessage();
+ exit;
+}
diff --git a/whapi_webhook/ai/respon_ai.php b/whapi_webhook/ai/respon_ai.php
new file mode 100644
index 0000000..5169a9d
--- /dev/null
+++ b/whapi_webhook/ai/respon_ai.php
@@ -0,0 +1,276 @@
+ ['lambat', 'tidak konek', 'jaringan', 'trouble', 'gangguan', 'internet mati'],
+ 'tagihan' => ['tagihan', 'bayar', 'invoice', 'pembayaran', 'rekening'],
+ 'pemasangan' => ['pasang', 'berlangganan', 'pemasangan', 'registrasi', 'daftar', 'masang'],
+ 'umum' => []
+ ];
+
+ $message = strtolower($message);
+ foreach ($rules as $topik => $keywords) {
+ foreach ($keywords as $keyword) {
+ if (strpos($message, $keyword) !== false) {
+ return $topik;
+ }
+ }
+ }
+ return 'umum';
+ }
+
+ private function get_data_pelanggan($sender) {
+ $pdo = $this->connectDB();
+ $stmt = $pdo->prepare("SELECT nama, nama_paket, status, akun, status_koneksi FROM v_pelanggan WHERE nomor_whatsapp = ?");
+ $stmt->execute([$sender]);
+ $pelanggan = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ if ($pelanggan) {
+ return "Pelanggan atas nama {$pelanggan['nama']} (ID: {$pelanggan['akun']}) menggunakan paket {$pelanggan['nama_paket']} dan status koneksi {$pelanggan['status_koneksi']}. Status langganan: {$pelanggan['status']}.";
+ }
+ return "Data pelanggan tidak ditemukan.";
+ }
+
+ private function get_id_server($apikey_wa) {
+ $pdo = $this->connectDB();
+ $stmt = $pdo->prepare("SELECT id FROM data_server WHERE apikey_whatsapp = ?");
+ $stmt->execute([$apikey_wa]);
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ return $row['id'] ?? null;
+ }
+
+ private function get_paket_berlangganan($id) {
+ $pdo = $this->connectDB();
+ $stmt = $pdo->prepare("SELECT nama, harga_paket FROM profile_paket WHERE id_data_server = ?");
+ $stmt->execute([$id]);
+ $pakets = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+ if (!$pakets) return "Paket berlangganan belum tersedia.";
+ $list = "Berikut adalah daftar paket layanan:\n";
+ foreach ($pakets as $p) {
+ $list .= "- {$p['nama']}: Rp " . number_format($p['harga_paket'], 0, ',', '.') . "\n";
+ }
+ return trim($list);
+ }
+
+ private function haversine($lat1, $lon1, $lat2, $lon2) {
+ $earthRadius = 6371;
+ $dLat = deg2rad($lat2 - $lat1);
+ $dLon = deg2rad($lon2 - $lon1);
+
+ $a = sin($dLat / 2) ** 2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon / 2) ** 2;
+ $c = 2 * atan2(sqrt($a), sqrt(1 - $a));
+ return $earthRadius * $c;
+ }
+
+ private function get_info_pemasangan($id, $lat = null, $lon = null) {
+ $pdo = $this->connectDB();
+
+ $stmt1 = $pdo->prepare("SELECT harga_sewa FROM data_server WHERE id = ?");
+ $stmt1->execute([$id]);
+ $row = $stmt1->fetch(PDO::FETCH_ASSOC);
+ $biaya = isset($row['harga_sewa']) ? "Biaya pemasangan sebesar Rp " . number_format($row['harga_sewa'], 0, ',', '.') . "." : "";
+
+ $stmt2 = $pdo->prepare("SELECT nama FROM master_desa WHERE id_data_server = ?");
+ $stmt2->execute([$id]);
+ $desas = $stmt2->fetchAll(PDO::FETCH_ASSOC);
+ $wilayah = $desas ? implode(", ", array_column($desas, 'nama')) : "Wilayah layanan belum terdaftar.";
+
+ $stmt3 = $pdo->prepare("SELECT titik_koordinat FROM master_topologi WHERE id_data_server = ?");
+ $stmt3->execute([$id]);
+ $koordinats = $stmt3->fetchAll(PDO::FETCH_ASSOC);
+
+ if ($lat !== null && $lon !== null && $koordinats) {
+ foreach ($koordinats as $k) {
+ $raw = $k['titik_koordinat'];
+
+ // Cek apakah tidak kosong dan mengandung koma
+ if (!empty($raw) && strpos($raw, ',') !== false) {
+ $parts = explode(',', $raw);
+
+ // Pastikan hasil explode memiliki dua elemen
+ if (count($parts) === 2) {
+ $latParsed = trim($parts[0]);
+ $lonParsed = trim($parts[1]);
+
+ // Validasi bahwa lat dan lon adalah angka
+ if (is_numeric($latParsed) && is_numeric($lonParsed)) {
+ $distance = $this->haversine($lat, $lon, (float)$latParsed, (float)$lonParsed);
+ if ($distance <= 0.3) {
+ $formulir = "Anda berada dalam jangkauan layanan kami.\n\nSilakan lengkapi data berikut untuk registrasi:\n"
+ . "- Nama lengkap\n"
+ . "- NIK\n"
+ . "- Alamat Lengkap\n"
+ . "- Titik Koordinat\n"
+ . "- Nomor WhatsApp\n"
+ . "- Paket Layanan";
+ return "$biaya\n$formulir";
+ }
+ }
+ }
+ }
+ }
+ return "Mohon maaf, titik koordinat Anda belum terjangkau jaringan kami.";
+ }
+
+ return "$biaya Wilayah layanan mencakup: $wilayah.\n\nSilakan kirimkan titik koordinat lokasi Anda agar kami bisa melakukan pengecekan jangkauan.";
+ }
+
+ private function get_data_tagihan($sender) {
+ $pdo = $this->connectDB();
+ $stmt = $pdo->prepare("SELECT nama, total_tagihan, limit_pembayaran, tanggal_bayar, status FROM v_tagihan WHERE nomor_whatsapp = ? ORDER BY tanggal_bayar DESC");
+ $stmt->execute([$sender]);
+ $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+ if (!$rows) return "Tidak ada tagihan yang ditemukan.";
+ $list = "Riwayat tagihan:\n";
+ foreach ($rows as $row) {
+ $list .= "- Tagihan: Rp " . number_format($row['total_tagihan'], 0, ',', '.') . " | Jatuh Tempo: {$row['limit_pembayaran']} | Status: {$row['status']}\n";
+ }
+ return trim($list);
+ }
+
+ private function simpan_sesi($sender, $topik) {
+ $pdo = $this->connectDB();
+ $stmt = $pdo->prepare("INSERT INTO chat_session (nomor_whatsapp, topik_terakhir) VALUES (?, ?) ON DUPLICATE KEY UPDATE topik_terakhir = VALUES(topik_terakhir)");
+ $stmt->execute([$sender, $topik]);
+ }
+
+ private function ambil_sesi($sender) {
+ $pdo = $this->connectDB();
+ $stmt = $pdo->prepare("SELECT topik_terakhir FROM chat_session WHERE nomor_whatsapp = ?");
+ $stmt->execute([$sender]);
+ $row = $stmt->fetch(PDO::FETCH_ASSOC);
+ return $row['topik_terakhir'] ?? null;
+ }
+
+ private function simpan_log($sender, $role, $pesan, $id) {
+ $pdo = $this->connectDB();
+ $stmt = $pdo->prepare("INSERT INTO chat_log (nomor_whatsapp, role, pesan, id_data_server) VALUES (?, ?, ?, ?)");
+ $stmt->execute([$sender, $role, $pesan, $id]);
+ }
+
+ public function respon_chat($message, $sender, $apikey_wa) {
+ $OPENAI_API_KEY = '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'; // Ganti dengan API key valid
+ $id = $this->get_id_server($apikey_wa);
+
+ if (empty($message) || empty($sender) || !$id) {
+ return [
+ 'id' => $id,
+ 'nomor' => $sender,
+ 'respon' => 'Input tidak valid atau data server tidak ditemukan.'
+ ];
+ }
+
+ $topik = $this->deteksi_topik($message);
+ $topik_sesi = $this->ambil_sesi($sender);
+ if (!$topik && $topik_sesi) {
+ $topik = $topik_sesi;
+ }
+
+ if ($topik) {
+ $this->simpan_sesi($sender, $topik);
+ }
+
+ $this->simpan_log($sender, 'user', $message, $id);
+
+ $pdo = $this->connectDB();
+ $stmt = $pdo->prepare("SELECT role, pesan FROM chat_log WHERE id_data_server = ? AND nomor_whatsapp = ? ORDER BY id DESC LIMIT 10");
+ $stmt->execute([$id, $sender]);
+ $riwayat = array_reverse($stmt->fetchAll(PDO::FETCH_ASSOC));
+
+ $messages = [];
+ foreach ($riwayat as $log) {
+ $messages[] = ['role' => $log['role'], 'content' => $log['pesan']];
+ }
+ $messages[] = ['role' => 'user', 'content' => $message];
+
+ $data_pelanggan = $this->get_data_pelanggan($sender);
+ $info_paket = $this->get_paket_berlangganan($id);
+ $info_pasang = $this->get_info_pemasangan($id);
+ $data_tagihan = $this->get_data_tagihan($sender);
+
+ $roles = [
+ 'gangguan' => 'Kamu adalah customer service penyedia layanan internet untuk masalah teknis.',
+ 'tagihan' => 'Kamu adalah customer service penyedia layanan internet untuk urusan tagihan.',
+ 'pemasangan' => 'Kamu adalah customer service penyedia layanan internet untuk pemasangan baru.',
+ 'umum' => 'Kamu adalah customer service penyedia layanan internet untuk pertanyaan umum.'
+ ];
+
+ $topik_final = $topik ?: 'umum';
+
+ switch ($topik_final) {
+ case 'tagihan':
+ $tambahan = $data_tagihan;
+ break;
+ case 'pemasangan':
+ $tambahan = $info_paket . "\n" . $info_pasang;
+ break;
+ default:
+ $tambahan = '';
+ break;
+ }
+
+ $system_message = $roles[$topik_final] . " Data pelanggan: $data_pelanggan\n$tambahan\nJawaban maksimal 3 kalimat, singkat, sopan, dan informatif.";
+ array_unshift($messages, ['role' => 'system', 'content' => $system_message]);
+
+ $payload = [
+ 'model' => 'gpt-3.5-turbo',
+ 'messages' => $messages,
+ 'temperature' => 0.7,
+ 'max_tokens' => 100
+ ];
+
+ $ch = curl_init('https://api.openai.com/v1/chat/completions');
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [
+ 'Content-Type: application/json',
+ 'Authorization: Bearer ' . $OPENAI_API_KEY,
+ ]);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
+
+ $response = curl_exec($ch);
+
+ if (curl_errno($ch)) {
+ $error = curl_error($ch);
+ curl_close($ch);
+ return [
+ 'id' => $id,
+ 'nomor' => $sender,
+ 'respon' => 'Terjadi kesalahan koneksi ke OpenAI: ' . $error
+ ];
+ }
+
+ curl_close($ch);
+ $data = json_decode($response, true);
+
+ $reply = $data['choices'][0]['message']['content'] ?? 'Maaf, tidak dapat memberikan jawaban saat ini.';
+ $this->simpan_log($sender, 'assistant', $reply, $id);
+
+ $hasil = [
+ 'id' => $id,
+ 'nomor' => $sender,
+ 'respon' => trim($reply)
+ ];
+
+ // Debug log
+ // file_put_contents("debug_respon_chat.log",
+ // "====\n[" . date("Y-m-d H:i:s") . "]\n".
+ // "Payload:\n" . print_r($payload, true) .
+ // "Response:\n" . print_r($data, true) .
+ // "Final:\n" . print_r($hasil, true),
+ // FILE_APPEND
+ // );
+
+ return $hasil;
+ }
+
+}
diff --git a/whapi_webhook/index.php b/whapi_webhook/index.php
new file mode 100644
index 0000000..c4c579e
--- /dev/null
+++ b/whapi_webhook/index.php
@@ -0,0 +1,39 @@
+
\ No newline at end of file