Pembayaran di Luar Sistem: Penanda, Bukan Modul
Fakta verifikasi dana di luar aplikasi dimodelkan sebagai dua kolom nullable: kontrak *bool wajib, 409 khusus, dan union SQL dua lengan.
Ringkasan
Jadi begini, verifikasi denda rental emang dilakuin bendahara di luar aplikasi, jadi nggak perlu payment gateway. Cukup nambah dua kolom nullable, payment_marked sama payment_marked_at, di tabel pengajuan yang udah ada. Ada satu endpoint penanda yang idempoten, dikawal izin tulis, dan komplain ditolak 409.
Layar administrasi meminta kepastian soal satu hal: apakah denda penyewaan ini sudah dibayar. Faktanya, verifikasi dana terjadi di luar aplikasi, dilakukan bendahara lewat kanal pembayaran kantor. Insting pertama biasanya dua: memasang payment gateway, atau menambah nilai baru di kolom status. Keduanya berlebihan. Prosesnya tidak punya siklus hidup pembayaran di dalam sistem, dan menambah nilai status berarti mengubah mesin status yang alurnya sudah stabil dan diaudit.
Model yang jujur justru paling kecil: dua kolom nullable di tabel pengajuan yang sudah ada. payment_marked bertipe boolean menyatakan pembayaran luar sistem sudah terverifikasi, dan payment_marked_at mencatat waktunya. Migrasi 000074 menambahkan keduanya; versi turunnya tinggal drop dua kolom itu. Tanpa gateway, tanpa nilai status baru, tanpa tabel baru.
Kontrak Permintaan yang Menolak Kebisuan
Titik ubahnya satu endpoint penanda pembayaran di belakang guard izin tulis layanan. Badan permintaannya satu field bernama marked, dan wajib hadir sebagai boolean. Kewajiban ini ditegakkan lewat dekode ke pointer boolean: pustaka encoding/json menyetel pointer ke nil saat JSON berisi null, dan mengurai nilai ke alamat pointer saat ada [8]. Karena itu field yang hilang dan field bernilai false terbaca berbeda, dan keduanya ditolak sebagai input tidak valid. Tanpa pointer, badan kosong akan diam-diam berarti "hapus penanda", sebuah perintah destruktif dari permintaan yang sebenarnya rusak.
Setelah dekode, lapisan layanan memeriksa ulang domainnya. Penanda hanya bermakna untuk pengajuan jenis penyewaan; jenis lain dijawab 409 lewat error khusus, terpisah dari pesan status-basi, supaya antarmuka tahu persis apa yang konflik. Urutan pemeriksaan cakupannya disalin dari alur update status: aktor tak dikenal dan baris di luar cakupan bidang ditolak lebih dulu sebagai tidak ditemukan. Barulah mutasi jalan, idempoten: setel true berarti boolean 1 plus waktu sekarang, batalkan berarti 0 plus NULL, dan predikat jenis penyewaan dipasang sekali lagi di dalam UPDATE sebagai pertahanan kedua di tingkat basis data.
Yang Terlihat dari Dua Sisi Kontrak
Dua kolom ini punya dua penonton dengan hak berbeda. Ke arah publik, struct berisi json:"-": handler publik memakai SELECT bintang dan pemeta sqlx tanpa mode unsafe menolak kolom yang tidak punya tujuan, jadi kolomnya harus ada di struct tapi tidak boleh ter-serialize. Ke arah admin, kontrak daftar justru menambahkan keduanya. Daftar admin dibangun dari UNION dua tabel, dan setiap lengan wajib mengembalikan bentuk kolom yang sama; lengan pengaduan menyumbang konstanta 0 dan NULL. Kolom DATE di lengan gabungan tetap dibungkus DATE_FORMAT pola YYYY-MM-DD agar terbaca sebagai string, bukan objek waktu dari driver [2].
Sisi antarmuka ikut berubah satu hal penting: kontrak GET kini membawa nilai penanda terbaru, jadi tombol alih di panel admin membaca keadaan server sebelum menggambar dirinya, bukan menyalakan-matikan tebakan. Auditnya best-effort setelah mutasi sukses dengan aksi payment_mark; gagal mencatat tidak membatalkan perubahan yang sah.
Hasil akhirnya bisa diuji langsung. Penanda disetel pada pengajuan penyewaan, daftar admin menampilkan statusnya di baris yang benar dan NULL di baris pengaduan, respons publik tidak membocorkan kolomnya, PUT tanpa badan ditolak 400, dan penanda pada pengaduan ditolak 409. Status pengajuan tidak pernah tersentuh. Sistem tetap mengaku tidak mengelola pembayaran; ia hanya mencatat fakta bahwa pihak lain sudah memverifikasinya.
Sumber
[2] MySQL 8.4 Reference Manual, Date and Time Functions (arsip Wayback), diakses 13 Oktober 2026.
[8] golang/go, src/encoding/json/decode.go, diakses 13 Oktober 2026.