diff --git a/CHANGELOG.md b/CHANGELOG.md index c24a40c..c307e3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/). +## [0.5.13] — 2026-11-05 — "lunas-notify" + +### Diperbaiki +- Notifikasi WA **LUNAS** memakai gateway aktif (GOWA/Fonnte/Meta/Baileys), bukan hanya Baileys. +- Deteksi koneksi GOWA membaca status device dari `/devices` (array) — sebelumnya dianggap tidak terhubung bila Device ID kosong, sehingga notifikasi lunas dilewati. +- Approve pembayaran kolektor dari APK admin dan pencocokan notifikasi pembayaran manual kini mengirim WA lunas ke pelanggan. + ## [0.5.12] — 2026-11-05 — "gowa-test-unsaved" ### Diperbaiki diff --git a/README.md b/README.md index f4ab3e7..7c82c86 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@

Lisensi: ISC - Versi 0.5.12 (gowa-test-unsaved) + Versi 0.5.13 (lunas-notify) Node.js >= 20 Docker Demo diff --git a/app-customer.js b/app-customer.js index 465fe86..e1e2861 100644 --- a/app-customer.js +++ b/app-customer.js @@ -628,8 +628,8 @@ async function trySendWaPaymentSuccess(settings, invoiceId, methodLabel) { if (!inv) return; const phone = String(inv.customer_phone || '').trim(); if (!phone) return; - const { sendWA, whatsappStatus } = await import('./services/whatsappBot.mjs'); - if (whatsappStatus.connection !== 'open') throw new Error('Bot WhatsApp belum terhubung'); + // Pakai gateway aktif (GOWA/Fonnte/Meta/Baileys), bukan hanya Baileys. + const waNotify = require('./services/waNotifyService'); const appUrl = (settings.public_base_url || '').replace(/\/$/, ''); const portalUrl = appUrl ? `${appUrl}/customer` : ''; @@ -654,7 +654,7 @@ async function trySendWaPaymentSuccess(settings, invoiceId, methodLabel) { }); logger.info(`[WEBHOOK][payment-notif] Sending WA success notif to ${phone} inv=${invoiceId} method=${metode}`); - await sendWA(phone, msg); + await waNotify.sendText(phone, msg); } catch (e) { logger.error(`[WEBHOOK][payment-notif] WA success notif failed: ${e?.message || e}`); } diff --git a/package.json b/package.json index 1353c1d..aed4612 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "open-isp", - "version": "0.5.12", + "version": "0.5.13", "description": "OPEN-ISP \u2014 sistem billing & manajemen ISP open-source (PWA pelanggan, multi-OLT, RADIUS, WhatsApp, QRIS)", "main": "app-customer.js", "scripts": { diff --git a/routes/adminPortal.js b/routes/adminPortal.js index e7fab36..6750f19 100644 --- a/routes/adminPortal.js +++ b/routes/adminPortal.js @@ -4925,6 +4925,29 @@ router.post('/payment-unmatched/:id/match', requireAdminSession, express.urlenco if (!unpaid) await customerSvc.activateCustomer(inv.customer_id); } } catch (e) {} + + // Notifikasi WA lunas ke pelanggan + try { + const cust = customerSvc.getCustomerById(inv.customer_id); + if (cust && cust.phone) { + const pkg = cust.package_id ? customerSvc.getPackageById(cust.package_id) : null; + await sendPaymentSuccessWA( + cust.phone, + cust.name, + `${inv.period_month}/${inv.period_year}`, + Number(inv.amount || 0).toLocaleString('id-ID'), + 'Manual (Webhook)', + { + customerId: cust.id, + invoiceId: inv.id, + username: cust.pppoe_username || cust.id, + customerCode: cust.customer_code || '', + packageName: pkg?.name || cust.package_name || '-', + paidAt: new Date(), method: 'Manual (Webhook)' + } + ); + } + } catch (e) {} req.session._msg = { type: 'success', text: 'Invoice #' + invId + ' ditandai LUNAS & notif dicocokkan.' }; } catch (e) { req.session._msg = { type: 'error', text: 'Gagal cocokkan: ' + e.message }; diff --git a/routes/customerAPI.js b/routes/customerAPI.js index 1f59f16..ae22df7 100644 --- a/routes/customerAPI.js +++ b/routes/customerAPI.js @@ -2359,6 +2359,37 @@ router.post('/app/admin/collector-payments/approve', requireAdminApiAuth, async if (unpaidCount === 0) { try { await customerSvc.activateCustomer(pay.customer_id); } catch (_) {} } + + // Notifikasi WA lunas ke pelanggan (gateway aktif) + try { + const customer = customerSvc.getCustomerById(pay.customer_id); + const inv = billingSvc.getInvoiceById(pay.invoice_id); + if (customer && customer.phone && inv) { + const settings = getSettingsWithCache(); + const waNotify = require('../services/waNotifyService'); + const whatsappService = require('../services/whatsappService'); + const pkg = customer.package_id ? customerSvc.getPackageById(customer.package_id) : null; + const appUrl = String(settings.public_base_url || '').replace(/\/$/, ''); + const template = db.getAppSetting('whatsapp_payment_success_message', ''); + const msg = whatsappService.formatPaymentSuccessMessage({ + customerName: customer.name, + invoiceId: inv.id, + customerUsername: customer.pppoe_username || customer.id || '-', + customerCode: customer.customer_code || '', + packageName: pkg?.name || customer.package_name || '-', + periodMonth: inv.period_month, + periodYear: inv.period_year, + amount: inv.amount, + paymentMethod: collectorLabel, + paidAt: new Date(), + companyName: settings.company_header || 'OPEN-ISP', + companyPhone: settings.company_phone || '', + portalUrl: appUrl ? appUrl + '/customer' : '', + customTemplate: template + }); + await waNotify.sendText(customer.phone, msg); + } + } catch (_) {} } res.json({ success: true, message: 'Pembayaran berhasil di-approve dan tagihan ditandai LUNAS' }); } catch (e) { res.status(500).json({ success: false, message: e.message }); } diff --git a/services/waNotifyService.js b/services/waNotifyService.js index 805fd9c..41bde2c 100644 --- a/services/waNotifyService.js +++ b/services/waNotifyService.js @@ -140,8 +140,12 @@ async function isConnected() { try { if (gw === 'gowa') { const st = await require('./gowaWhatsappService').gowaStatus(); - const d = (st && st.data && st.data.results) || (st && st.data) || {}; - val = !!(d.is_connected || d.is_logged_in || d.connected); + const raw = (st && st.data && st.data.results) || (st && st.data) || {}; + const list = Array.isArray(raw) ? raw : [raw]; + val = list.some(function (d) { + return !!(d && (d.is_connected || d.is_logged_in || d.connected || + d.state === 'connected' || d.state === 'logged_in')); + }); } else if (gw === 'baileys') { const mod = await import('./whatsappBot.mjs'); const s = mod.whatsappStatus; diff --git a/version.txt b/version.txt index 12697f7..7988f13 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.5.12 \ No newline at end of file +0.5.13 \ No newline at end of file