Payment Gateway Virtual Account, Episode 0

Payment Gateway:
Fitur Standar, Tantangan Desain, dan Trade-off

Endy Muhardin, ArtiVisi Intermedia

Dari Komentar Vlog #48

Seorang penonton, programmer di daerah, bertanya di kolom komentar:

"Saya jarang dapat project serumit payment gateway. Boleh dijelaskan fitur standar yang harus ada?"


Apa Itu Payment Gateway Virtual Account

Secara umum

Payment gateway VA menghubungkan aplikasi penagihan dengan bank. Tugasnya:

  • Mendaftarkan tagihan beserta nomor VA-nya
  • Menjawab inquiry bank saat pembayar memasukkan nomor VA
  • Mencatat pembayaran, lalu memberi tahu aplikasi penagihan
  • Mencocokkan catatan pembayaran dengan laporan settlement dari bank

Yang dibahas di video ini

  • Payment gateway buatan ArtiVisi: multi-bank, open source, dan berjalan di server institusi sendiri (self-hosted)
  • Account Receivable atau aplikasi hulu cukup berintegrasi sekali dengan satu API
  • Adapter untuk tiap bank menangani protokol bank masing-masing

Tanpa rekening perantara. Uang pembayar masuk dari bank langsung ke rekening institusi, sedangkan gateway hanya mengelola datanya: tagihan, nomor VA, notifikasi pembayaran, dan hasil rekonsiliasi.

Cakupan Produk

Termasuk

  • Hanya kanal Virtual Account untuk penagihan masuk
  • Satu API untuk banyak bank
  • Rekonsiliasi terpadu lintas bank
  • Open source, Apache 2.0

Tidak termasuk

  • Peran agregator (Midtrans, Xendit, DOKU) sebagai perantara
  • Switching kartu: tidak ada data kartu sehingga di luar scope PCI DSS
  • QRIS: MDR (merchant discount rate) persentase tidak ekonomis untuk nominal SPP
  • Disbursement keluar, kartu kredit, e-wallet: produk lain

Pihak yang Terlibat

Aplikasi hulupenerbit tagihanRegistrasiAkademik (SPP)Aplikasi sekolah(Sawala)AccountReceivablesubledger piutangconsumer gatewayHub notifikasiemail, SMSPembayarPayment gateway (self-hosted)Consumer APIWebhooklewat outboxRegistry VA,chargeRekonsiliasiAdapterbank(bankconnector)BankMaybank · SNAPBSI · REST/JSONCIMB · SOAP/XMLRekening penampunganinstitusipiutangbuka charge dan VApembayaran masukoutbox notifikasitagihan dan pengingattransfer ke nomor VAinquiry,paymentCSV settlement dari portal bankdanaGaris putus-putus: aliran dana dan berkas settlement

Dana mengalir dari bank langsung ke rekening institusi, sedangkan gateway hanya mengelola data. Account Receivable bersifat opsional karena aplikasi hulu yang sudah mengelola tagihan sendiri dapat langsung menjadi consumer.

Self-hosted versus Provider SaaS

AspekGateway self-hostedProvider, model agregatorbawaan di semua providerProvider, model fasilitatorMidtrans: Fasilitator · DOKU: Direct · Xendit: Switcher · Faspay: Direct
Hubungan kontrakInstitusi berkontrak langsung dengan tiap bank. Penyedia gateway hanya menjadi penghubung dan konsultan.Institusi berkontrak dengan provider saja. VA menggunakan company code milik provider.Institusi berkontrak langsung dengan bank dan mendapat company code sendiri. Provider menjadi penghubung (BCA menyebutnya IT Gateway) dan membantu pengurusan berkas.
Jalur integrasi APIConsumer → gateway di server institusi → API tiap bankConsumer → API provider → API bankConsumer → API provider → API bank
Aliran danaBank → rekening institusiBank → rekening provider → dicairkan ke institusi, T+0 hingga 3 hari kerjaBank → rekening institusi, sesuai perjanjian dengan bank
Biaya per transaksiBiaya bank saja (contoh BSI: Rp 2.000)Tarif flat yang sudah mencakup biaya bank (Midtrans, DOKU: Rp 4.000 + PPN)Biaya bank ditambah fee provider (Midtrans, Xendit: Rp 2.000 + PPN)
Server dan dataMilik institusiDi providerDi provider
Source codeOpen source (Apache 2.0)ProprietaryProprietary

