Skip to content

[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:

  1. 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.
  2. Update baris Staging saat naik ke UAT. Ubah "Stage Saat Ini" di header menjadi STAGING.
  3. Update baris Production saat naik ke Prod. Ubah "Stage Saat Ini" di header menjadi PRODUCTION.
  4. Update entry di README.md setiap kali status berubah. -->

1. Informasi Tugas & Referensi Bisnis ​

  • Task ID / Nomor Referensi: TASK-SET-EXPIRED-BILLING-V3

  • Sumber 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):

  • Modul Terkait: Single Billing

  • Service / Repository: single-billing-BE

  • PIC 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-billing yang dilengkapi validasi status ketat (mencegah pembatalan billing yang sudah lunas atau sudah expired), pencatatan riwayat tracking status 312, dan penyesuaian query checkExistingBilling dengan 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-billing dengan middleware doIntrospection dan validasi Joi schemasSetExpiredBillingV3.
    • [x] Milestone 2: Thin Controller Handler — Penambahan controller setExpiredBilling_V3 pada app/controllers/v3/billing_controller.js.
    • [x] Milestone 3: Service Business Logic & Tracking — Implementasi logika bisnis setExpiredBillingHelper pada app/helpers/billing_helper.js dengan status guard fl_paid dan fl_expired, pencatatan status tracking code 312, dan raw query audit logging.
    • [x] Milestone 4: Database Model Query — Penambahan query model setBillingExpired pada app/models/billing_models.js (UPDATE billing.subbiller_hdr SET fl_expired = true).
    • [x] Milestone 5: Existing Billing Filter Adjustment — Pembaruan query checkExistingBilling pada app/models/tagihan_models.js dengan filter (fl_expired is not true) guna membebaskan kunci permohonan billing baru.

2. Deployment Lifecycle ​

#EnvironmentTanggal & WaktuPICStatusCatatan
1Development2026-08-27 12:18Hermansyah EfendiDEPLOYEDBranch: development (Commit: ec51278)
2Staging (UAT)2026-10-14 13:00Hermansyah Efendi / QADEPLOYEDRelease 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]
3Production2026-10-14 20:06achmad fauziDEPLOYEDBranch: 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 ​

TanggalEnvironmentPerubahanPIC
2026-10-14ProductionStatus diupdate ke PRODUCTION (DEPLOYED, CLOSED), rilis Release v1.9.2 via MR !232Hermansyah Efendi
2026-08-27DevelopmentInisialisasi draft audit log dari context block backend (commit ec51278)Hermansyah Efendi
2026-09-07DevelopmentStandardisasi QA Playbook (5 skenario uji terstruktur), penyelarasan dependensi silang V3, dan penguatan dokumentasi lifecycle billingHermansyah 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 HTTP 404 Not Found.
      • Jika fl_paid === true, tolak dengan HTTP 400 Bad Request: "Billing sudah dibayar dan tidak dapat diubah statusnya menjadi expired".
      • Jika fl_expired === true, tolak dengan HTTP 400 Bad Request: "Billing sudah dalam status expired".
    • Update Status & Tracking:
      • Menjalankan setBillingExpired untuk mengeset fl_expired = true dan memperbarui expired_date dengan timestamp sistem saat ini pada tabel billing.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".
    • Pelepasan Kunci pada Existing Billing Check:
      • Query checkExistingBilling pada app/models/tagihan_models.js diperbarui dengan menyertakan kondisi and (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.
  • 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()::date
  • Kontrak 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"
      }

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 IDBranchAuthorTanggal & JamRingkasan Perubahan
ec51278developmentHermansyah Efendi2026-08-27 12:18feat(billing): implement set expired billing endpoint v3

File yang Berubah ​

Path FileKategoriKeterangan
app/routes/v3/index.jsAPI RouteRegistrasi rute /set-expired-billing dengan middleware pengamanan
app/controllers/v3/billing_controller.jsControllerHandler controller setExpiredBilling_V3
app/helpers/billing_helper.jsService / HelperImplementasi logic setExpiredBillingHelper, validasi guard, logging, dan tracking 312
app/models/billing_models.jsModel / QueryFungsi query setBillingExpired
app/models/tagihan_models.jsModel / SchemaSkema validasi Joi schemasSetExpiredBillingV3 dan update query checkExistingBilling
tests/helpers/billing_helper.test.jsUnit TestTest suite pengujian helper setExpiredBillingHelper

Cuplikan Kode / Implementasi Inti ​

javascript
// 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)
text
 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) ​
  1. Skenario 1 — Happy Path: Set Expired Billing Berhasil:

    • Pre-kondisi: Terdapat data billing aktif di billing.subbiller_hdr dengan fl_paid = false dan fl_expired = false.
    • Trigger / Request: POST /api/v3/set-expired-billing dengan 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_expired berubah menjadi true, timestamp expired_date terisi, riwayat tracking mencatat kode status 312, dan query logging mencatat action set_expired.
  2. Skenario 2 — Negative Test: Penolakan Set Expired pada Tagihan Lunas:

    • Pre-kondisi: Data billing pada billing.subbiller_hdr memiliki status fl_paid = true.
    • Trigger / Request: POST /api/v3/set-expired-billing dengan trx_id_insw tagihan 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_expired tidak berubah, tidak ada mutasi state atau tracking 312 baru.
  3. Skenario 3 — Negative Test: Penolakan Set Expired pada Tagihan yang Sudah Expired:

    • Pre-kondisi: Data billing pada billing.subbiller_hdr memiliki status fl_expired = true.
    • Trigger / Request: POST /api/v3/set-expired-billing dengan trx_id_insw tagihan yang sudah expired.
    • Ekspektasi Response: HTTP 400 Bad Request, message: "Billing sudah dalam status expired".
    • Verifikasi Database / Log: Tidak terjadi pembaruan timestamp expired_date berulang.
  4. Skenario 4 — Negative Test: Trx ID Tidak Ditemukan:

    • Pre-kondisi: Mengirimkan trx_id_insw acak/fiktif yang tidak terdaftar di database.
    • Trigger / Request: POST /api/v3/set-expired-billing dengan 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.
  5. 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 checkExistingBilling mengabaikan 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_hdr dengan kode billing baru.
Bukti Response Verifikasi Staging (Diisi saat UAT) ​
json
{
  "status": "PASSED",
  "note": "Pengujian verifikasi UAT dan validasi integrasi berhasil lolos, rilis production v1.9.2 telah live."
}

Single Source of Truth (SSOT) Arsitektur & Perubahan Teknis Backend