BorderPay← Blog

Blog

Cara Menyimpan Status Pembayaran dengan Benar di Database

Oleh Admin5 mnt baca

Bagian dari topik: Integrasi API Pembayaran untuk Developer

Cara Menyimpan Status Pembayaran dengan Benar di Database
Foto: panumas nikhomkhai / Pexels

Menyimpan status pembayaran di database terdengar sepele sampai kamu menemukan pesanan yang dikirim padahal belum dibayar, atau produk digital yang tidak terbuka padahal uang sudah masuk. Akar masalahnya hampir selalu sama: aplikasi mengandalkan sinyal yang salah untuk menentukan apakah sebuah transaksi benar-benar lunas. Merancang model status yang benar sejak awal jauh lebih murah daripada membereskan data yang sudah kacau.

Inti persoalannya adalah menentukan satu sumber kebenaran dan memetakan transisi status secara disiplin. Artikel ini membahas bagaimana memodelkan status pembayaran (pending, paid, expired, failed), mengapa webhook harus jadi acuan alih-alih halaman yang muncul di browser pelanggan, dan kolom apa saja yang perlu kamu simpan agar setiap transaksi bisa ditelusuri dan dicocokkan.

Model Status sebagai State Machine

Anggap setiap pembayaran sebagai objek yang berpindah antar keadaan (state). Keadaan yang perlu kamu tangani:

StatusArtiTransisi lanjut
pendingPembayaran dibuat, menunggu dibayarpaid, expired, failed
paidDana berhasil diterimaterminal
expiredKode kedaluwarsa sebelum dibayar (QRIS 60 menit, VA 24 jam)terminal
failedPembayaran gagal diprosesterminal

Aturan penting: status terminal tidak boleh berubah lagi. Sekali paid, jangan pernah membiarkannya kembali ke pending. Ini melindungimu dari webhook yang datang terlambat atau tidak berurutan. Cara paling aman adalah menuliskan transisi yang diizinkan secara eksplisit di kode, dan menolak transisi yang tidak valid, bukan menimpa status begitu saja.

Simpan status sebagai kolom enum atau string terbatas, jangan boolean is_paid saja. Boolean tidak bisa membedakan "belum dibayar" dari "gagal" atau "kedaluwarsa", padahal ketiganya menuntut tindakan berbeda. Dengan status yang eksplisit, kamu juga bisa membangun laporan dan dasbor operasional yang jauh lebih berguna, misalnya menghitung berapa banyak transaksi yang kedaluwarsa versus gagal, yang menjadi petunjuk apakah ada masalah di sisi pengalaman pengguna.

Perlakukan waktu sebagai bagian dari status. Simpan kapan tiap perpindahan terjadi, bukan hanya keadaan akhirnya. Riwayat transisi ini sangat berharga saat kamu perlu menyelidiki keluhan, misalnya pelanggan yang mengaku sudah membayar tetapi pesanannya belum berubah. Dengan jejak waktu, kamu bisa memastikan apakah webhook memang tiba dan kapan.

Webhook adalah Sumber Kebenaran

Ketika pelanggan selesai membayar, browser mereka biasanya diarahkan kembali ke halaman "terima kasih". Godaan besarnya: menandai pesanan lunas begitu halaman itu terbuka. Jangan.

Halaman kembalian (redirect) di browser tidak bisa dipercaya sebagai bukti pembayaran. Pelanggan bisa menutup tab sebelum diarahkan, jaringannya putus, atau seseorang membuka URL sukses itu secara langsung tanpa benar-benar membayar. Callback browser hanya cocok untuk urusan tampilan, misalnya menampilkan "pembayaran sedang diproses".

Sumber kebenaran yang sah adalah webhook — notifikasi server-ke-server yang dikirim penyedia pembayaran, ditandatangani dengan HMAC. Di browser kamu hanya boleh memegang reference_id sebagai token publik untuk menanyakan status; API key tetap di server dan tidak pernah dikirim ke klien. Jadi alur yang benar:

  1. Browser menampilkan status sementara berdasarkan reference_id.
  2. Webhook payment.paid masuk ke server, diverifikasi tanda tangannya.
  3. Server memperbarui status di database menjadi paid dan menjalankan aksi bisnis.