Sumber: halaman harga dan dokumentasi Midtrans, DOKU, Xendit, Faspay, serta halaman Virtual Account BCA, diakses 19 September 2026. Fee fasilitator Midtrans berasal dari S&K yang lebih lama; tarif Xendit per Desember 2024 dan direvisi pada 2026. BCA mewajibkan rekening BCA atas nama badan usaha, termasuk pada model agregator.

Alur Dasar

Aplikasi huluAccount ReceivableHub notifikasiPayment gatewayBankPembayar1. terbitkan tagihan2. buka charge + nomor VAcharge aktif, VA terdaftar3. kirim tagihanemail / SMS berisi nomor VA4. masukkan nomor VA5. inquiry: tagihan siapa, berapa?nama dan jumlah, dalam 2–3 detiktampilkan nama dan jumlah6. konfirmasi dan bayar7. payment: dibayar, ref #123ack8. webhook PAYMENT_RECEIVED (HMAC)Akhir hari: bank mengkredit rekening institusi setelah dipotong biaya9. CSV settlement dari portal bank10. webhook susulan dari rekonsiliasiGaris putus-putus: balasan atau berkas

Lima Istilah yang Wajib Dipahami

IstilahArti di sini
Virtual Account (VA)Nomor rekening per pembayar yang mengarahkan transfer ke rekening institusi sekaligus menandai tagihan mana yang dibayar. Tidak ada uang yang mengendap di dalamnya.
InquiryPanggilan sinkron bank saat pembayar mengetik nomor VA. Jawabannya nama dan jumlah tagihan, atau NOT_FOUND, dalam 2–3 detik.
Payment callbackPanggilan kedua dari bank setelah pembayar mengonfirmasi: gateway mencatat pembayaran, menjawab ack, lalu mengirim webhook ke consumer.
SettlementBank mengkredit rekening institusi atas pembayaran VA hari itu, minus biaya per transaksi.
RekonsiliasiPencocokan akhir hari berkas settlement bank dengan catatan pembayaran gateway untuk memulihkan notifikasi hilang dan menandai selisih, duplikat, dan kredit tak dikenal.

Peta Fitur = Migrasi Flyway V1–V10

V1 baseline15 tabel: escrow_account, consumer, charge, virtual_account, payment, reconciliation_run, reconciliation_discrepancy, audit_event, webhook_delivery, snap_access_token, snap_external_id, bank_ip_rule, role, role_permission, operator
V2 seedRole bawaan: ADMIN, OPERATOR, AUDITOR
V3, V4Deskripsi dan nomor tagihan pada charge
V5 analysis_reportLaporan analisis yang dihitung di luar, disimpan sebagai deret
V6 device_authDevice code dan token (RFC 8628) untuk CLI dan agent
V7 settlement_feeBiaya settlement per escrow account; NULL berarti belum diisi sehingga rekonsiliasi tidak dapat dijalankan
V8 bank_journal_numberNomor jurnal bank pada payment
V9 bank_callbackSalinan utuh setiap callback dari bank, termasuk field yang belum dimodelkan
V10 retire INSTALLMENTJenis charge cicilan dihapus; gagal jika masih ada barisnya
Escrow accountRelasi biller bank: adapter, kredensial, rekening settlement, ruang nomor VA

src/main/resources/db/migration/ di repo. Setiap migrasi mencatat keputusan produk.

Fitur 1: Adapter Bank

BankProtokolBentuk endpoint
MaybankSNAP v1.0.2 (BI/ASPI) via REST/JSON3 endpoint: access-token, inquiry, payment
BSIREST/JSON proprietary1 endpoint, field action: inquiry, payment, reversal
CIMBSOAP/XML proprietaryServlet Spring-WS di path terpisah

