fix(wa): notifikasi LUNAS lewat gateway aktif + approve APK
All checks were successful
CI / test (push) Successful in 2m26s

- isConnected GOWA: tangani /devices array (state connected)
- app-customer payment-notif: pakai waNotify, bukan Baileys
- customerAPI approve kolektor + payment-unmatched: kirim WA lunas
- versi 0.5.13
This commit is contained in:
oxpoe
2026-11-05 17:32:31 +07:00
parent 310e91ed97
commit 7db4cb81a3
8 changed files with 73 additions and 8 deletions

View File

@@ -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

View File

@@ -11,7 +11,7 @@
<p align="center">
<img src="https://img.shields.io/badge/lisensi-ISC-ADFF2F" alt="Lisensi: ISC">
<img src="https://img.shields.io/badge/versi-0.5.12_gowa--test2-ADFF2F" alt="Versi 0.5.12 (gowa-test-unsaved)">
<img src="https://img.shields.io/badge/versi-0.5.13_lunas--notify-ADFF2F" alt="Versi 0.5.13 (lunas-notify)">
<img src="https://img.shields.io/badge/Node.js-%E2%89%A520-339933" alt="Node.js >= 20">
<img src="https://img.shields.io/badge/docker-ready-2496ED?logo=docker&logoColor=white" alt="Docker">
<a href="https://demo-openisp.oxpoe.xyz"><img src="https://img.shields.io/badge/demo-live-ADFF2F?logo=googlechrome&logoColor=black" alt="Demo"></a>

View File

@@ -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}`);
}

View File

@@ -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": {

View File

@@ -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 };

View File

@@ -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 }); }

View File

@@ -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;

View File

@@ -1 +1 @@
0.5.12
0.5.13