Ketika Tambah Saldo Tidak Cukup: Belajar Membangun E-Wallet yang Lebih Aman

·Abdul Wahid Kahar

Ketika pertama kali memikirkan backend e-wallet, fitur yang terlintas biasanya sederhana: register, login, cek saldo, top up, dan transfer. Terasa lengkap. Tapi begitu sistem mulai menyentuh saldo uang sungguhan, pertanyaannya berubah bentuk.

Bagaimana kalau request timeout, tapi transaksi sebenarnya sudah sukses di sisi server? Kalau saldo user berubah tengah malam, bagaimana cara membuktikan perubahannya benar? Pertanyaan-pertanyaan ini tidak muncul di tutorial, tapi inilah yang membedakan sistem finansial dari CRUD biasa.

Di project ini, saya mencoba membangun backend e-wallet dengan pendekatan yang lebih production-minded — menggunakan Go, PostgreSQL, Redis, dan Docker. Fokusnya bukan hanya membuat endpoint berjalan, tapi membuat alur transaksi lebih aman, bisa diaudit, dan lebih mudah dikembangkan ke depannya.

Kenapa menyimpan balance saja tidak cukup

Kolom wallets.balance memang cepat dibaca. Satu query, satu angka, selesai. Tapi angka itu tidak bisa menjawab pertanyaan audit yang paling mendasar: dari mana angka ini berasal?

Kalau saldo berubah dari Rp150.000 menjadi Rp80.000, siapa yang mengubahnya, kapan, dan berapa? Tanpa catatan yang jelas, kesalahan kecil di sistem uang bisa berubah menjadi masalah yang sangat sulit di-debug. Di sinilah konsep ledger masuk.

Desain yang saya pakai memisahkan concern dengan jelas. wallets.balance tetap ada sebagai snapshot saldo terkini — untuk performa baca. Tapi setiap perubahan saldo juga dicatat di ledger_entries sebagai audit trail yang immutable: dari mana, kapan, berapa, saldo sebelum dan sesudahnya. Selain itu ada top_up_orders untuk lifecycle top up, dan transfers sebagai business record setiap transfer.

Top up bukan sekadar tambah saldo

Di banyak implementasi sederhana, top up langsung menambah balance. Tapi di dunia nyata, top up melewati payment gateway — dan payment gateway bisa lambat, bisa gagal, bisa mengirim callback terlambat.

Solusinya adalah memperlakukan top up sebagai order lifecycle. User membuat top up order dengan status pending. Saldo belum berubah. Baru setelah sistem atau payment callback melakukan konfirmasi, status berubah menjadi confirmed, saldo bertambah, dan ledger entry dibuat. Dengan cara ini, tidak ada saldo yang berubah karena request yang belum terkonfirmasi.

Transfer harus atomic, tanpa terkecuali

Transfer melibatkan dua pihak. Saldo pengirim dikurangi, saldo penerima ditambah, transfer record dibuat, ledger debit dan credit dicatat. Kalau salah satu langkah ini gagal di tengah jalan, sistemnya corrupt.

Caranya satu: semua operasi itu harus berjalan dalam satu database transaction. Berhasil semua, atau gagal semua. Tidak ada kondisi di antara keduanya.

Ini mungkin pelajaran paling fundamental dari project ini: di sistem finansial, partial success sama berbahayanya dengan total failure.

Idempotency bukan fitur tambahan

Di aplikasi nyata, user bisa menekan tombol transfer dua kali. Jaringan bisa timeout. Frontend bisa retry request secara otomatis. Tanpa perlindungan, satu aksi user bisa berubah menjadi dua transaksi uang yang keduanya diproses.

Solusinya adalah idempotency key. Setiap request penting membawa header Idempotency-Key. Backend menyimpan key tersebut bersama hash payload. Kalau request yang sama dikirim ulang, backend tidak memproses transaksi baru — ia mengembalikan hasil yang sudah ada.

Yang saya sadari setelah mengimplementasikannya: idempotency bukan fitur yang ditambahkan belakangan. Ini adalah kontrak antara sistem dan user — janji bahwa satu niat akan selalu menghasilkan satu aksi, tidak peduli berapa kali requestnya dikirim.

Apa yang saya pelajari

Awalnya saya pikir e-wallet hanya soal tambah dan kurang saldo. Setelah membangun top up lifecycle, atomic transfer, idempotency, dan ledger, gambarannya jauh lebih jelas sekarang.

Sistem yang benar bukan hanya yang endpoint-nya berjalan. Tapi yang bisa menjawab pertanyaan audit, tahan terhadap kondisi jaringan yang tidak ideal, dan tidak mengizinkan state yang tidak konsisten meski hanya sesaat.

Masih banyak yang belum ada di project ini — reconciliation, fraud detection, rate limiting per user. Tapi fondasinya sudah bisa berdiri. Dan bagi saya, itu titik mulai yang jauh lebih solid.

Github: https://github.com/abdulwahidkahar/go-ewallet-backend