Protokol dan cara autentikasi ketiga bank berbeda-beda. Adapter ditentukan oleh escrow account, satu per relasi bank.

SNAP menetapkan token bertanda tangan RSA dan tanda tangan HMAC-SHA512 untuk setiap transaksi. Kode: src/main/java/com/artivisi/paymentgateway/adapter/{maybank, bsi, cimb, snap}

Fitur 2: Sisi Consumer

Consumer API

  • POST /api/charges, lalu cancel, extend, reprice
  • Autentikasi X-Client-Id dan X-Client-Secret
  • 201 jika charge baru dibuat, 200 jika consumer reference sudah ada

Webhook lewat transactional outbox

  • Event webhook dan pembayaran ditulis dalam satu transaksi
  • Empat event: PAYMENT_RECEIVED, CHARGE_PAID, PAYMENT_REVERSED, CHARGE_CANCELLED
  • Webhook ditandatangani HMAC-SHA256, dikirim ulang dengan backoff eksponensial
  • Semaphore terpisah per consumer agar endpoint yang lambat tidak menghambat consumer lain

Fitur 3: Jenis Tagihan

Menurut banyaknya pembayaran

  • OPEN: nominal bebas dan dapat dibayar berkali-kali, tanpa pernah lunas. Contoh: rekening donasi.
  • CLOSED: nominal tetap dan hanya dibayar sekali. Tagihan ditutup begitu lunas. Contoh: uang pendaftaran.
  • INSTALLMENT: total tagihan dilunasi dalam beberapa kali pembayaran. Contoh: SPP yang dicicil.

Implementasi di payment gateway ArtiVisi

  • OPEN: diimplementasikan di gateway
  • CLOSED: diimplementasikan di gateway
  • INSTALLMENT: tidak diimplementasikan di gateway, melainkan di Account Receivable. Account Receivable menyimpan jadwal cicilan, lalu mengubah nominal satu tagihan CLOSED di gateway pada setiap periode cicilan.

Fitur 4: Nomor VA

Dua Konsep Penting: Idempotency dan Rekonsiliasi

Idempotency

Permintaan yang sama boleh datang lebih dari sekali, tetapi hanya dicatat sekali.

  • Diperlukan karena bank mengirim ulang notifikasi pembayaran jika tidak mendapat jawaban tepat waktu, dan consumer bisa mengirim ulang permintaan setelah koneksinya terputus.
  • Tanpa idempotency, satu pembayaran bisa tercatat dua kali, atau satu tagihan dibuat dua kali.
  • Caranya: setiap permintaan membawa nomor referensi yang unik, dan gateway tidak mencatat nomor referensi yang sama untuk kedua kalinya.

Rekonsiliasi

Mencocokkan catatan pembayaran di gateway dengan uang yang benar-benar masuk ke rekening, menurut laporan settlement dari bank.

  • Diperlukan karena notifikasi dari bank bisa hilang di jaringan, notifikasi bisa terkirim walaupun uangnya tidak masuk, dan uang yang masuk sudah dipotong biaya bank.
  • Tanpa rekonsiliasi, selisih antara catatan dan isi rekening baru terlihat saat bagian keuangan menutup buku.
  • Dilakukan setiap hari untuk setiap rekening penampungan.

Fitur 5 dan 6: Idempotency dan Rekonsiliasi di Gateway

Fitur 5: tiga kunci idempotency

  • Notifikasi pembayaran dari bank: referensi bank, unik per nomor VA (uq_payment_va_reference)
  • Permintaan pembuatan tagihan dari consumer: referensi consumer (uq_charge_consumer_reference)
  • Permintaan SNAP: external id, unik per hari (tabel snap_external_id)
  • Gateway memeriksa referensi bank sebelum mengunci tagihan, lalu memeriksanya lagi setelah tagihan terkunci. Pemeriksaan kedua mendeteksi notifikasi ganda yang tiba hampir bersamaan.
  • Jika bank mengirim ulang notifikasi, gateway memberikan jawaban yang sama seperti pada notifikasi pertama, tanpa mencatat pembayaran baru.

