[TASK-SET-EXPIRED-BILLING-V3] Implementasi Endpoint Set Expired Billing V3 dan Tracking Pembatalan Single Billing
Modul: Single Billing | Tipe: Feature | 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:
TASK-SET-EXPIRED-BILLING-V3Sumber Instruksi: Pembaruan Siklus Hidup Billing V3 & Penanganan Pembatalan / Expired Billing
Dokumen Referensi / Lampiran: Single Billing V3 - Set Expired Billing.postman_collection.json
Task / Dependensi Terkait (Cross-Service):
- FEAT-SIMPONI-V3-ISOLATION — Isolasi protokol dan arsitektur route V3
- REFACTOR-TAGIHAN-CONTROLLER-DEDUPLICATION — Deduplikasi controller dan orkestrasi helper V3
- TASK-SPLIT-BILLING-202608 — Orkestrasi pembatalan dan reset billing dari modul Pengangkut
Modul Terkait:
Single BillingService / Repository:
single-billing-BEPIC Pengembang: Hermansyah Efendi
Latar Belakang & Ringkasan Kebutuhan: Pada alur operasional SSm Pengangkut, ketika pengguna jasa membatalkan warta atau merevisi pengajuan sebelum kode billing dilunasi, billing aktif yang telah terbit di Single Billing harus dapat dibatalkan atau di-set expired secara eksplisit agar tidak memblokir permohonan billing baru atas nomor aju yang sama. Sebelumnya, sistem hanya mengandalkan pengecekan pasif tanggal kadaluarsa Simponi. Task ini mengimplementasikan endpoint
POST /api/v3/set-expired-billingyang dilengkapi validasi status ketat (mencegah pembatalan billing yang sudah lunas atau sudah expired), pencatatan riwayat tracking status312, dan penyesuaian querycheckExistingBillingdengan filter(fl_expired is not true)agar pengajuan baru dapat diterbitkan tanpa hambatan.Ruang Lingkup & Rincian Sub-Pekerjaan (Technical Scope & Milestones):
- [x] Milestone 1: Route Registration & Security Guard — Registrasi rute
POST /api/v3/set-expired-billingdengan middlewaredoIntrospectiondan validasi JoischemasSetExpiredBillingV3. - [x] Milestone 2: Thin Controller Handler — Penambahan controller
setExpiredBilling_V3padaapp/controllers/v3/billing_controller.js. - [x] Milestone 3: Service Business Logic & Tracking — Implementasi logika bisnis
setExpiredBillingHelperpadaapp/helpers/billing_helper.jsdengan status guardfl_paiddanfl_expired, pencatatan status tracking code312, dan raw query audit logging. - [x] Milestone 4: Database Model Query — Penambahan query model
setBillingExpiredpadaapp/models/billing_models.js(UPDATE billing.subbiller_hdr SET fl_expired = true). - [x] Milestone 5: Existing Billing Filter Adjustment — Pembaruan query
checkExistingBillingpadaapp/models/tagihan_models.jsdengan filter(fl_expired is not true)guna membebaskan kunci permohonan billing baru.
- [x] Milestone 1: Route Registration & Security Guard — Registrasi rute
2. Deployment Lifecycle
| # | Environment | Tanggal & Waktu | PIC | Status | Catatan |
|---|---|---|---|---|---|
| 1 | Development | 2026-08-27 12:18 | Hermansyah Efendi | DEPLOYED | Branch: development (Commit: ec51278) |
| 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-08-27 | Development | Inisialisasi draft audit log dari context block backend (commit ec51278) | Hermansyah Efendi |
| 2026-09-07 | Development | Standardisasi QA Playbook (5 skenario uji terstruktur), penyelarasan dependensi silang V3, dan penguatan dokumentasi lifecycle billing | Hermansyah Efendi |
3. Analisis Teknis & Rincian Perubahan
Alur Logika & Bisnis:
- Validasi Status Sebelum Expire:
- Memeriksa keberadaan data billing berdasarkan
trx_id_insw. Jika tidak ditemukan, kembalikan HTTP404 Not Found. - Jika
fl_paid === true, tolak dengan HTTP400 Bad Request: "Billing sudah dibayar dan tidak dapat diubah statusnya menjadi expired". - Jika
fl_expired === true, tolak dengan HTTP400 Bad Request: "Billing sudah dalam status expired".
- Memeriksa keberadaan data billing berdasarkan
- Update Status & Tracking:
- Menjalankan
setBillingExpireduntuk mengesetfl_expired = truedan memperbaruiexpired_datedengan timestamp sistem saat ini pada tabelbilling.subbiller_hdr. - Memanggil
insertTableTracking(trx_id_insw, recordTime, "312")untuk mencatat status kadaluarsa billing pada timeline disposisi. - Mencatat raw query logging dengan action
"set_expired"dan deskripsi"Success Set Expired Billing >> setExpiredBilling_V3".
- Menjalankan
- Pelepasan Kunci pada Existing Billing Check:
- Query
checkExistingBillingpadaapp/models/tagihan_models.jsdiperbarui dengan menyertakan kondisiand (fl_expired is not true) and to_date(expired_date, 'YYYY-MM-DD') > now()::date. - Hal ini memastikan nomor aju yang billing lamanya telah dibatalkan/expired dapat langsung diproses ulang untuk pembentukan kode billing baru tanpa terblokir masa aktif Simponi sebelumnya.
- Query
- Validasi Status Sebelum Expire:
Perubahan Database (DDL / DML):
sql-- DML Terprogram: Update status expired pada tabel billing.subbiller_hdr UPDATE billing.subbiller_hdr SET fl_expired = true, expired_date = to_char(now(), 'YYYY-MM-DD HH24:MI:SS') WHERE trx_id_insw = $1 RETURNING *; -- Penyesuaian Query Pengecekan Billing Eksisting (app/models/tagihan_models.js) -- Penambahan filter `(fl_expired is not true)` agar record yang telah di-expire diabaikan: -- SELECT ... FROM billing.subbiller_hdr WHERE ... AND (fl_expired is not true) AND to_date(expired_date, 'YYYY-MM-DD') > now()::dateKontrak API & Response Payload:
POST/api/v3/set-expired-billing- Deskripsi: Mengubah status billing yang terdaftar di Single Billing menjadi expired secara eksplisit berdasarkan
trx_id_insw. - Autentikasi / Guard:
Bearer Token(doIntrospection),validateSchemaSimponi(schemasSetExpiredBillingV3.body) - Request Header:
Authorization:Bearer <token>Content-Type:application/json
- Request Body:json
{ "trx_id_insw": "TRX-123456", "alasan": "Pembatalan warta oleh pengguna jasa" } - Response Payload (
200 OK):json{ "success": true, "statusCode": 200, "message": "Berhasil mengubah status billing menjadi expired", "data": { "trx_id_insw": "TRX-123456", "kode_billing_induk": "820260729744850", "fl_expired": true, "expired_date": "2026-08-27 12:18:31.000", "alasan": "Pembatalan warta oleh pengguna jasa" } } - Response Error (
400 Bad Request):json{ "success": false, "statusCode": 400, "message": "Billing sudah dibayar dan tidak dapat diubah statusnya menjadi expired" }json{ "success": false, "statusCode": 400, "message": "Billing sudah dalam status expired" } - Response Error (
404 Not Found):json{ "success": false, "statusCode": 404, "message": "Data billing dengan aju tersebut tidak ditemukan" }
- Deskripsi: Mengubah status billing yang terdaftar di Single Billing menjadi expired secara eksplisit berdasarkan
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 |
|---|---|---|---|---|
ec51278 | development | Hermansyah Efendi | 2026-08-27 12:18 | feat(billing): implement set expired billing endpoint v3 |
File yang Berubah
| Path File | Kategori | Keterangan |
|---|---|---|
app/routes/v3/index.js | API Route | Registrasi rute /set-expired-billing dengan middleware pengamanan |
app/controllers/v3/billing_controller.js | Controller | Handler controller setExpiredBilling_V3 |
app/helpers/billing_helper.js | Service / Helper | Implementasi logic setExpiredBillingHelper, validasi guard, logging, dan tracking 312 |
app/models/billing_models.js | Model / Query | Fungsi query setBillingExpired |
app/models/tagihan_models.js | Model / Schema | Skema validasi Joi schemasSetExpiredBillingV3 dan update query checkExistingBilling |
tests/helpers/billing_helper.test.js | Unit Test | Test suite pengujian helper setExpiredBillingHelper |
Cuplikan Kode / Implementasi Inti
// app/helpers/billing_helper.js
export const setExpiredBillingHelper = async ({ trx_id_insw, alasan }) => {
let logging = [
null, null, Date.now() / 1000, null, 1, trx_id_insw,
{ trx_id_insw, alasan }, null, null, "Set Expired Billing",
null, true, null, null, "set_expired", "INSW"
];
try {
const dataSubbillerHdr = await getDataSubbillerByTrxIdINSW(trx_id_insw);
if (!dataSubbillerHdr) {
return {
success: false,
statusCode: status.notfound,
message: "Data billing dengan aju tersebut tidak ditemukan",
};
}
if (dataSubbillerHdr.fl_paid === true) {
return {
success: false,
statusCode: status.bad,
message: "Billing sudah dibayar dan tidak dapat diubah statusnya menjadi expired",
};
}
if (dataSubbillerHdr.fl_expired === true) {
return {
success: false,
statusCode: status.bad,
message: "Billing sudah dalam status expired",
};
}
const updatedBilling = await setBillingExpired(trx_id_insw);
if (!updatedBilling) {
return {
success: false,
statusCode: status.error,
message: "Gagal memperbarui status expired di database",
};
}
const recordTime = moment().tz("Asia/Jakarta").format("YYYY-MM-DD HH:mm:ss.SSS");
await insertTableTracking(trx_id_insw, recordTime, "312");
logging[0] = dataSubbillerHdr.id_subbiller_hdr;
logging[1] = dataSubbillerHdr.kode_billing_induk || null;
logging[9] = "Success Set Expired Billing >> setExpiredBilling_V3";
await insertLogRawQuery(logging);
return {
success: true,
statusCode: status.success,
message: "Berhasil mengubah status billing menjadi expired",
data: {
trx_id_insw: trx_id_insw,
kode_billing_induk: dataSubbillerHdr.kode_billing_induk || null,
fl_expired: true,
expired_date: updatedBilling.expired_date || recordTime,
alasan: alasan || null,
},
};
} catch (error) {
logging[3] = Date.now() / 1000;
logging[9] = "Error Set Expired Billing";
await insertLogRawQuery(logging);
return {
success: false,
statusCode: status.error,
message: "Internal server error",
error: error.message || error,
};
}
};5. Bukti Verifikasi Pengujian (QA / Unit Test / API Verification)
A. Verifikasi Unit Test (Development Stage)
- Test Suite File:
tests/helpers/billing_helper.test.js - Status Eksekusi CLI:
PASSED (7/7 tests passed)
RUN v4.1.11 D:/WORK/LNSW/single-billing-BE
✓ tests/helpers/billing_helper.test.js (7 tests) 18ms
Test Files 1 passed (1)
Tests 7 passed (7)
Start at 11:51:17
Duration 368ms (transform 88ms, setup 78ms, import 64ms, tests 18ms, 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 (Dipanggil langsung atau via integrasi pengangkut-backend) - Kebutuhan Akses / Role Akun: Token SSO valid (
Bearer Auth), Postman Collection Single Billing V3 - Set Expired Billing.postman_collection.json - Hasil Pengujian UAT:
PASSED - Catatan QA: Verifikasi 5 skenario endpoint set expired billing V3 dan tracking status 312 berhasil lolos pengujian UAT.
Skenario Pengujian QA (Test Scenarios)
Skenario 1 — Happy Path: Set Expired Billing Berhasil:
- Pre-kondisi: Terdapat data billing aktif di
billing.subbiller_hdrdenganfl_paid = falsedanfl_expired = false. - Trigger / Request:
POST/api/v3/set-expired-billingdengan body:json{ "trx_id_insw": "TRX-123456", "alasan": "Uji pembatalan warta oleh pengguna jasa" } - Ekspektasi Response: HTTP
200 OK,success: true,statusCode: 200,data.fl_expired: true. - Verifikasi Database / Log: Kolom
fl_expiredberubah menjaditrue, timestampexpired_dateterisi, riwayat tracking mencatat kode status312, dan query logging mencatat actionset_expired.
- Pre-kondisi: Terdapat data billing aktif di
Skenario 2 — Negative Test: Penolakan Set Expired pada Tagihan Lunas:
- Pre-kondisi: Data billing pada
billing.subbiller_hdrmemiliki statusfl_paid = true. - Trigger / Request:
POST/api/v3/set-expired-billingdengantrx_id_inswtagihan yang sudah lunas. - Ekspektasi Response: HTTP
400 Bad Request,message: "Billing sudah dibayar dan tidak dapat diubah statusnya menjadi expired". - Verifikasi Database / Log: State
fl_expiredtidak berubah, tidak ada mutasi state atau tracking312baru.
- Pre-kondisi: Data billing pada
Skenario 3 — Negative Test: Penolakan Set Expired pada Tagihan yang Sudah Expired:
- Pre-kondisi: Data billing pada
billing.subbiller_hdrmemiliki statusfl_expired = true. - Trigger / Request:
POST/api/v3/set-expired-billingdengantrx_id_inswtagihan yang sudah expired. - Ekspektasi Response: HTTP
400 Bad Request,message: "Billing sudah dalam status expired". - Verifikasi Database / Log: Tidak terjadi pembaruan timestamp
expired_dateberulang.
- Pre-kondisi: Data billing pada
Skenario 4 — Negative Test: Trx ID Tidak Ditemukan:
- Pre-kondisi: Mengirimkan
trx_id_inswacak/fiktif yang tidak terdaftar di database. - Trigger / Request:
POST/api/v3/set-expired-billingdengan body{"trx_id_insw": "TRX-FIKTIF-99999"}. - Ekspektasi Response: HTTP
404 Not Found,message: "Data billing dengan aju tersebut tidak ditemukan". - Verifikasi Database / Log: Tidak ada perubahan data apapun pada tabel database.
- Pre-kondisi: Mengirimkan
Skenario 5 — Verifikasi Re-request Billing Pasca Expired:
- Pre-kondisi: Billing lama atas nomor aju X telah berhasil di-set expired (
fl_expired = true). - Trigger / Request: Kirim request pembuatan/rekap billing baru atas nomor aju X melalui endpoint V3.
- Ekspektasi Response: Query
checkExistingBillingmengabaikan invoice lama yang telah expired berkat klausul(fl_expired is not true), dan permohonan billing baru berhasil diterbitkan tanpa terblokir tanggal expired Simponi. - Verifikasi Database / Log: Record baru berhasil diinsert ke
billing.subbiller_hdrdengan kode billing baru.
- Pre-kondisi: Billing lama atas nomor aju X telah berhasil di-set expired (
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."
}