fix(qris): kode unik lintas order + pelunasan multi-tagihan; fix kirim tagihan manual
Some checks failed
CI / test (push) Has been cancelled

- webhook QRIS: nominal total tagihan + kode -> lunaskan semua tagihan pelanggan
- isQrisAmountTaken() cek invoice+voucher+donasi (cegah ambigu)
- billingSendService: require('qrcode'), nominal unik + {{qris_kode}}, fallback link
- pilih tagihan terlama; pesan pengingat manual yang jujur saat di-skip
- versi 0.5.5
This commit is contained in:
oxpoe
2026-11-05 12:48:59 +07:00
parent b34e5a34d8
commit 34d4563a5f
10 changed files with 97 additions and 51 deletions

View File

@@ -1011,7 +1011,21 @@ app.post('/api/webhook/v1/payment-notif', multer().any(), async (req, res) => {
const custId = Number(inv.customer_id || 0);
if (invId > 0) {
const noteLine = `AUTO-QRIS: cocok nominal unik Rp ${amount} (service=${String(service || '-')}, notif=${notifId || '-'})`;
markInvoicePaidAppendNote.run('QRIS', noteLine, noteLine, notifId || null, invId);
// QRIS multi-tagihan: nominal = total semua tagihan + kode unik yang disimpan
// di salah satu invoice. Lunaskan semuanya, jangan hanya invoice pemilik kode.
const unpaidRows = custId > 0
? db.prepare("SELECT id, amount FROM invoices WHERE customer_id=? AND status='unpaid' ORDER BY period_year ASC, period_month ASC, id ASC").all(custId)
: [];
const totalUnpaid = unpaidRows.reduce((sum, row) => sum + (Number(row.amount) || 0), 0);
const qrisCode = Number(inv.qris_unique_code || 0) || 0;
if (qrisCode > 0 && unpaidRows.length > 1 && totalUnpaid + qrisCode === Number(amount)) {
const noteBundle = `${noteLine} — lunas ${unpaidRows.length} tagihan`;
const payOne = db.prepare(`UPDATE invoices SET status='paid', paid_at=NOW_LOCAL(), paid_by_name=?, notes=CASE WHEN notes IS NULL OR TRIM(notes) = '' THEN ? ELSE notes || '\n' || ? END, qris_paid_notif_id=? WHERE id=? AND status='unpaid'`);
for (const row of unpaidRows) payOne.run('QRIS', noteBundle, noteBundle, notifId || null, row.id);
logger.info(`[WEBHOOK][payment-notif] MATCH bundle invoice=${invId} invoices=${unpaidRows.length} total=${totalUnpaid} amount=${amount}`);
} else {
markInvoicePaidAppendNote.run('QRIS', noteLine, noteLine, notifId || null, invId);
}
matchedInvoiceId = invId;
if (notifId) {