fix(wa): notifikasi LUNAS lewat gateway aktif + approve APK
All checks were successful
CI / test (push) Successful in 2m26s
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:
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/).
|
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"
|
## [0.5.12] — 2026-11-05 — "gowa-test-unsaved"
|
||||||
|
|
||||||
### Diperbaiki
|
### Diperbaiki
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/lisensi-ISC-ADFF2F" alt="Lisensi: ISC">
|
<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/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">
|
<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>
|
<a href="https://demo-openisp.oxpoe.xyz"><img src="https://img.shields.io/badge/demo-live-ADFF2F?logo=googlechrome&logoColor=black" alt="Demo"></a>
|
||||||
|
|||||||
@@ -628,8 +628,8 @@ async function trySendWaPaymentSuccess(settings, invoiceId, methodLabel) {
|
|||||||
if (!inv) return;
|
if (!inv) return;
|
||||||
const phone = String(inv.customer_phone || '').trim();
|
const phone = String(inv.customer_phone || '').trim();
|
||||||
if (!phone) return;
|
if (!phone) return;
|
||||||
const { sendWA, whatsappStatus } = await import('./services/whatsappBot.mjs');
|
// Pakai gateway aktif (GOWA/Fonnte/Meta/Baileys), bukan hanya Baileys.
|
||||||
if (whatsappStatus.connection !== 'open') throw new Error('Bot WhatsApp belum terhubung');
|
const waNotify = require('./services/waNotifyService');
|
||||||
|
|
||||||
const appUrl = (settings.public_base_url || '').replace(/\/$/, '');
|
const appUrl = (settings.public_base_url || '').replace(/\/$/, '');
|
||||||
const portalUrl = appUrl ? `${appUrl}/customer` : '';
|
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}`);
|
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) {
|
} catch (e) {
|
||||||
logger.error(`[WEBHOOK][payment-notif] WA success notif failed: ${e?.message || e}`);
|
logger.error(`[WEBHOOK][payment-notif] WA success notif failed: ${e?.message || e}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "open-isp",
|
"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)",
|
"description": "OPEN-ISP \u2014 sistem billing & manajemen ISP open-source (PWA pelanggan, multi-OLT, RADIUS, WhatsApp, QRIS)",
|
||||||
"main": "app-customer.js",
|
"main": "app-customer.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -4925,6 +4925,29 @@ router.post('/payment-unmatched/:id/match', requireAdminSession, express.urlenco
|
|||||||
if (!unpaid) await customerSvc.activateCustomer(inv.customer_id);
|
if (!unpaid) await customerSvc.activateCustomer(inv.customer_id);
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} 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.' };
|
req.session._msg = { type: 'success', text: 'Invoice #' + invId + ' ditandai LUNAS & notif dicocokkan.' };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
req.session._msg = { type: 'error', text: 'Gagal cocokkan: ' + e.message };
|
req.session._msg = { type: 'error', text: 'Gagal cocokkan: ' + e.message };
|
||||||
|
|||||||
@@ -2359,6 +2359,37 @@ router.post('/app/admin/collector-payments/approve', requireAdminApiAuth, async
|
|||||||
if (unpaidCount === 0) {
|
if (unpaidCount === 0) {
|
||||||
try { await customerSvc.activateCustomer(pay.customer_id); } catch (_) {}
|
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' });
|
res.json({ success: true, message: 'Pembayaran berhasil di-approve dan tagihan ditandai LUNAS' });
|
||||||
} catch (e) { res.status(500).json({ success: false, message: e.message }); }
|
} catch (e) { res.status(500).json({ success: false, message: e.message }); }
|
||||||
|
|||||||
@@ -140,8 +140,12 @@ async function isConnected() {
|
|||||||
try {
|
try {
|
||||||
if (gw === 'gowa') {
|
if (gw === 'gowa') {
|
||||||
const st = await require('./gowaWhatsappService').gowaStatus();
|
const st = await require('./gowaWhatsappService').gowaStatus();
|
||||||
const d = (st && st.data && st.data.results) || (st && st.data) || {};
|
const raw = (st && st.data && st.data.results) || (st && st.data) || {};
|
||||||
val = !!(d.is_connected || d.is_logged_in || d.connected);
|
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') {
|
} else if (gw === 'baileys') {
|
||||||
const mod = await import('./whatsappBot.mjs');
|
const mod = await import('./whatsappBot.mjs');
|
||||||
const s = mod.whatsappStatus;
|
const s = mod.whatsappStatus;
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.5.12
|
0.5.13
|
||||||
Reference in New Issue
Block a user