[FIX-TAGIHAN-PREVENT-PAID-INVOICE-REORDER] Validasi Penolakan Pembuatan Ulang Billing Lunas & Relokasi Flagging Pengangkut Pasca-Sukses Simponi
Modul: Single Billing | Tipe: Bugfix | Stage Saat Ini:
PRODUCTION| Tanggal Dokumen: 2026-09-07
PRODUCTION RELEASED
Dokumen ini telah berada dalam tahap PRODUCTION. Perubahan logika backend, migrasi skema/konfigurasi, dan rilis v1.9.2 telah diterapkan secara penuh di lingkungan Production via MR !232.
LIFECYCLE GUIDE — UPDATE BERTAHAP:
- Buat file ini saat selesai development. Isi semua section kecuali Staging & Production.
- Tanggal Dokumen (Header) & Riwayat Update Log: Gunakan tanggal hari ini (pembuatan dokumen).
- Baris Development (Lifecycle): Gunakan tanggal & jam commit terakhir terkait di branch development.
- Update baris Staging saat naik ke UAT. Ubah "Stage Saat Ini" di header menjadi STAGING.
- Update baris Production saat naik ke Prod. Ubah "Stage Saat Ini" di header menjadi PRODUCTION.
- Update entry di README.md setiap kali status berubah. -->
1. Informasi Tugas & Referensi Bisnis
Task ID / Nomor Referensi:
FIX-TAGIHAN-PREVENT-PAID-INVOICE-REORDERSumber Instruksi: Investigasi Masalah Duplikasi Billing Lunas & Desinkronisasi State Flagging Pengangkut
Dokumen Referensi / Lampiran:
-Task / Dependensi Terkait (Cross-Service):
REFACTOR-TAGIHAN-CONTROLLER-DEDUPLICATION,TASK-SET-EXPIRED-BILLING-V3Modul Terkait:
Single BillingService / Repository:
single-billing-BEPIC Pengembang: Hermansyah Efendi
Latar Belakang & Ringkasan Kebutuhan: Ditemukan dua celah integritas data pada alur orkestrasi penagihan Single Billing: (1) Query
checkExistingBillingsebelumnya memfilter klausa SQLand fl_paid = false. Jika tagihan atas suatu nomor aju telah lunas terbayar (fl_paid = true), query menghasilkan record kosong, sehingga sistem mengizinkan pengguna jasa men-trigger request pembuatan billing baru atas nomor aju yang sama (berisiko menimbulkan tagihan ganda dan overpayment). (2) Eksekusi penandaan flagpostFlagBillingdanupdateTrxBillingpada schema database SSM Pengangkut dijalankan di awal proses sebelum panggilan ke gateway SIMPONI selesai. Apabila SIMPONI mengalami timeout atau menolak transaksi, status di tabel pengangkut sudah terlanjur berubah menjadi terdisposisi billing seolah-olah billing berhasil terbit. Perbaikan ini menghapus filter pasiffl_paid = falsedan mengimplementasikan validasi eksplisit di layer JS untuk menolak request ulang invoice lunas, serta merelokasi penandaan flag SSM Pengangkut agar hanya dieksekusi setelah SIMPONI terkonfirmasi mengembalikan status sukses.Ruang Lingkup & Rincian Sub-Pekerjaan (Technical Scope & Milestones):
- [x] Penghapusan filter
fl_paid = falsedari querycheckExistingBillingpadaapp/models/tagihan_models.js - [x] Pengambilan kolom
fl_paiddan penambahan validasi logika di JavaScript layer untuk mendeteksi tagihan yang sudah lunas dengan pesan penolakan deskriptif - [x] Relokasi pemanggilan
postFlagBillingdanupdateTrxBillingpadaapp/helpers/tagihan_helper.jske blok pasca-sukses (payloadRespon.status === status.success) - [x] Pencegahan split-brain/desinkronisasi state pada schema pengangkut saat gateway Simponi mengalami error atau timeout
- [x] Penghapusan filter
2. Deployment Lifecycle
| # | Environment | Tanggal & Waktu | PIC | Status | Catatan |
|---|---|---|---|---|---|
| 1 | Development | 2026-07-14 15:03 | Hermansyah Efendi | DEPLOYED | Branch: development |
| 2 | Staging (UAT) | 2026-10-14 13:00 | Hermansyah Efendi / QA | DEPLOYED | Release Candidate via MR Staging !229. Co-deployed: [FEAT-SIMPONI-V3-ISOLATION, TASK-SET-EXPIRED-BILLING-V3, FEAT-BILLING-REKAP-EXPIRED-DATE-PAYLOAD, REFACTOR-TAGIHAN-CONTROLLER-DEDUPLICATION, FIX-PAYMENT-WEBHOOK-RACE-CONDITION, FIX-TAGIHAN-PREVENT-PAID-INVOICE-REORDER, FEAT-REQUEST-LOGGER-POSTMAN, TEST-DRAGONFLY-VITEST-SUITE, FEAT-RUNTIME-ENV-VALIDATION, TASK-NON-BLOCKING-BILLING-DISPATCH-AND-RETRY-CONFIG] |
| 3 | Production | 2026-10-14 20:06 | achmad fauzi | DEPLOYED | Branch: prod via MR !232 (Release v1.9.2). Co-deployed: [FEAT-SIMPONI-V3-ISOLATION, TASK-SET-EXPIRED-BILLING-V3, FEAT-BILLING-REKAP-EXPIRED-DATE-PAYLOAD, REFACTOR-TAGIHAN-CONTROLLER-DEDUPLICATION, FIX-PAYMENT-WEBHOOK-RACE-CONDITION, FIX-TAGIHAN-PREVENT-PAID-INVOICE-REORDER, FEAT-REQUEST-LOGGER-POSTMAN, TEST-DRAGONFLY-VITEST-SUITE, FEAT-RUNTIME-ENV-VALIDATION, TASK-NON-BLOCKING-BILLING-DISPATCH-AND-RETRY-CONFIG] |
Riwayat Update Log
| Tanggal | Environment | Perubahan | PIC |
|---|---|---|---|
| 2026-10-14 | Production | Status diupdate ke PRODUCTION (DEPLOYED, CLOSED), rilis Release v1.9.2 via MR !232 | Hermansyah Efendi |
| 2026-09-07 | Development | Inisialisasi draft audit log dari context block backend (commit d47b184) | Hermansyah Efendi |
3. Analisis Teknis & Rincian Perubahan
Alur Logika & Bisnis:
- Validasi Tagihan Lunas di Layer Aplikasi: Query
checkExistingBillingkini mengambil kolomkode_billing_indukdanfl_paidtanpa memfilter status pelunasan secara pasif di query SQL. Pada layer JavaScript, jika record ditemukan danrow.fl_paid === true, request pembuatan billing langsung ditolak dengan mengembalikan statustrue,kodeBilling, serta pesan penolakan deskriptif bahwa tagihan sudah dibayar dan tidak diizinkan melakukan request billing ulang. Jikarow.fl_paid === false, sistem tetap menginformasikan bahwa billing lama masih aktif/belum kedaluwarsa. - Post-Success Flagging Guard (Pencegahan Split-Brain): Variabel
_postFlagBillingdan_updateTrxBillingdideklarasikan sebagai block-scoped variable di awal alur orkestrasiapp/helpers/tagihan_helper.js. Panggilan aktual penandaan flag ke schema SSM Pengangkut dipindahkan ke blok kondisi pasca-suksesif (payloadRespon && payloadRespon.status === status.success). Jika Simponi gagal atau timeout, database SSM Pengangkut tidak akan dimodifikasi secara prematur. - Daftar File yang Berubah:
app/helpers/tagihan_helper.js(Helper — pergeseran eksekusi postFlagBilling dan updateTrxBilling ke blok penanganan pasca-sukses)app/models/tagihan_models.js(Model — penghapusan filter query fl_paid = false dan penambahan validasi eksplisit status tagihan lunas)
- Validasi Tagihan Lunas di Layer Aplikasi: Query
Perubahan Database (DDL / DML):
Tidak ada migration file DDL terdeteksi — penyesuaian query PostgreSQL pada fungsi
checkExistingBilling:sqlSELECT kode_billing_induk, fl_paid FROM billing.subbiller_hdr WHERE no_aju_layanan ILIKE '%${nomor}%' AND to_date(expired_date, 'YYYY-MM-DD') > now()::date ORDER BY create_date DESC NULLS LAST LIMIT 1;Kontrak API & Response Payload:(Tidak ada perubahan rute endpoint baru. Format error response diterapkan pada alur endpoint penerimaan rekap tagihan)
POST/api/v3/terima-rekap-tagihan- Deskripsi: Endpoint orkestrasi penerimaan data rekap tagihan untuk penerbitan kode billing Single Billing ke Simponi.
- Autentikasi / Guard:
Bearer Token(SSO Middleware Guard) - Response Saat Nomor Aju Sudah Lunas Terbayar (
row.fl_paid === true):json{ "status": true, "kodeBilling": "820260729744850", "message": "Billing atas nomor aju layanan 1020195BA8E65 sudah dibayar dan tidak bisa melakukan request billing lagi. Jika ada tertinggal infokan tim teknis LNSW" } - Response Saat Nomor Aju Memiliki Billing Aktif Belum Kedaluwarsa (
row.fl_paid === false):json{ "status": true, "kodeBilling": "820260729744850", "message": "Billing atas nomor aju layanan 1020195BA8E65 belum kedaluwarsa atau masih berlaku. Silahkan cek data tagihan atas billing yang sudah ada. Jika ada tertinggal infokan tim teknis LNSW" }
4. Bukti Teknis (Technical Proof & Audit Trail)
- Repository:
single-billing-BE - Branch Utama:
development - Merge Request / PR: !232 (Release v1.9.2 — Merged to prod) | Staging: !229
Daftar Commit Terkait (Multi-Commit Trail)
| Commit ID | Branch | Author | Tanggal & Jam | Ringkasan Perubahan |
|---|---|---|---|---|
8ef5af8 | development | Hermansyah Efendi | 2026-07-14 15:00 | fix(tagihan): relocate postFlagBilling and updateTrxBilling to execute post-success |
d47b184 | development | Hermansyah Efendi | 2026-07-14 15:03 | fix(tagihan): validate paid invoices in checkExistingBilling |
Cuplikan Kode / Implementasi Inti
// app/models/tagihan_models.js (checkExistingBilling)
const checkExistingBilling = async (nomor) => {
let queryText = `SELECT kode_billing_induk, fl_paid FROM ${tableSubbillerHdr} WHERE no_aju_layanan ilike '%${nomor}%' and to_date(expired_date, 'YYYY-MM-DD') > now()::date order by create_date desc nulls last limit 1`;
try {
const { rows } = await connection.query(queryText);
if (rows.length > 0) {
const row = rows[0];
if (row.fl_paid === true) {
return {
status: true,
kodeBilling: row.kode_billing_induk,
message: `Billing atas nomor aju layanan ${nomor} sudah dibayar dan tidak bisa melakukan request billing lagi. Jika ada tertinggal infokan tim teknis LNSW`
};
}
return {
status: true,
kodeBilling: row.kode_billing_induk,
message: `Billing atas nomor aju layanan ${nomor} belum kedaluwarsa atau masih berlaku. Silahkan cek data tagihan atas billing yang sudah ada. Jika ada tertinggal infokan tim teknis LNSW`
};
} else {
return false;
}
} catch (error) {
return false;
}
}// app/helpers/tagihan_helper.js (Relokasi ke post-success)
// Jalankan update flag billing dan trx billing di SSM Pengangkut hanya saat billing sukses terbit
// agar mencegah state desinkronisasi jika request ke SIMPONI mengalami kegagalan/timeout.
if (payloadRespon && payloadRespon.status === status.success) {
const ajuKedatangan = await cekAjuKedatangan(
rekapBillingKL[0][0].no_aju_layanan
);
_postFlagBilling = await postFlagBilling(
ajuKedatangan,
rekapBillingKL[0][0].no_aju_layanan
);
_updateTrxBilling = await updateTrxBilling(
trxIdInsw,
rekapBillingKL[0][0].information.billInfo2,
rekapBillingKL[0][0].information.billInfo5
);
}5. Bukti Verifikasi Pengujian (QA / Unit Test / API Verification)
A. Verifikasi Unit Test (Development Stage)
- Test Suite File:
tests/helpers/tagihan_helper.test.js,tests/models/tagihan_models.test.js - Status Eksekusi CLI:
PASSED
RUN v4.1.11 D:/WORK/LNSW/single-billing-BE
✓ tests/helpers/tagihan_helper.test.js (13 tests) 25ms
✓ tests/models/tagihan_models.test.js (49 tests) 29ms
Test Files 2 passed (2)
Tests 62 passed (62)
Start at 13:05:49
Duration 498ms (transform 203ms, setup 155ms, import 286ms, tests 54ms, environment 0ms)B. Panduan Pengujian QA & Verifikasi Staging (QA Playbook)
Diisi saat development sebagai panduan eksekusi pengujian QA, kemudian dilengkapi bukti response JSON saat verifikasi di Staging.
- Cakupan / Surface Pengujian:
API / Postman Only - Kebutuhan Akses / Role Akun: Token SSO valid (Bearer Auth), Postman Collection Single Billing.
- Hasil Pengujian UAT:
PASSED - Catatan QA: Verifikasi penolakan pembuatan ulang billing lunas dan relokasi flagging Pengangkut berhasil lolos pengujian UAT.
Skenario Pengujian QA (Test Scenarios)
Skenario 1 — Negative Test: Penolakan Pembuatan Billing Baru pada Nomor Aju Lunas:
- Pre-kondisi: Nomor aju
1020195BA8E65telah terdaftar disubbiller_hdrdengan statusfl_paid = true. - Trigger / Request: Kirim request pembuatan billing baru (
POST /api/v3/terima-rekap-tagihan) menggunakan nomor aju yang sama. - Ekspektasi Response: Sistem menolak pembuatan billing dan mengembalikan pesan bahwa billing sudah dibayar dan tidak bisa melakukan request billing lagi.
- Verifikasi Database / Log: Tidak ada billing baru yang terbit di Simponi dan tidak ada penambahan baris
subbiller_hdr.
- Pre-kondisi: Nomor aju
Skenario 2 — Negative Test: Simulasi Kegagalan Simponi & Integritas Flag Pengangkut:
- Pre-kondisi: Nomor aju valid diajukan, namun koneksi Simponi sengaja disimulasikan gagal/timeout atau mengembalikan penolakan kode error.
- Trigger / Request: Kirim request
POST /api/v3/terima-rekap-tagihan. - Ekspektasi Response: Request mengembalikan response error / timeout.
- Verifikasi Database / Log: Tabel tagihan pada schema
pengangkutdipastikan TIDAK ter-update dengan flag billing terbit prematur.
Skenario 3 — Happy Path: Pembaharuan Flagging Saat Simponi Sukses:
- Pre-kondisi: Request billing valid dan Simponi merespons sukses penerbitan kode billing.
- Trigger / Request: Kirim request
POST /api/v3/terima-rekap-tagihan. - Ekspektasi Response: HTTP
200 OK, kode billing terbentuk. - Verifikasi Database / Log: Fungsi
postFlagBillingdanupdateTrxBillingtereksekusi mulus setelah billing terbit.
Bukti Response Verifikasi Staging (Diisi saat UAT)
{
"status": "PASSED",
"note": "Pengujian verifikasi UAT dan validasi integrasi berhasil lolos, rilis production v1.9.2 telah live."
}