Multi-Gateway Webhook & Double-Entry Reconcile
Idempotent Webhook Processing & Fault-Tolerant Ledger Outbox
Pengolahan webhook multi-gateway (Midtrans, Xendit, Bank Direct VA) dengan transactional outbox pattern dan double-entry ledger untuk rekonsiliasi bebas selisih.
Diagram Alur Data & Arsitektur Sistem
Visual InteraktifArsitektur Idempotent Webhook & Zero-Variance Settlement
Distributed lock Redlock dengan transactional outbox untuk menjamin integritas ledger
Payment Gateway
Webhook Callback (Midtrans/Xendit)
HMAC Guard
Signature Verification
Redis Redlock
Distributed Mutex (<1ms)
PostgreSQL Ledger
Atomic Double-Entry Tx
Outbox Dispatcher
CDC Event to Unblock Queue
Problem & Latar Belakang Kebutuhan
Saat siklus penagihan bulanan tanggal 1–5, puluhan ribu callback webhook pembayaran masuk serentak dari berbagai gateway. Gangguan jaringan atau retry agresif dari gateway sering menyebabkan race condition, double-crediting saldo, serta selisih antara pencatatan billing internal dan mutasi bank pihak ketiga.
Implementasi Reconcile Controller NestJS & Transactional Outbox
Setiap webhook diverifikasi keabsahan signature HMAC-nya, lalu dilakukan penguncian terdistribusi menggunakan Redis Redlock berbasis hash payload dan invoice ID. Jika lock berhasil, mutasi saldo dicatat ke tabel double-entry ledger PostgreSQL dalam satu transaksi atomik bersama event di tabel transactional outbox. Background worker NestJS membaca outbox untuk memicu unblock subscriber secara deterministik.
Aspek Skema Double-Entry Ledger PostgreSQL
Tabel PostgreSQL ledger_entries mencatat mutasi berpasangan (Debit: Gateway Clearing Account, Credit: Accounts Receivable Customer). Transaksi menggunakan level isolation READ COMMITTED dengan locking SELECT FOR UPDATE pada row invoice pelanggan.
Keamanan, Idempotensi & Kepatuhan SLA
Validasi signature HMAC-SHA256 pada header webhook, distributed lock per invoice ID dengan TTL 2 detik, serta pencatatan audit log transaksi finansial yang immutable (append-only).
// Idempotent Multi-Gateway Webhook Controller & Outbox Pattern di NestJS
@Controller('billing/webhooks')
@UseGuards(HmacSignatureGuard)
export class BillingWebhookController {
constructor(
private readonly redlockService: RedlockService,
@InjectDataSource() private readonly dataSource: DataSource,
) {}
@Post(':gateway')
async handleGatewayWebhook(
@Param('gateway') gateway: string,
@Body() payload: PaymentWebhookDto,
@Res() res: Response,
) {
const lockKey = `lock:webhook:${payload.invoiceId}`;
// 1. Acquire Distributed Mutex via Redis Redlock (TTL 2000ms)
let lock: Lock;
try {
lock = await this.redlockService.acquire([lockKey], 2000);
} catch {
// Gracefully absorb duplicate retry callbacks
return res.status(HttpStatus.ACCEPTED).json({ status: 'duplicate_event_absorbed' });
}
try {
// 2. Transaksi Atomik: Settle Invoice + Double-Entry Ledger + Transactional Outbox
await this.dataSource.transaction('READ COMMITTED', async (manager) => {
const invoice = await manager.createQueryBuilder(Invoice, 'inv')
.setLock('pessimistic_write')
.where('inv.id = :id', { id: payload.invoiceId })
.getOne();
if (invoice?.status === 'SETTLED') return;
await manager.update(Invoice, payload.invoiceId, {
status: 'SETTLED',
settledAt: new Date(),
});
await manager.insert(LedgerEntry, {
invoiceId: payload.invoiceId,
debitAccount: `GATEWAY_CLEARING_${gateway.toUpperCase()}`,
creditAccount: 'AR_CUSTOMER',
amount: payload.amount,
});
await manager.insert(OutboxEvent, {
topic: 'billing.invoice.settled',
payload: JSON.stringify(payload),
status: 'PENDING',
});
});
return res.status(HttpStatus.OK).json({ status: 'settled' });
} finally {
await lock.release().catch(() => null);
}
}
}Live Simulator & Execution Sandbox
PostGIS GiST R-Tree ST_DWithin query executed in 3.8ms. Matched ODP-FATMAWATI-42 (84.2m).
Estimated optical attenuation: -17.4 dBm. Port 4 of 8 available. Redis lock acquired with 24h TTL.
HMAC-SHA256 verified. Redis Redlock acquired. Invoice INV-2026-0982 settled with zero ledger variance.
ST_DWithin R-Tree matched ODP in 4.1ms (dist: 84.2m). Optical loss -17.4 dBm. Splitter port locked with 24h Redis TTL.
Eksplorasi Fitur Terkait Lainnya
Lihat Semua Fitur →PostGIS Spatial R-Tree & ODP Fiber Allocation
Validasi geospasial real-time titik koordinat pelanggan, kalkulasi jarak kabel drop optik, estimasi degradasi redaman (dBm), dan auto-lock port splitter ODP tanpa full-table scan.
Sales Order SPK & Automated Provisioning Lifecycle
Otomasi penerbitan Surat Perintah Kerja (SPK) pasang baru pelanggan, validasi KTP/NIK, reservasi port ODP selama 24 jam, dan pembuatan akun PPPoE/RADIUS otomatis di NestJS.
Multi-Tenant Branch & Multi-Level Approval Engine
Pemisahan data operasional antar cabang regional ISP berbasis Role-Based Access Control (RBAC) & Scoped Tenant Guard serta pipeline persetujuan multi-level otomatis (Sales -> NOC Kelayakan -> Finance Margin -> Dispatch).
SLA Ticketing & CS Incident Assignment Management
Sistem manajemen tiket gangguan pelanggan berbasis web portal yang diinput oleh tim Customer Service (CS), dikelompokkan berdasarkan kategori insiden, lalu di-assign ke tim teknis terkait dengan pelacakan batas waktu resolusi SLA.