All checks were successful
CI / test (push) Successful in 2m43s
- ensureInvoiceQris menerima base total agar kode unik konsisten dengan cron/portal - pesan manual menampilkan total tagihan, bukan satu invoice
70 lines
3.0 KiB
JavaScript
70 lines
3.0 KiB
JavaScript
'use strict';
|
|
/**
|
|
* invoiceQrisService.js — pastikan kode unik + payload QRIS dinamis untuk sebuah invoice.
|
|
* QRIS dinamis = QRIS statis (settings.qris_static_payload) + nominal (amount + kode unik).
|
|
*/
|
|
const db = require('../config/database');
|
|
const qrisUtil = require('../utils/qrisUtil');
|
|
const { getSettings } = require('../config/settingsManager');
|
|
|
|
/**
|
|
* Cek apakah nominal unik sudah dipakai order lain yang masih menunggu bayar.
|
|
* WAJIB mencakup invoice, voucher, dan donasi: webhook mencocokkan ketiganya
|
|
* sekaligus, jadi nominal yang dipakai dua order berbeda akan dianggap ambigu
|
|
* dan pembayarannya tidak diproses.
|
|
*/
|
|
function isQrisAmountTaken(amount, opts = {}) {
|
|
const amt = Number(amount || 0);
|
|
if (!Number.isFinite(amt) || amt <= 0) return false;
|
|
const exInv = Number(opts.excludeInvoiceId || 0);
|
|
const exVoucher = Number(opts.excludeVoucherOrderId || 0);
|
|
const exDonation = Number(opts.excludeDonationOrderId || 0);
|
|
try {
|
|
const inv = db.prepare('SELECT id FROM invoices WHERE status=? AND qris_amount_unique=? AND id!=? LIMIT 1').get('unpaid', amt, exInv);
|
|
if (inv && inv.id) return true;
|
|
const voc = db.prepare('SELECT id FROM public_voucher_orders WHERE status=? AND qris_amount_unique=? AND id!=? LIMIT 1').get('pending', amt, exVoucher);
|
|
if (voc && voc.id) return true;
|
|
const don = db.prepare('SELECT id FROM public_donation_orders WHERE status=? AND qris_amount_unique=? AND id!=? LIMIT 1').get('pending', amt, exDonation);
|
|
if (don && don.id) return true;
|
|
} catch (e) { /* tabel opsional belum ada */ }
|
|
return false;
|
|
}
|
|
|
|
function ensureInvoiceQris(inv, baseAmountOverride) {
|
|
const settings = getSettings() || {};
|
|
const invId = Number((inv && inv.id) || 0);
|
|
// baseAmountOverride dipakai bila nominal QRIS = total beberapa tagihan
|
|
const base = Number(baseAmountOverride || (inv && inv.amount) || 0);
|
|
if (!invId || base <= 0) return { uniqueCode: 0, amount: base, payload: '' };
|
|
|
|
let code = Number(inv.qris_unique_code || 0);
|
|
let amountUnique = Number(inv.qris_amount_unique || 0);
|
|
|
|
if (!(amountUnique > 0 && code > 0 && (amountUnique - code) === base)) {
|
|
let chosen = 0, chosenAmt = 0;
|
|
for (let c = 1; c <= 999; c++) {
|
|
const a = base + c;
|
|
if (!isQrisAmountTaken(a, { excludeInvoiceId: invId })) { chosen = c; chosenAmt = a; break; }
|
|
}
|
|
if (chosen) {
|
|
code = chosen;
|
|
amountUnique = chosenAmt;
|
|
try {
|
|
db.prepare('UPDATE invoices SET qris_unique_code=?, qris_amount_unique=?, qris_assigned_at=(NOW_LOCAL()) WHERE id=?')
|
|
.run(code, amountUnique, invId);
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
|
|
const finalAmount = amountUnique > 0 ? amountUnique : base;
|
|
const raw = String(settings.qris_static_payload || '').trim();
|
|
let payload = '';
|
|
if (raw) {
|
|
try { payload = qrisUtil.convertStaticQrisToDynamic(raw, finalAmount); }
|
|
catch (e) { payload = raw; }
|
|
}
|
|
return { uniqueCode: code, amount: finalAmount, payload };
|
|
}
|
|
|
|
module.exports = { ensureInvoiceQris, isQrisAmountTaken };
|