Fitur 6: rekonsiliasi

  • Operator mengunduh berkas settlement (CSV) dari portal bank, lalu mengunggahnya ke gateway. Berkas yang diunggah harus mengikuti format kolom yang baku, yaitu nomor VA, referensi bank, jumlah, dan waktu transaksi. Pemetaan dari format asli tiap bank ke format baku tersebut masih dikembangkan.
  • Setiap kredit dari bank dicocokkan dengan pembayaran yang tercatat di gateway.
  • Jika uang sudah masuk tetapi notifikasinya tidak pernah sampai, gateway mencatat pembayaran tersebut dan mengirim webhook yang tertunda.
  • Jika notifikasi sudah diterima tetapi uangnya tidak ada di laporan bank, pembayaran tersebut ditandai untuk diperiksa.
  • Gateway juga menandai selisih jumlah, kredit ganda, dan kredit untuk nomor VA yang tidak dikenal.
  • Biaya settlement tiap bank wajib diisi, termasuk bila nilainya nol.

Fitur 7: Admin dan Keamanan

Tantangan Desain

Setiap Bank Berbeda

Yang berbedaContohDampaknya pada kode
ProtokolSNAP (REST/JSON), REST/JSON proprietary, SOAP/XMLSatu adapter per bank, masing-masing dengan endpoint dan parser sendiri
Autentikasi dan tanda tangan requestToken bertanda tangan RSA, checksum, atau tanpa tanda tangan di dalam requestVerifikasi dilakukan di adapter. Jika bank tidak menandatangani request, gateway menambah pengamanan di jaringan, misalnya allowlist IP.
Nama field, kode respons, format tanggalIstilah, kode jawaban, dan format waktu yang berbeda-bedaAdapter menerjemahkan semuanya ke model internal yang sama untuk inquiry dan pembayaran
Format nomor VA dan biayaPrefix, jumlah digit, dan biaya per transaksiDisimpan sebagai konfigurasi per escrow account, bukan sebagai konstanta di kode
Berkas settlementSusunan kolom CSV dari portal bankDipetakan ke format baku sebelum rekonsiliasi
Tempat nomor VA disimpanDi database gateway, atau di database bankGateway harus mendukung kedua model

Karena perbedaan antarbank ditangani oleh adapter dan konfigurasi per escrow account, logika tagihan, pembayaran, dan rekonsiliasi di inti aplikasi tidak bergantung pada bank tertentu.

Batas Tanggal Mengikuti Zona Waktu Bank

Bank menyusun laporan settlement per tanggal menurut WIB, sedangkan server umumnya menyimpan waktu dalam UTC, yang tertinggal tujuh jam dari WIB.


Jika batas tanggal rekonsiliasi dihitung dalam UTC, pembayaran pukul 04.38 WIB tanggal 25 jatuh ke tanggal 24. Akibatnya, rekonsiliasi tanggal 25 menemukan uang masuk yang tidak tercatat di gateway, sedangkan rekonsiliasi tanggal 24 menemukan pembayaran yang uangnya tidak ada di laporan bank.


Satu pembayaran yang sah menghasilkan dua selisih palsu. Karena itu, batas tanggal rekonsiliasi harus dihitung dalam zona waktu bank.

Keamanan dan PCI DSS

PCI DSS

  • Nomor VA dan nomor rekening bukan data pemegang kartu, sehingga gateway ini berada di luar scope PCI DSS.
  • Meski begitu, langkah pengamanan teknis dari PCI DSS v4.0 tetap diterapkan sebagai standar minimum: tanpa kredensial bawaan, secret terenkripsi, password disimpan dengan bcrypt, akun dikunci setelah gagal login berulang, TOTP, dan audit log.
  • Data kartu sama sekali tidak boleh disimpan sebelum CDE (cardholder data environment) dirancang. Begitu data kartu tersimpan, seluruh sistem masuk scope PCI DSS.

