Memuat halaman kreator
rekomendasi

Satu provider mengirim status SUCCESS begitu callback diterima. Provider lain baru menganggap transaksi berhasil setelah dananya bisa dikonfirmasi. Provider berikutnya mengirim PENDING, melakukan retry tanpa urutan yang pasti, lalu berubah menjadi PAID beberapa menit kemudian.
Semua provider memakai kata yang mirip. Tidak satu pun menjanjikan hal yang sama.
Di titik ini, payment system mulai terlihat kompleks. Tim menyalahkan jumlah provider, perbedaan API, dan kombinasi flow sync dengan async. Padahal masalah yang lebih mendasar muncul lebih awal:
Apa arti berhasil di sistem kita sendiri?
Kalau pertanyaan itu belum dijawab, setiap integrasi baru akan menjawabnya dengan cara berbeda.
Status dari provider menjelaskan apa yang terjadi di dalam sistem mereka. Dia tidak otomatis menjelaskan apa yang boleh dilakukan produk kita.
Ketika provider mengirim SUCCESS, apakah order boleh langsung diproses? Apakah merchant sudah boleh melihat pendapatan? Apakah dana sudah settle? Apakah transaksi masih bisa dibatalkan?
Jawabannya bisa berbeda untuk QRIS, virtual account, kartu, e-wallet, atau transfer manual. Masalah dimulai ketika tim menyimpan status provider langsung sebagai state internal.
Provider A mengirim SUCCESS, jadi transaksi internal ikut menjadi SUCCESS. Provider B mengirim PAID, lalu sistem menambah state baru bernama PAID. Provider C punya COMPLETED, jadi adapter berikutnya memperkenalkan satu variasi lagi.
Tanpa sadar, bahasa provider menjadi bahasa bisnis kita. Yang menentukan lifecycle bukan lagi keputusan internal. Integrasi yang datang paling akhir yang menentukannya.
Begitu state provider bocor ke domain, logic bisnis mulai tumbuh mengelilinginya. Order boleh diproses kalau statusnya SUCCESS, kecuali provider tertentu memakai PAID.
Notification dikirim saat transaksi COMPLETED, kecuali untuk flow async yang harus menunggu callback kedua. Reconciliation menganggap transaksi selesai pada state yang berbeda untuk setiap metode pembayaran.
Setiap kondisi terlihat kecil saat ditambahkan. Semuanya punya alasan teknis yang masuk akal. Tapi setelah beberapa integrasi, tidak ada satu orang pun yang bisa menjawab dengan cepat apa arti state transaksi tanpa bertanya provider mana yang dipakai.
Kelihatannya seperti kompleksitas akibat scale. Sebenarnya itu complexity akibat vocabulary yang tidak pernah diputuskan.
Solusinya bukan memaksa semua provider mengikuti satu flow teknis yang identik. Kartu, QRIS, virtual account, dan transfer manual memang berbeda. Timing-nya berbeda. Cara konfirmasinya berbeda. Jaminan setelah callback juga berbeda.
Normalisasi yang baik tidak menghapus perbedaan itu. Dia mendefinisikan arti bisnis yang konsisten di atas perbedaan tersebut.
Sebagai contoh, sistem bisa memiliki lifecycle payment seperti:
INITIATED → AWAITING_PAYMENT → PROCESSING → PAID | FAILED | EXPIRED
Nama dan transisinya bukan standar universal. Setiap produk bisa membutuhkan model berbeda. Yang penting, PAID harus punya satu definisi internal. Bukan sekadar “provider mengirim success,” tapi kondisi bisnis yang cukup jelas untuk menentukan tindakan berikutnya.
Jalan menuju PAID boleh berbeda. Arti PAID tidak.
Kesalahan berikutnya adalah mencoba menjadikan satu enum sebagai jawaban untuk seluruh lifecycle uang.
Payment berhasil bukan berarti dana sudah settle. Refund dimulai bukan berarti payment kembali menjadi pending. Chargeback terjadi setelah payment berhasil, tapi bukan berarti sejarah keberhasilannya harus dihapus.
Kalau semua konsep itu dimasukkan ke satu field, state akan segera penuh dengan kombinasi seperti PAID_BUT_NOT_SETTLED, REFUND_PENDING, atau CHARGEBACK_AFTER_SUCCESS.
Satu sumber kebenaran tidak harus berarti satu status untuk semuanya. Payment, refund, dan settlement bisa memiliki lifecycle masing-masing. Yang harus satu adalah definisi dan ownership untuk setiap lifecycle tersebut.
Dengan pemisahan itu, sistem bisa menjawab tiga pertanyaan berbeda tanpa mencampurnya:
- Apakah customer sudah membayar?
- Apakah pengembalian dana sedang berjalan?
- Apakah dana sudah settle ke pihak yang berhak?
Adapter provider seharusnya tidak memperkenalkan state bisnis baru setiap kali menemukan istilah yang berbeda. Tugasnya adalah menerjemahkan event provider ke dalam transisi yang dipahami sistem.
Alurnya sederhana:
Provider event → adapter mapping → canonical transition → business action
Raw status dan payload provider tetap perlu disimpan untuk audit dan investigasi. Tapi keduanya bukan sumber kebenaran untuk perilaku produk. Domain internal yang menentukan apakah transisinya valid.
Kalau callback yang sama datang dua kali, transisi harus tetap idempotent. Kalau event datang terlambat atau tidak berurutan, sistem harus tahu apakah transisi itu masih sah. Kalau provider mengirim status yang tidak dikenal, sistem harus memperlakukannya sebagai exception yang terlihat, bukan diam-diam menciptakan behavior baru.
Provider membawa fakta eksternal. Sistem kita yang memberi fakta itu arti bisnis.
Integrasi baru pasti akan menemukan perbedaan. Pertanyaannya bukan apakah model canonical harus pernah berubah. Model yang baik tetap bisa salah atau tidak lengkap.
Pertanyaannya adalah jenis perbedaan apa yang ditemukan:
Apakah provider memperlihatkan kondisi bisnis yang memang belum pernah kita dukung?
Atau hanya memakai protocol dan vocabulary berbeda untuk kondisi yang sebenarnya sudah ada?
Kalau perbedaannya punya konsekuensi bisnis yang nyata, model canonical mungkin memang perlu diperluas. Kalau perbedaannya hanya detail provider, adapter yang harus menyerapnya.
Tanpa tes ini, setiap keanehan integrasi akan masuk ke core domain. Beberapa bulan kemudian, model internal tidak lagi menjelaskan bisnis. Dia hanya menjadi arsip semua provider yang pernah diintegrasikan.
Sebelum adapter baru ditulis, saya ingin lima hal ini punya jawaban:
1. Meaning: apa arti bisnis dari setiap canonical state?
2. Transition: perpindahan state mana yang valid, ditolak, atau aman diulang?
3. Mapping: status dan event provider masuk ke transisi yang mana?
4. Source of truth: di mana canonical state disimpan, dan data mentah apa yang tetap disimpan untuk audit?
5. Ownership: siapa yang memutuskan ketika provider memperkenalkan kondisi yang tidak cocok dengan model?
Itulah kontrak minimum integrasi. Bukan interface yang bisa menampung semua kemungkinan. Bukan daftar lengkap semua status provider.
Kontrak minimum adalah batas yang menjaga perbedaan provider tetap berada di tempatnya.
Suka konten Arya Nugroho?
Dukung kreator agar terus berkarya.