[FEAT-MANUAL-BILLING-REQUEST] Penyesuaian Alur Bisnis Tagihan Pengangkut (Manual Request Billing & Idempotency)
Modul: Pengangkut | Tipe: Feature | Stage Saat Ini:
STAGING| Tanggal Dokumen: 2026-08-31
1. Informasi Tugas & Referensi Bisnis
Task ID / Nomor Referensi:
FEAT-MANUAL-BILLING-REQUESTSumber Instruksi: Penyesuaian Alur Bisnis Tagihan Pengangkut (Manual Request Billing & Idempotency)
Dokumen Referensi / Lampiran: manual_billing_request_postman_collection.json
Task / Dependensi Terkait (Cross-Service): Tidak ada
Modul Terkait:
PengangkutService / Repository:
pengangkut-backendPIC Pengembang: Hermansyah Efendi
Latar Belakang & Ringkasan Kebutuhan: Penyesuaian alur bisnis penerbitan Single Billing pada modul Pengangkut Kapal. Sebelumnya, pembuatan billing dipicu secara otomatis (auto-billing) pada handler
POST /tagihandanPOST /warta-kapal/send/putResponse. Alur ini diubah menjadi manual billing request yang dipicu secara eksplisit oleh pengguna/agen setelah semua prasyarat kelayakan (eligibility) terpenuhi (dokumen LK3 disetujui, tagihan Inaportnet aktif/lengkap, dan tagihan Sinkarkes aktif/lengkap). Untuk mencegah duplicate billing requests atau race conditions saat penekanan tombol berulang, diimplementasikan mekanisme Token-Bound Idempotency Key berbasis Redis dengan validasix-idempotency-keyberdurasi 5 menit yang terikat pada hash sesi token autentikasi pengguna.Ruang Lingkup & Rincian Sub-Pekerjaan (Technical Scope & Milestones):
- [x] Penghapusan pemicu auto-billing otomatis pada handler
POST /tagihandanPOST /warta-kapal/send/putResponse. - [x] Implementasi reusable
BillingEligibilityServiceuntuk verifikasi status LK3, Tagihan Inaportnet, dan Tagihan Sinkarkes. - [x] Implementasi endpoint
GET /inquiry-billinguntuk pengecekan status kelayakan dan rincian draft/bypassed tagihan. - [x] Implementasi Idempotency Service & Middleware berbasis Redis token session hash (
POST /generate-idempotency-key). - [x] Implementasi endpoint
POST /request-billingdilindungi Idempotency Guard (x-idempotency-key). - [x] Penulisan Vitest unit test suite komprehensif untuk seluruh billing service v2 dan idempotency service.
- [x] Penghapusan pemicu auto-billing otomatis pada handler
2. Deployment Lifecycle
| # | Environment | Tanggal & Waktu | PIC | Status | Catatan |
|---|---|---|---|---|---|
| 1 | Development | 2026-06-25 17:01 | Hermansyah Efendi | DEPLOYED | Branch: development (Merged from feat/manual-billing-request) |
| 2 | Staging (UAT) | 2026-09-09 11:34 | achmad fauzi | DEPLOYED | Release Candidate v1.18.2 via MR !2080. Co-deployed: [TASK-DRAGONFLYDB-MIGRATION, TASK-SPLIT-BILLING-202608, FEAT-MANUAL-BILLING-REQUEST, MAINT-SUPERSET-SUMMARY-SHIP-SYNC-SECURITY, REFACTOR-DEPRECATE-POST-STATUS-SERVICE-MIGRATION, TASK-PINO-LOGGER-REFACTOR, ND-304-LNSW.34-2026, ND-256-LNSW.34-2026, ND-183-LNSW.34-2026] |
| 3 | Production | — | — | PENDING | — |
Riwayat Update Log
| Tanggal | Environment | Perubahan | PIC |
|---|---|---|---|
| 2026-09-09 | Staging | Promosi ke STAGING via MR !2080 (Release Candidate v1.18.2) | Hermansyah Efendi |
| 2026-08-31 | Development | Initial log created from backend context block | Hermansyah Efendi |
3. Analisis Teknis & Rincian Perubahan
Alur Logika & Bisnis:
- Decoupling Auto-Billing: Menghapus pemanggilan otomatis pembuatan billing dari alur warta kapal dan penerimaan tagihan K/L. Tagihan sekarang hanya dikonsolidasi dan menunggu perintah eksplisit dari pengguna.
- Reusable Billing Eligibility Service (
BillingEligibilityService): Layanan terpusat untuk memverifikasi prasyarat penerbitan billing. Mengecek kelengkapan dan persetujuan dokumen LK3 (doc=7,code=2), ketersediaan tagihan Warta Kemenhub (Inaportnet), serta tagihan Sinkarkes Kemenkes. Mendukung integrasi status tagihanACTIVEdanBYPASSED(split billing). - Inquiry Billing (
InquiryBillingService): EndpointGET /inquiry-billingmengevaluasi kelayakan pengajuan aju kapal (resolusi nomor aju keberangkatan102dan kedatangan101), mengembalikan status billing (is_billing,is_generated,is_expired,is_paid), status kelayakan (is_eligible,requirements), serta rinciandraft_tagihandanbypassed_tagihan. - Token-Bound Idempotency Guard (
IdempotencyService&validateIdempotency): Menggantikan mekanisme captcha lama dengan token-bound idempotency key system. Kunci dihasilkan viaPOST /generate-idempotency-key, disimpan di Redis dengan TTL 5 menit (300 detik), dan terikat pada SHA-256 hash dari Bearer Token pengguna serta service number. Saat endpointPOST /request-billingdipanggil dengan headerx-idempotency-key, middleware mengonsumsi (single-use) kunci tersebut secara atomik untuk mencegah double billing generation. - Manual Request Billing (
RequestBillingService): EndpointPOST /request-billingyang memvalidasi kelayakan viaBillingEligibilityService, mengonsumsi idempotency key, dan memicu pembuatan kode billing Simponi via Single Billing Service. - Daftar File yang Berubah:
app/routers/billing_route.js[Router]app/routers/idempotency.router.js[Router]app/controllers/v2/billing.controller.js[Controller]app/controllers/v2/idempotency.controller.js[Controller]app/services/inquiry-billing.service.js[Service]app/services/request-billing.service.js[Service]app/services/billing-eligibility.service.js[Service]app/services/idempotency.service.js[Service]app/middlewares/validate-idempotency.js[Middleware]app/validations/billing.validation.js[Validation]app/validations/idempotency.validation.js[Validation]app/models/billing_models.js[Model]tests/services/inquiry-billing.service.test.js[Unit Test]tests/services/request-billing.service.test.js[Unit Test]tests/services/billing-eligibility.service.test.js[Unit Test]tests/services/idempotency.service.test.js[Unit Test]
Perubahan Database (DDL / DML):
sql-- Tidak ada migration file DDL/DML yang terdeteksi untuk task ini. -- Seluruh state session dan kunci idempotency dikelola secara in-memory/cache pada Redis cluster.Kontrak API & Response Payload:
1.
GET/inquiry-billing- Deskripsi: Mengambil status kelayakan (eligibility) pengajuan billing beserta rincian draft tagihan aktif dan tagihan yang di-bypass.
- Autentikasi / Guard:
Bearer Token(doIntrospection,checkAccessAgentByServiceNumber) - Request Query Params:
serviceNumber:string(Nomor Aju Keberangkatan / Kedatangan, misal102...atau101...)
- Response Payload (
200 OK):json{ "data": { "billing_status": { "is_billing": true, "is_generated": false, "is_expired": false, "id_billing": null, "is_paid": false, "total_tagihan": 1500000, "is_split_billing": false, "payment_detail": null, "billing_detail": null }, "eligibility": { "is_eligible": true, "message": "Semua syarat terpenuhi", "requirements": { "lk3": { "required": true, "completed": true, "name": "LK3" }, "inaport": { "required": true, "completed": true, "status": "ACTIVE", "name": "Tagihan Warta" }, "sinkarkes": { "required": true, "completed": true, "status": "ACTIVE", "name": "Tagihan Sinkarkes" } } }, "draft_tagihan": [], "bypassed_tagihan": [] }, "message": "Data billing ditemukan", "statusCode": 200, "success": true } - Response Error (
400 Bad Request):json{ "data": null, "message": "Data kedatangan tidak ditemukan untuk service number ini", "statusCode": 400, "success": false, "errors": [] }
2.
POST/generate-idempotency-key- Deskripsi: Membuat single-use idempotency key berdurasi 5 menit yang terikat pada hash token otentikasi user dan service number.
- Autentikasi / Guard:
Bearer Token(doIntrospection,checkAccessAgentByServiceNumber) - Request Body:json
{ "type": "GENERATE_BILLING", "serviceNumber": "102202608310001" } - Response Payload (
200 OK):json{ "data": { "idempotency_key": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d", "expires_in": 300, "valid_until": "2026-08-31T09:02:00.000Z" }, "message": "Idempotency key berhasil dibuat", "statusCode": 200, "success": true } - Response Error (
400 Bad Request):json{ "data": null, "message": "Validation Failed", "statusCode": 400, "success": false, "errors": [ { "field": "body.type", "message": "Tipe idempotency tidak valid" } ] }
3.
POST/request-billing- Deskripsi: Mengajukan pembuatan tagihan Single Billing secara manual setelah kelayakan terpenuhi dan validasi idempotency lolos.
- Autentikasi / Guard:
Bearer Token(doIntrospection,validateIdempotency('GENERATE_BILLING'),checkAccessAgentByServiceNumber) - Request Header:
x-idempotency-key:9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
- Request Body:json
{ "serviceNumber": "102202608310001" } - Response Payload (
200 OK):json{ "data": { "kode_billing": "820260831000123", "trx_id_insw": "102202608310001" }, "message": "Proses generate billing berhasil dilakukan", "statusCode": 200, "success": true } - Response Error (
400 Bad Request):json{ "data": null, "message": "Idempotency key tidak valid atau sudah kedaluwarsa, silakan muat ulang halaman", "statusCode": 400, "success": false, "errors": [] }
4. Bukti Teknis (Technical Proof & Audit Trail)
- Repository:
pengangkut-backend - Branch Utama:
feat/manual-billing-request - Merge Request / PR: MR !2080 (Release Candidate v1.18.2)
Daftar Commit Terkait (Multi-Commit Trail)
| Commit ID | Branch | Author | Tanggal & Jam | Ringkasan Perubahan |
|---|---|---|---|---|
9a670759 | feat/manual-billing-request | Hermansyah Efendi | 2026-06-25 17:01 | refactor(billing): use double negation for parent billing code validation |
fe12d383 | feat/manual-billing-request | Hermansyah Efendi | 2026-06-25 16:06 | fix(billing): require parent billing code for generated billing status |
81399b5f | feat/manual-billing-request | Hermansyah Efendi | 2026-06-25 10:51 | feat(billing): allow dynamic billing version switching via environment variable |
2cb9de06 | feat/manual-billing-request | Hermansyah Efendi | 2026-06-08 15:35 | feat(idempotency): add idempotency validation for warta-kapal and cop submission |
fcefa795 | feat/manual-billing-request | Hermansyah Efendi | 2026-06-04 19:32 | feat(idempotency): replace captcha with token-bound idempotency key system |
450a18b1 | feat/manual-billing-request | Hermansyah Efendi | 2026-06-04 15:00 | feat(billing): add inquiry-billing endpoint and billing eligibility service |
Cuplikan Kode / Implementasi Inti
// app/services/inquiry-billing.service.js
import connection from '../connection.js';
import { BadRequestError } from '../error/response-error.js';
import { checkAjuKedatangan, checkDataBilling, dataDraftTagihan } from '../models/billing_models.js';
import { billingEligibilityService } from './billing-eligibility.service.js';
class InquiryBillingService {
async getByServiceNumber(serviceNumber) {
let serviceNumberKeberangkatan = serviceNumber;
let serviceNumberKedatangan = null;
if (serviceNumber.substr(0, 3) === "102") {
serviceNumberKedatangan = await checkAjuKedatangan(serviceNumber);
if (!serviceNumberKedatangan) {
throw new BadRequestError("Data kedatangan tidak ditemukan untuk service number ini");
}
}
// 1. Cek kelayakan billing via reusable service
const eligibility = await billingEligibilityService.checkEligibility(serviceNumber);
// 2. Cek billing status
const dataSingleBilling = serviceNumberKeberangkatan ? await checkDataBilling(serviceNumberKeberangkatan) : [];
const isBillingActive = eligibility.requirements.inaport.required || eligibility.requirements.sinkarkes.required;
const hasBillingRecord = dataSingleBilling.length > 0 && !!dataSingleBilling[0].kode_billing_induk;
const isBillingExpired = hasBillingRecord && dataSingleBilling[0].is_expired === true;
const isPaid = hasBillingRecord && dataSingleBilling[0].fl_paid === true;
return {
billing_status: {
is_billing: isBillingActive,
is_generated: hasBillingRecord && !isBillingExpired,
is_expired: isBillingExpired,
id_billing: (hasBillingRecord && !isBillingExpired) ? dataSingleBilling[0].trx_id_insw : null,
is_paid: isPaid
},
eligibility: {
is_eligible: eligibility.is_eligible,
message: eligibility.message,
requirements: eligibility.requirements
}
};
}
}
export const inquiryBillingService = new InquiryBillingService();5. Bukti Verifikasi Pengujian (QA / Unit Test / API Verification)
A. Verifikasi Unit Test (Development Stage)
- Test Suite File:
tests/services/inquiry-billing.service.test.js,tests/services/request-billing.service.test.js,tests/services/billing-eligibility.service.test.js,tests/services/idempotency.service.test.js - Status Eksekusi CLI:
PASSED
RUN v4.1.10 D:/WORK/LNSW/pengangkut-backend
✓ tests/services/request-billing.service.test.js (9 tests) 23ms
✓ tests/services/billing-eligibility.service.test.js (17 tests) 16ms
✓ tests/services/inquiry-billing.service.test.js (6 tests) 14ms
✓ tests/services/idempotency.service.test.js (11 tests) 15ms
Test Files 4 passed (4)
Tests 43 passed (43)
Start at 08:57:11
Duration 661ms (transform 269ms, setup 0ms, import 696ms, tests 69ms, environment 1ms)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:
Full E2E (UI + Backend) - Kebutuhan Akses / Role Akun:
Role Agen Pengangkut / PPJK - Postman Collection File: manual_billing_request_postman_collection.json
- Hasil Pengujian UAT:
PENDING - Catatan QA: Unit test suite (43 tests) telah lulus secara empiris pada local test runner. Menunggu deployment ke environment Staging untuk pengujian E2E menggunakan Postman Collection & UI Flow.
A. Konfigurasi Variabel Postman Collection
| Variable Name | Default Value / Template | Fungsi & Deskripsi |
|---|---|---|
baseUrl | https://demo-backend.insw.go.id/pengangkut/api | Base URL utama service Pengangkut Core |
singleBillingBaseUrl | https://demo-backend.insw.go.id/single-billing | Base URL service Single Billing |
apiKey | dSGZSMBOtdh4dWLYwW7qT38nVroknMHn | API Key pengangkut |
bearerToken | KZ6IlAA4NxocmCozKC71xaYymS7sMAgM_94h0gftFnG | Bearer token otentikasi user |
singleBillingToken | 9kuq3cU3AXa5XGPV2Pyec1kGZesDJPxX | Bearer token otentikasi API DJA Simponi / Single Billing |
inswKemenhubKey | xxx | Key untuk header insw-kemenhub-key pada respon LK3 |
inswKemenkesKey | xxx | Key otentikasi integrasi Kemenkes |
billingKemenhubKey | xxx | Key untuk simponi_req.key (Tagihan Inaportnet Kemenhub KL 022) |
billingKemenkesKey | xxx | Key untuk simponi_req.key (Tagihan Sinkarkes Kemenkes KL 024) |
serviceNumber | 102019B83694C | Nomor Aju / Service Number Pengangkut valid (Keberangkatan) |
serviceNumberKedatangan | 101019543877B | Nomor Aju / Service Number Pengangkut valid (Kedatangan) |
invalidServiceNumber | 1029999999999 | Service Number buatan untuk uji data tidak ditemukan |
idempotencyKey | "" | Diisi otomatis oleh skrip pengujian Postman saat Key Gen |
kodeBilling | 820260710740418 | Kode billing target untuk simulasi callback pelunasan Simponi DJA |
B. Skenario Simulasi Injeksi Data Prasyarat (Data Seeding)
Sebelum menguji alur kelayakan billing, jalankan 5 cURL berikut untuk mengisi data prasyarat di database:
1. Simulasi Approval LK3 Kemenhub (POST /warta-kapal/send/putResponse)
curl --location 'https://demo-backend.insw.go.id/pengangkut/api/warta-kapal/send/putResponse' \
--header 'Content-Type: application/json' \
--header 'insw-kemenhub-key: xxx' \
--data '{
"no_aju": "102019B83694C",
"tipe": "LK3",
"nomor_layanan": "BDN.IDMAK.2609.000140",
"status": 2,
"message": "",
"nomor_produk": "SL003.IDMAK.0926.000142",
"waktu_approve": "2026-09-09 11:10:22",
"link_produk": "https://simpadu-inaportnet.dephub.go.id/index.php/document/pdfs/lk3_nakhoda/SL003.DN.IDMAK.2609.000139",
"kode_dermaga": ""
}'2. Simulasi Tagihan Inaportnet Kemenhub (POST /tagihan)
curl --location 'https://demo-backend.insw.go.id/pengangkut/api/tagihan' \
--header 'Content-Type: application/json' \
--data '{
"simponi_req": {
"method": "billingcode",
"key": "xxx",
"data": [
{
"header": [
"102019B83694C",
"INV.IDMAK.2609.000003",
"",
null,
"022",
"04",
"413439",
"F",
"1",
834660,
"Labuh-PU-PT. JELAJAH LAUT NUSANTARA",
"413439",
"014844831042001"
],
"detail": [
[
"Labuh-PU-PT. JELAJAH LAUT NUSANTARA",
"000401",
"201615C",
"425513",
90,
9274,
"Per GT/15 Hari",
834660,
"19",
"51",
0
]
]
},
{
"header": [
"102019B83694C",
"INV.IDMAK.2609.000009",
"",
null,
"022",
"04",
"287473",
"F",
"1",
175000,
"VTS-PU-IDMAK-PT. JELAJAH LAUT NUSANTARA",
"287473",
"014844831042001"
],
"detail": [
[
"VTS-PU-IDMAK-PT. JELAJAH LAUT NUSANTARA",
"000930",
"201615C",
"425517",
175000,
1,
"Per Kapal",
175000,
"19",
"51",
0
]
]
}
]
}
}'3. Simulasi Tagihan Sinkarkes Kemenkes Keberangkatan (POST /tagihan)
curl --location 'https://demo-backend.insw.go.id/pengangkut/api/tagihan' \
--header 'Content-Type: application/json' \
--data '{
"simponi_req": {
"method": "billingcode",
"key": "xxx",
"data": {
"header": [
"102019B83694C",
"58830",
"SIxxxxx",
"2026-09-08 07:23:44",
"024",
"05",
"416145",
"F",
"1",
85000,
"PT JELAJAH LAUT NUSANTARA",
"416145",
"014844831042001"
],
"detail": [
[
"PT JELAJAH LAUT NUSANTARA",
"001111",
"2024045",
"425314",
85000,
1,
"Per Pemeriksaan Per Kapal",
85000,
"19",
"51"
]
]
}
}
}'4. Simulasi Tagihan Sinkarkes Kemenkes Kedatangan (POST /tagihan)
curl --location 'https://demo-backend.insw.go.id/pengangkut/api/tagihan' \
--header 'Content-Type: application/json' \
--data '{
"simponi_req": {
"method": "billingcode",
"key": "xxx",
"data": {
"header": [
"101019543877B",
"58830",
"SIxxxxx",
"2026-09-08 07:23:44",
"024",
"05",
"416145",
"F",
"1",
85000,
"PT JELAJAH LAUT NUSANTARA",
"416145",
"014844831042001"
],
"detail": [
[
"PT JELAJAH LAUT NUSANTARA",
"001111",
"2024045",
"425314",
85000,
1,
"Per Pemeriksaan Per Kapal",
85000,
"19",
"51"
]
]
}
}
}'5. Simulasi Notifikasi Pembayaran Lunas Simponi DJA ke Single Billing (POST /single-billing/dja/activeinquiry_v2)
curl --location 'https://demo-backend.insw.go.id/single-billing/dja/activeinquiry_v2' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer 9kuq3cU3AXa5XGPV2Pyec1kGZesDJPxX' \
--data '{
"simponi_req": {
"method": "activeinquiry_v2",
"data": [
"8186790", // [0] ID Tagihan / Trx Simponi DJA
"2026090915908818403", // [1] Trx ID INSW / ID Billing Single Billing
"820260710740418", // [2] Kode Billing Induk Simponi
"FBDC361QVML892TK", // [3] Nomor Transaksi Penerimaan Negara (NTPN)
"630523721197", // [4] Nomor Transaksi Bank (NTB) / Ref Bank
"2026-09-09 14:44:42", // [5] Tanggal & Jam Pembayaran (YYYY-MM-DD HH:mm:ss)
"520008000990", // [6] Nomor Rekening Kas Negara / Bank Persepsi
"7015", // [7] Kode Channel Bayar / Kode Bank Persepsi
"2026-09-09 00:00:00" // [8] Tanggal Buku / Tanggal Setor Kas Negara
]
}
}'C. Matriks Pengujian Fisik (13 Skenario Detailed)
1. Positive Cases (Happy Path)
GET /inquiry-billing(Status Eligible):- Trigger / Request: Query
serviceNumber=102019B83694Csesudah Data Seeding diselesaikan. - Ekspektasi Response: HTTP
200 OK,eligibility.is_eligible = true,eligibility.message = "Semua syarat terpenuhi". - Verifikasi Database / Log: Seluruh persyaratan LK3, Tagihan Warta, dan Tagihan Sinkarkes berstatus
completed = true.
- Trigger / Request: Query
POST /generate-idempotency-key(Valid Key Generation):- Trigger / Request: Body
{"type":"GENERATE_BILLING", "serviceNumber":"102019B83694C"}. - Ekspektasi Response: HTTP
200 OK, menghasilkan UUIDidempotency_keydenganexpires_in: 300(5 menit). - Verifikasi Database / Log: Kunci tersimpan di Redis dengan namespace token hash sesi user.
- Trigger / Request: Body
POST /request-billing(Eksekusi Billing Berhasil):- Trigger / Request: Header
x-idempotency-key: <UUID>, Body{"serviceNumber":"102019B83694C"}. - Ekspektasi Response: HTTP
200 OK,message = "Proses generate billing berhasil dilakukan", Kode Billing Simponi terbit. - Verifikasi Database / Log: Idempotency key terkonsumsi dan terhapus dari Redis; record
subbiller_hdrberhasil terbuat.
- Trigger / Request: Header
2. Negative Cases — Validation & Authorization Errors
GET /inquiry-billing(Missing QueryserviceNumber):- Trigger / Request: Dipanggil tanpa query parameter
serviceNumber. - Ekspektasi Response: HTTP
400 Bad Request,message = "Validation Failed", error detail pada fieldquery.serviceNumber.
- Trigger / Request: Dipanggil tanpa query parameter
GET /inquiry-billing(Data Service Number Tidak Ditemukan):- Trigger / Request: Query
serviceNumber=1029999999999. - Ekspektasi Response: HTTP
400 Bad Request,message = "Data kedatangan tidak ditemukan untuk service number ini".
- Trigger / Request: Query
POST /generate-idempotency-key(Action Type Tidak Valid):- Trigger / Request: Body
{"type":"INVALID_ACTION_TYPE", "serviceNumber":"102019B83694C"}. - Ekspektasi Response: HTTP
400 Bad Request,message = "Validation Failed".
- Trigger / Request: Body
POST /generate-idempotency-key(Tanpa Header Authorization):- Trigger / Request: Request dikirim tanpa header
Authorization: Bearer <token>. - Ekspektasi Response: HTTP
401 Unauthorized/400 Bad Request,message = "Token otentikasi tidak ditemukan".
- Trigger / Request: Request dikirim tanpa header
3. Negative Cases — Ineligibility & Business Logic Errors
POST /request-billing(Service Number Belum Eligible / Belum LK3):- Trigger / Request: Request billing pada
serviceNumberyang belum disetujui LK3-nya. - Ekspektasi Response: HTTP
400 Bad Request,message = "LK3 Belum Terselesaikan".
- Trigger / Request: Request billing pada
POST /request-billing(Duplicate Submission / Kode Billing Masih Aktif):- Trigger / Request: Request billing diulang ketika kode billing aktif sudah terbit di database.
- Ekspektasi Response: HTTP
400 Bad Request,message = "Billing atas nomor aju layanan 102019B83694C belum kedaluwarsa atau masih berlaku. Silahkan cek data tagihan atas billing yang sudah ada.".
4. Negative Cases — Idempotency & Security Boundary Errors
POST /request-billing(Missing Headerx-idempotency-key):- Trigger / Request: Request billing dikirim tanpa header
x-idempotency-key. - Ekspektasi Response: HTTP
400 Bad Request,message = "Header x-idempotency-key wajib disertakan".
- Trigger / Request: Request billing dikirim tanpa header
POST /request-billing(Key Palsu / Expired):- Trigger / Request: Header
x-idempotency-key: 99999999-9999-9999-9999-999999999999. - Ekspektasi Response: HTTP
400 Bad Request,message = "Idempotency key tidak valid atau sudah kedaluwarsa, silakan muat ulang halaman".
- Trigger / Request: Header
POST /request-billing(Payload Mismatch / Service Number Berbeda):- Trigger / Request: Key dibentuk untuk
serviceNumberA, tetapi dikirim dengan body{"serviceNumber": "serviceNumber B"}. - Ekspektasi Response: HTTP
400 Bad Request,message = "Idempotency Key tidak valid!".
- Trigger / Request: Key dibentuk untuk
POST /request-billing(Replay Attack / Re-use Key yang Sudah Dikonsumsi):- Trigger / Request: Mengirimkan request billing dua kali berturut-turut menggunakan
x-idempotency-keyyang sama. - Ekspektasi Response: Request pertama
200 OK(key terhapus dari Redis), Request kedua400 Bad Request(Idempotency key tidak valid atau sudah kedaluwarsa...).
- Trigger / Request: Mengirimkan request billing dua kali berturut-turut menggunakan
Bukti Response Verifikasi Staging (Diisi saat UAT)
{
"status": "PENDING",
"note": "Menunggu deployment ke environment Staging untuk eksekusi skenario uji QA."
}