Prinsip verifikasi dan penanganan webhook dibahas tuntas di webhook pembayaran.

Kolom yang Wajib Kamu Simpan

Selain status, simpan cukup data agar setiap transaksi bisa ditelusuri dan dicocokkan dengan laporan mitra:

  • reference_id — pengenal transaksi dari BorderPay. Jadikan unik dan diindeks; ini kunci untuk mencocokkan webhook dengan baris di database.
  • status — keadaan saat ini dari state machine.
  • amount dan currency — nominal yang diharapkan.
  • payment_method — QRIS atau Virtual Account.
  • created_at dan paid_at — waktu pembuatan dan waktu lunas.
  • raw_event (opsional) — payload webhook terakhir untuk audit.

Dengan reference_id sebagai kunci, ketika webhook tiba kamu cukup mencari baris yang cocok, memeriksa transisi status yang diizinkan, lalu memperbaruinya. Data ini juga menjadi fondasi saat kamu perlu mencocokkan catatan internal dengan laporan penyedia, sebuah proses yang dibahas di rekonsiliasi transaksi.

Beda Menyimpan Status dari Idempotensi

Dua hal ini sering tertukar, padahal berbeda peran:

  • Menyimpan status menjawab: "sekarang transaksi ini ada di keadaan apa?" Fokusnya pada model data dan transisi yang benar.
  • Idempotensi menjawab: "kalau webhook yang sama datang dua kali, bagaimana agar aksi tidak berjalan ganda?" Fokusnya pada deduplikasi.

Keduanya saling melengkapi. Webhook bisa terkirim lebih dari sekali karena retry, jadi sebelum memproses, cek apakah reference_id itu sudah pernah menghasilkan aksi. Kalau sudah, balas 200 tanpa mengulang efek sampingnya. State machine memastikan status benar; idempotensi memastikan efek sampingnya (kirim email, buka akses, potong stok) hanya sekali. Detail teknik deduplikasi ada di idempotency API pembayaran.

Menangani Status Non-Terminal

Tidak setiap pembayaran berakhir paid. Kamu perlu menangani sisi lain:

  • Saat webhook payment.expired masuk, tandai expired dan bebaskan sumber daya yang tadi ditahan, misalnya stok yang dikunci.
  • Saat payment.failed, tandai failed dan beri pelanggan jalan untuk mencoba lagi dengan transaksi baru.
  • Untuk transaksi yang lama menggantung di pending, andalkan payment.expired dari sistem alih-alih menebak sendiri kapan sesuatu kedaluwarsa.

Menangani status non-terminal dengan benar sama pentingnya dengan menangani paid. Stok yang tidak pernah dibebaskan setelah pembayaran kedaluwarsa akan membuat inventarismu tampak habis padahal barang masih ada. Pelanggan yang transaksinya failed tetapi tidak diberi jalan mencoba lagi akan pergi. Karena itu, petakan aksi untuk setiap keadaan sejak awal, bukan hanya jalur bahagia.

Mengantisipasi Webhook yang Tidak Berurutan

Jaringan tidak menjamin urutan. Ada kemungkinan kecil kamu menerima notifikasi yang datang terlambat setelah status sudah berpindah. Karena status terminal tidak boleh mundur, aturan transisi eksplisit di kodemu akan otomatis menolak pembaruan yang tidak masuk akal, misalnya mencoba mengubah paid menjadi expired. Inilah alasan mengapa memodelkan transisi yang diizinkan jauh lebih aman daripada sekadar menimpa kolom status dengan nilai apa pun yang datang terakhir.

Kesimpulan

Menyimpan status pembayaran dengan benar bertumpu pada dua keputusan: modelkan transaksi sebagai state machine dengan status terminal yang tidak bisa mundur, dan jadikan webhook satu-satunya sumber kebenaran, bukan callback browser. Simpan reference_id sebagai kunci unik, catat cukup data untuk penelusuran, dan pisahkan urusan status dari idempotensi.

Kalau kamu menerapkan pola ini sejak awal, data pembayaranmu akan tetap konsisten meski webhook datang terlambat, berulang, atau tidak berurutan. Pelajari struktur payload dan event di dokumentasi sebelum kamu memfinalkan skema database.

Siap menerima pembayaran?

Coba integrasi & mode test gratis sejak hari pertama.

Daftar gratis →