Integrasi bank dan pipeline

  • Kekuatan autentikasi tiap bank berbeda. Jika bank tidak menandatangani request, gateway menambah pengamanan di jaringan, misalnya allowlist IP.
  • Pemeriksaan otomatis di CI: SpotBugs, CodeQL, Dependency-Check, SonarCloud, dan ZAP.

Bank Menentukan Tempat Penyimpanan Nomor VA

Disimpan di gatewayDisimpan di bank
Cara kerjaBank meneruskan setiap inquiry ke gateway, lalu mengirim notifikasi pembayaran.Gateway mendaftarkan setiap nomor VA ke bank saat tagihan dibuat. Bank memeriksa pembayaran dengan datanya sendiri dan hanya mengirim notifikasi pembayaran.
Contoh bankMaybank, BSI, CIMBBNI
Membatalkan atau mengubah nominal tagihanCukup mengubah data di gateway, tanpa memanggil bankPerubahan harus dikirim ke bank, dan bisa terjadi bersamaan dengan pembayaran yang sedang diproses bank (race condition). Selisihnya diselesaikan lewat rekonsiliasi.
Batas waktu pembayaranDiperiksa gateway saat inquiryDikirim ke bank, lalu bank yang memeriksanya
Status implementasiTiga adapter sudah dibuatSudah disiapkan di model data. Adapternya dibuat saat bank seperti BNI diintegrasikan.

Karena setiap bank menentukan modelnya sendiri, gateway harus mendukung kedua model.

Trade-off: Monolit Relasional atau Event-Sourced

KriteriaMonolit + PostgreSQLEvent-sourced, Kafka + RocksDB
Menjawab inquiry dalam 2–3 detikDijawab langsung di dalam proses aplikasiHarus menunggu event tersimpan di Kafka lebih dahulu
Dua pembayaran untuk tagihan yang samaBaris tagihan dikunci dalam satu transaksi database, sehingga pembayaran kedua menunggu pembayaran pertama selesaiTidak ada lock, sehingga pembayaran ganda baru terdeteksi dan ditandai setelah terjadi
ThroughputBeban puncak institusi hanya puluhan hingga ratusan TPS, masih dalam kapasitas PostgreSQLKemampuan scale-out Kafka belum dibutuhkan pada beban sebesar itu
Operasional1 aplikasi + 1 PostgreSQLTambah 1 broker Kafka, dan lebih banyak kode yang harus ditulis serta dirawat

Pilihannya: monolit dengan PostgreSQL. Proses perbandingannya, termasuk benchmark yang sempat menyesatkan, dibahas di episode 1 hingga 4.

Di Luar Cakupan Saat Ini

FiturStatusAlasan
QRIS, kartu kredit, e-walletDibuat jika klien membutuhkanUntuk nominal sebesar SPP, MDR persentase lebih mahal dibandingkan tarif flat VA
Disbursement (transfer keluar)Produk terpisahDitangani Disbursement Platform
Pengelolaan dana: saldo, deposit, penarikanTidak direncanakanUang pembayar masuk dari bank langsung ke rekening institusi, sedangkan gateway hanya mengelola data pembayaran
Penyusunan nomor VA di gatewayBelum dibuatSaat ini consumer yang menyusun nomor VA, lalu gateway memeriksa prefix dan jumlah digitnya
Settlement pull APIMenunggu bankBank yang sudah terintegrasi belum menyediakan API untuk menarik data settlement
Membaca PDF rekening koranTidak direncanakanBerkas CSV dari portal bank lebih dapat diandalkan
Circuit breaker otomatis pada webhookTidak direncanakanSebagai gantinya, tersedia kill-switch (penghenti manual) dan replay (pengiriman ulang)
Nilai default konfigurasiTidak direncanakanTidak ada bank default maupun adapter fallback. Jika konfigurasi tidak lengkap, aplikasi berhenti dengan pesan kesalahan yang jelas.

Checklist untuk yang Mau Membuat Sendiri

Episode berikutnya


github.com/artivisi/payment-gateway · artivisi.com/products/payment-gateway/