[TASK-NON-BLOCKING-BILLING-DISPATCH-AND-RETRY-CONFIG] Isolasi Error Dispatcher Downstream INAPORT/SINKARKES Menggunakan Promise.allSettled dan Konfigurasi Percobaan Ulang Dinamis
Modul: Single Billing | Tipe: Bugfix | Stage Saat Ini:
PRODUCTION| Tanggal Dokumen: 2026-10-13
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.
1. Informasi Tugas & Referensi Bisnis
Task ID / Nomor Referensi:
TASK-NON-BLOCKING-BILLING-DISPATCH-AND-RETRY-CONFIGSumber Instruksi:
CHANGELOG.md (Release v1.9.2) & Internal HardeningDokumen Referensi / Lampiran: Tidak ada
Task / Dependensi Terkait (Cross-Service): Tidak ada
Modul Terkait:
Single BillingService / Repository:
single-billing-BEPIC Pengembang: Hermansyah Efendi
Latar Belakang & Ringkasan Kebutuhan: Pada alur penerbitan single billing SIMPONI, kegagalan transmisi ke downstream pihak ketiga (INAPORT dan SINKARKES) sebelumnya menggunakan
Promise.allsehingga me-reject seluruh proses rekap tagihan meskipun kode billing SIMPONI telah berhasil terbit. Selain itu, nilai maksimum percobaan/retry ke downstream masih bersifat hardcoded (5 untuk Inaport dan 3 untuk Sinkarkes) sehingga menyulitkan penyesuaian latensi di environment non-produksi, serta ditemukan transitive security vulnerability akibat dependensinodemon. Oleh karena itu, dilakukan isolasi dispatcher denganPromise.allSettled, penambahan konfigurasi retry dinamis via environment variable, pembersihan dependensi dev, serta rilis versiv1.9.2.Ruang Lingkup & Rincian Sub-Pekerjaan (Technical Scope & Milestones):
- [x] Penyelarasan payload INAPORT agar menggunakan kode billing induk pada skenario single billing terpadu (
d800d37) - [x] Isolasi error dispatcher INAPORT dan SINKARKES dengan safe wrapper dan
Promise.allSettledpada orchestrator tagihan (1ba23df) - [x] Penyertaan catatan kegagalan parsial pada response
pengirimanDataBillingtanpa merusak status HTTP 200 (1ba23df) - [x] Pembersihan dependensi
nodemondan mitigasi vulnerability vianpm audit fix(d2a90a6,c539815) - [x] Bump versi rilis
v1.9.2padapackage.json,package-lock.json, danCHANGELOG.md(ce44197) - [x] Penambahan konfigurasi dinamis
INAPORT_MAX_ATTEMPTSdanSINKARKES_MAX_RETRIESvia skemaENV_SCHEMA(ed0ebd8) - [x] Penambahan unit test Vitest untuk safe dispatching, retry env, dan validator skema (
1ba23df,ed0ebd8)
- [x] Penyelarasan payload INAPORT agar menggunakan kode billing induk pada skenario single billing terpadu (
2. Deployment Lifecycle
| # | Environment | Tanggal & Waktu | PIC | Status | Catatan |
|---|---|---|---|---|---|
| 1 | Development | 2026-10-13 10:57 | Hermansyah Efendi | DEPLOYED | Branch: development |
| 2 | Staging (UAT) | 2026-10-14 13:00 | Hermansyah Efendi / QA | DEPLOYED | Release Candidate via MR Staging !231. 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-10-13 | Development | Inisialisasi draft audit log isolasi error downstream billing dispatcher dan konfigurasi retry dinamis (release v1.9.2) | Hermansyah Efendi |
3. Analisis Teknis & Rincian Perubahan
Alur Logika & Bisnis:
- Isolasi Kegagalan Downstream (
dispatchBillingNotifications): Memisahkan pemanggilan transmisi downstreamsafePostBillingInaportdansafePostBillingSinkarkesmenggunakanPromise.allSettled. Jika salah satu atau kedua target eksternal mengalami timeout atau error jaringan, promise tidak melempar rejection yang membatalkan transaksi utama, sehingga status rekap tagihan dan kode billing SIMPONI yang telah terbit tetap tersimpan serta dikembalikan ke pemanggil (caller). - Observabilitas Status Pengiriman: Objek response
pengirimanDataBillingmenyertakan indikator booleantoInaportdantoSinkarkes. Apabila ada target yang gagal terkirim, response secara transparan menyertakan pesan pada fieldcatatan(contoh:"Pengiriman data billing ke INAPORT gagal") dengan tetap mempertahankan status HTTP200 OK. - Mapping Kode Billing Induk INAPORT: Memastikan payload yang dikirimkan ke INAPORT pada skenario single billing terpadu menggunakan kode billing induk SIMPONI hasil generate orkestrator, menjaga konsistensi identifikasi pembayaran lintas platform.
- Konfigurabilitas Percobaan Ulang Dinamis (Environment Configuration): Menghilangkan nilai konstan hardcoded (
maxAttempts = 5di Inaport danmaxRetries = 3di Sinkarkes). Transmisi ke downstream kini dikendalikan secara dinamis via variabel lingkungan:INAPORT_MAX_ATTEMPTS: Mengatur batas maksimal iterasi percobaan request ke INAPORT (fungsiloginAsINAPORT,sendBillingInaport, dansendFlagLunasInaport) dengan nilai default5.SINKARKES_MAX_RETRIES: Mengatur batas maksimal pengulangan (retry count) ke SINKARKES (fungsisendBillingSinkarkesdansendFlagLunasSinkarkes) dengan nilai default3.- Defensive Type Parsing: Helper
getMaxAttemptsdangetMaxRetriesmengimplementasikan fungsi ekstraksi defensifparseInt(..., 10). Apabila nilai variabel lingkungan tidak didefinisikan, bernilai bukan integer, atau<= 0, helper secara otomatis melakukan fallback ke nilai default (5untuk Inaport,3untuk Sinkarkes). - Skema Validasi Lingkungan (
ENV_SCHEMA): Kedua variabel didaftarkan padaapp/utils/env-validator.jsdengan opsi non-blocking{ required: false, default: "5" }dan{ required: false, default: "3" }, menjamin tersedianya nilai fallback yang aman tanpa memicu exception startup aplikasi.
- Hardening Keamanan & Dependensi: Menghapus dependensi
nodemonyang menyisakan import tak terpakai pada model tracking, menyelesaikan kerentanan transitive dependencies melaluinpm audit fix, serta penyesuaian versi rilisv1.9.2.
- Isolasi Kegagalan Downstream (
Rincian Konfigurasi Environment Variable:
Nama Variabel Tipe Data Status Default (Prod) Rekomendasi Staging Konsumen / Helper Deskripsi & Dampak Operasional INAPORT_MAX_ATTEMPTSInteger Opsional ( required: false)"5""1"app/helpers/inaport.js(getMaxAttempts)Batas maksimal perulangan request otentikasi login, penerbitan tagihan, dan flag lunas ke service INAPORT. Pada Staging diset 1agar fail-fast tanpa overhead retry perulangan.SINKARKES_MAX_RETRIESInteger Opsional ( required: false)"3""1"app/helpers/sinkarkes.js(getMaxRetries)Batas maksimal retry loop pengiriman data tagihan dan flag lunas ke service SINKARKES saat terjadi timeout/downstream down. Pada Staging diset 1(nilai integer positif minimal).Perubahan Database (DDL / DML):
sql-- Tidak ada perubahan skema database (DDL/DML) untuk task ini. -- Perubahan murni pada layer service logic (tagihan_helper), helper dispatcher downstream (inaport/sinkarkes), dan konfigurasi runtime schema.Kontrak API & Response Payload:
POST/api/v2/terima-rekap-tagihan&POST/api/v3/terima-rekap-tagihan- Deskripsi: Endpoint orkestrasi rekap data tagihan dan penerbitan single billing ke SIMPONI, INAPORT, dan SINKARKES.
- Autentikasi / Guard:
Bearer Token(authMiddleware) - Request Header:
Authorization: Bearer <token>Content-Type: application/json
- Request Body: Sesuai kontrak tagihan eksisting (multi-satker / single satker).
- Response Payload Sukses Penuh (
200 OK):json{ "status": 200, "kode_billing": { "kodeBillingPnbp": "820261012000001" }, "pengirimanDataBilling": { "toInaport": true, "toSinkarkes": true }, "message": "Berhasil merekap tagihan INSW dan meminta kode billing ke SIMPONI" } - Response Payload Sukses dengan Kegagalan Parsial Downstream (
200 OK):json{ "status": 200, "kode_billing": { "kodeBillingPnbp": "820261012000001" }, "pengirimanDataBilling": { "toInaport": false, "toSinkarkes": true, "catatan": "Pengiriman data billing ke INAPORT gagal" }, "message": "Berhasil merekap tagihan INSW dan meminta kode billing ke SIMPONI" }
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: !231
Daftar Commit Terkait (Multi-Commit Trail)
| Commit ID | Branch | Author | Tanggal & Jam | Ringkasan Perubahan |
|---|---|---|---|---|
ce44197ff85514430e9f8f3414ce10d708f937ed | development | Hermansyah Efendi | 2026-10-13 10:57 | chore(release): v1.9.2 |
ed0ebd8ac1a080a05ba3b806beb23f853c6fb368 | development | Hermansyah Efendi | 2026-10-12 20:41 | feat(helpers): make inaport and sinkarkes retry counts configurable via env |
c539815ecbe9f9a516a055697bb73b2526a9ef09 | development | Hermansyah Efendi | 2026-10-12 19:02 | fix(tracking): remove unused nodemon import |
2c62c0a4513cd6fa0f4a6bc69976eb440c73fdb6 | development | Hermansyah Efendi | 2026-10-12 18:29 | chore(release): v1.9.1 (superseded by v1.9.2) |
d2a90a680a1e571da9ebca7bcea5423da470e893 | development | Hermansyah Efendi | 2026-10-12 18:25 | chore(deps): remove nodemon and resolve security audit vulnerabilities |
1ba23df1774402f5f205ea428d45f9408f1201be | development | Hermansyah Efendi | 2026-10-12 18:25 | fix(tagihan): prevent inaport and sinkarkes dispatch errors from stopping orchestrator |
d800d3739d289fc79be307352f7f3c4c9dea6e9c | development | Hermansyah Efendi | 2026-09-30 19:27 | fix(billing): use parent billing code for inaport payload |
Cuplikan Kode / Implementasi Inti
// Cuplikan safe dispatcher pada app/helpers/tagihan_helper.js
const dispatchBillingNotifications = async (payloadInaport, payloadSinkarkes, trxIdInsw) => {
const [inaportSettled, sinkarkesSettled] = await Promise.allSettled([
safePostBillingInaport(payloadInaport, trxIdInsw),
safePostBillingSinkarkes(payloadSinkarkes, trxIdInsw),
]);
const sendToInaport = inaportSettled.status === "fulfilled" ? inaportSettled.value : false;
const sendToSinkarkes = sinkarkesSettled.status === "fulfilled" ? sinkarkesSettled.value : false;
if (sendToInaport) {
await updateFlInaport(trxIdInsw);
}
if (sendToSinkarkes) {
await updateFlSinkarkes(trxIdInsw);
}
const pengirimanDataBilling = {
toInaport: sendToInaport,
toSinkarkes: sendToSinkarkes,
};
const failedTargets = [];
if (!sendToInaport) failedTargets.push("INAPORT");
if (!sendToSinkarkes) failedTargets.push("SINKARKES");
if (failedTargets.length > 0) {
pengirimanDataBilling.catatan = `Pengiriman data billing ke ${failedTargets.join(" dan ")} gagal`;
logger.warn("Dispatched billing notifications with partial failure", {
trxIdInsw,
failedTargets,
toInaport: sendToInaport,
toSinkarkes: sendToSinkarkes,
});
}
return {
sendToInaport,
sendToSinkarkes,
pengirimanDataBilling,
};
};// Cuplikan parsing defensif INAPORT_MAX_ATTEMPTS pada app/helpers/inaport.js
const getMaxAttempts = () => {
const parsed = parseInt(process.env.INAPORT_MAX_ATTEMPTS, 10);
return Number.isInteger(parsed) && parsed > 0 ? parsed : 5;
};// Cuplikan parsing defensif SINKARKES_MAX_RETRIES pada app/helpers/sinkarkes.js
const getMaxRetries = () => {
const parsed = parseInt(process.env.SINKARKES_MAX_RETRIES, 10);
return Number.isInteger(parsed) && parsed > 0 ? parsed : 3;
};// Cuplikan registrasi skema default pada app/utils/env-validator.js
export const ENV_SCHEMA = {
// ...
INAPORT: { required: true },
INAPORT_API: { required: true },
INAPORT_MAX_ATTEMPTS: { required: false, default: "5" },
// ...
SINKARKES_API: { required: true },
SINKARKES_MAX_RETRIES: { required: false, default: "3" },
// ...
};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/helpers/sinkarkes.test.js,tests/utils/env-validator.test.js - Status Eksekusi CLI:
PASSED
// Cuplikan dari tests/helpers/tagihan_helper.test.js
it("should continue successfully with partial warning when inaport dispatch fails", async () => {
mockPostBillingInaport.mockRejectedValue(new Error("Inaport downstream timeout"));
mockPostBillingSinkarkes.mockResolvedValue(true);
const res = createMockResponse();
await processRekapTagihanOrchestrator(validReq, res, next, "v3");
expect(res.statusCode).toBe(200);
expect(res.body.pengirimanDataBilling.toInaport).toBe(false);
expect(res.body.pengirimanDataBilling.toSinkarkes).toBe(true);
expect(res.body.pengirimanDataBilling.catatan).toContain("INAPORT gagal");
});
// Cuplikan verifikasi skema default dari tests/utils/env-validator.test.js
it("should have INAPORT_MAX_ATTEMPTS and SINKARKES_MAX_RETRIES configured with default values", () => {
expect(ENV_SCHEMA.INAPORT_MAX_ATTEMPTS).toBeDefined();
expect(ENV_SCHEMA.INAPORT_MAX_ATTEMPTS.required).toBe(false);
expect(ENV_SCHEMA.INAPORT_MAX_ATTEMPTS.default).toBe("5");
expect(ENV_SCHEMA.SINKARKES_MAX_RETRIES).toBeDefined();
expect(ENV_SCHEMA.SINKARKES_MAX_RETRIES.required).toBe(false);
expect(ENV_SCHEMA.SINKARKES_MAX_RETRIES.default).toBe("3");
}); RUN v4.1.11 D:/WORK/LNSW/single-billing-BE
✓ tests/utils/env-validator.test.js (11 tests) 21ms
✓ tests/helpers/sinkarkes.test.js (17 tests) 28ms
✓ tests/helpers/tagihan_helper.test.js (17 tests) 50ms
Test Files 3 passed (3)
Tests 45 passed (45)
Start at 10:43:58
Duration 561ms (transform 396ms, setup 255ms, import 400ms, tests 98ms, 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&B2B Webhook + UI Verification - Kebutuhan Akses / Role Akun:
Token Bearer Pengangkut/Akses Database Single Billing (tabel billing & tracking) - Hasil Pengujian UAT:
PASSED - Catatan QA: Pengujian verifikasi end-to-end UAT dan isolasi downstream dispatcher telah lolos.
Skenario Pengujian QA (Test Scenarios)
Skenario 1 — Happy Path Penerbitan Billing Terpadu:
- Pre-kondisi: Data tagihan warta valid dan belum pernah diterbitkan billing. Service SIMPONI, INAPORT, dan SINKARKES dapat diakses normal.
- Trigger / Request:
POST /api/v3/terima-rekap-tagihandengan payload rekap valid. - Ekspektasi Response: HTTP
200 OK,kode_billingterisi,pengirimanDataBilling.toInaportbernilaitrue,pengirimanDataBilling.toSinkarkesbernilaitrue, tanpa fieldcatatan. - Verifikasi Database / Log: Kolom
fl_inaportdanfl_sinkarkespada tabelbillingbernilai1/true.
Skenario 2 — Partial Downstream Failure (INAPORT Down):
- Pre-kondisi: Endpoint mock/staging INAPORT dimatikan atau disimulasikan timeout, sementara SIMPONI dan SINKARKES normal.
- Trigger / Request:
POST /api/v3/terima-rekap-tagihandengan payload rekap valid. - Ekspektasi Response: HTTP
200 OK, kode billing SIMPONI tetap terbit,pengirimanDataBilling.toInaportbernilaifalse,pengirimanDataBilling.catatanberbunyi"Pengiriman data billing ke INAPORT gagal". - Verifikasi Database / Log: Log Winston/Loki mencatat level
warn(Dispatched billing notifications with partial failure), tracking record214tercatat pada tabel tracking.
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."
}