Cara Membuat Sistem Donasi dengan Kode Unik Transfer di WordPress Tanpa Payment Gateway
Banyak lembaga kecil menerima donasi lewat transfer bank biasa. Masalahnya muncul saat donasi mulai ramai: tiga orang mentransfer Rp100.000 di hari yang sama, dan admin bingung transfer mana milik siapa. Payment gateway bisa menyelesaikannya, tetapi ada potongan biaya per transaksi dan proses pendaftarannya tidak selalu mudah untuk yayasan atau panitia sosial.
Jalan tengah yang sudah lama dipakai toko online di Indonesia adalah kode unik: setiap tagihan diberi tambahan beberapa rupiah sehingga totalnya berbeda-beda. Di tutorial ini kita membangun sistem tersebut di WordPress, mulai dari tabel donasi, pemilihan kode unik yang aman dari tabrakan, verifikasi yang tidak bisa dobel, sampai kuitansi dengan angka terbilang. Semua contoh diambil dari plugin Galang Dana & Donasi Online yang bisa kamu download gratis di Papicode (link di akhir artikel).
1. Tabel donasi sendiri, bukan post meta
Donasi adalah data transaksi yang akan sering dijumlahkan, difilter per status, dan dicari berdasarkan nominal. Menyimpannya sebagai post meta membuat query lambat. Buat tabel khusus saat plugin diaktifkan:
dbDelta("CREATE TABLE {$wpdb->prefix}gdn_donasi (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
kode VARCHAR(24) NOT NULL,
token CHAR(32) NOT NULL,
kampanye_id BIGINT UNSIGNED NOT NULL,
nama VARCHAR(80) NOT NULL,
wa VARCHAR(20) NOT NULL,
nominal INT UNSIGNED NOT NULL,
kode_unik SMALLINT UNSIGNED NOT NULL DEFAULT 0,
total INT UNSIGNED NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'menunggu',
created_at DATETIME NOT NULL,
batas_at DATETIME NOT NULL,
verified_at DATETIME NULL,
PRIMARY KEY (id),
UNIQUE KEY token (token),
KEY kampanye_status (kampanye_id,status),
KEY status_total (status,total)
) $c;");
Perhatikan indeks status_total. Indeks ini dipakai dua kali: saat mencari kode unik yang masih kosong, dan saat admin menempel angka dari mutasi bank ke kotak pencarian. Kolom nominal, kode_unik, dan total disimpan terpisah supaya laporan tetap bisa menunjukkan berapa niat donasi aslinya.
2. Memilih kode unik tanpa tabrakan
Idenya sederhana: ambil semua total tagihan yang masih menunggu di rentang nominal+1 sampai nominal+999, lalu pilih acak angka yang belum terpakai.
$dipakai = array_map('intval', $wpdb->get_col($wpdb->prepare(
"SELECT total FROM $tabel WHERE status = 'menunggu' AND total BETWEEN %d AND %d",
$nominal + 1, $nominal + 999
)));
$bebas = array_values(array_diff(range(1, 999), array_map(fn ($t) => $t - $nominal, $dipakai)));
$unik = $bebas[wp_rand(0, count($bebas) - 1)];
Karena yang dicek adalah total, bukan pasangan nominal dan kode, tabrakan lintas nominal juga tercegah. Tagihan Rp50.000 + 100 tidak akan bentrok dengan tagihan Rp50.100 tanpa kode.
Tetapi ada celah: dua donatur yang menekan tombol di detik yang sama bisa membaca daftar yang sama lalu kebetulan mendapat angka yang sama. Solusinya adalah kunci bernama milik MySQL:
if (!$wpdb->get_var("SELECT GET_LOCK('gdn_kode_unik', 10)")) {
return new WP_Error('sibuk', 'Server sedang sibuk, coba sekali lagi.');
}
try {
// baca total yang dipakai, pilih kode, INSERT
} finally {
$wpdb->query("SELECT RELEASE_LOCK('gdn_kode_unik')");
}
GET_LOCK membuat permintaan kedua menunggu sampai permintaan pertama selesai menyimpan. Blok finally memastikan kunci selalu dilepas walau di tengah jalan fungsi mengembalikan error. Kuncinya hanya dipegang beberapa milidetik, jadi tidak terasa oleh pengunjung.
3. Verifikasi yang tidak bisa terhitung dua kali
Admin yang tidak sabar sering menekan tombol dua kali, atau dua pengurus membuka daftar yang sama. Kalau logikanya "baca status, lalu ubah", dua permintaan bisa sama-sama lolos. Gabungkan pengecekan dan perubahan dalam satu UPDATE bersyarat:
$n = $wpdb->query($wpdb->prepare(
"UPDATE $tabel SET status = %s, verified_at = %s, verified_by = %d
WHERE id = %d AND status IN ('menunggu', 'kedaluwarsa')",
'terverifikasi', current_time('mysql'), get_current_user_id(), $id
));
if (!$n) {
return new WP_Error('status', 'Donasi ini sudah diproses sebelumnya.');
}
MySQL mengunci baris selama UPDATE, sehingga hanya satu permintaan yang mendapat 1 row affected. Status kedaluwarsa tetap diizinkan karena di dunia nyata ada donatur yang baru mentransfer setelah batas waktu, dan donasinya tetap harus dicatat.
Dana terkumpul cukup dihitung dari donasi yang sudah diverifikasi. Jangan menyimpan angka "terkumpul" di post meta lalu menambahkannya manual, karena angka itu mudah melenceng saat ada donasi yang ditolak.
SELECT COALESCE(SUM(total), 0), COUNT(*) FROM $tabel
WHERE kampanye_id = %d AND status = 'terverifikasi'
4. Tagihan kedaluwarsa dengan WP-Cron
Tagihan yang dibiarkan menunggu selamanya akan menghabiskan stok kode unik untuk nominal populer seperti Rp100.000. Jadwalkan tugas per jam saat plugin diaktifkan:
if (!wp_next_scheduled('gdn_cron_kedaluwarsa')) {
wp_schedule_event(time() + 300, 'hourly', 'gdn_cron_kedaluwarsa');
}
add_action('gdn_cron_kedaluwarsa', function () {
global $wpdb;
$wpdb->query($wpdb->prepare(
"UPDATE {$wpdb->prefix}gdn_donasi SET status = 'kedaluwarsa'
WHERE status = 'menunggu' AND batas_at < %s", current_time('mysql')));
});
WP-Cron hanya berjalan saat ada pengunjung, jadi fungsi yang sama juga dipanggil sebelum memilih kode unik dan saat admin membuka daftar donasi. Dengan begitu data selalu segar walau situs sedang sepi.
5. Data publik yang tidak membocorkan privasi
Dinding donatur membuat program terasa hidup, tetapi tabel yang sama juga berisi nomor WhatsApp dan email. Biasakan membuat fungsi khusus yang hanya memilih kolom aman, dan lakukan penyamaran nama anonim di server, bukan di JavaScript:
$rows = $wpdb->get_results($wpdb->prepare(
"SELECT nama, anonim, total, pesan, verified_at FROM $tabel
WHERE kampanye_id = %d AND status = 'terverifikasi'
ORDER BY verified_at DESC LIMIT %d OFFSET %d", $id, $limit, $offset), ARRAY_A);
return array_map(fn ($r) => [
'nama' => $r['anonim'] ? 'Hamba Allah' : $r['nama'],
'total' => gdn_rp($r['total']),
'pesan' => (string) $r['pesan'],
], $rows);
Halaman tagihan pribadi dibuka lewat token acak 32 karakter (wp_generate_password(32, false)), bukan ID berurutan yang bisa ditebak. Tambahkan juga noindex lewat filter wp_robots supaya link tagihan yang tersebar tidak masuk mesin pencari.
6. Kuitansi dengan angka terbilang
Kuitansi terasa resmi bila jumlahnya ditulis dengan huruf. Fungsi rekursif kecil sudah cukup untuk Bahasa Indonesia, termasuk pengecualian "sebelas", "seratus", dan "seribu":
function gdn_terbilang(int $n): string {
$s = ['', 'satu', 'dua', 'tiga', 'empat', 'lima', 'enam', 'tujuh', 'delapan', 'sembilan', 'sepuluh', 'sebelas'];
$f = function (int $n) use (&$f, $s): string {
if ($n < 12) return $s[$n];
if ($n < 20) return $f($n - 10) . ' belas';
if ($n < 100) return trim($f(intdiv($n, 10)) . ' puluh ' . $f($n % 10));
if ($n < 200) return trim('seratus ' . $f($n - 100));
if ($n < 1000) return trim($f(intdiv($n, 100)) . ' ratus ' . $f($n % 100));
if ($n < 2000) return trim('seribu ' . $f($n - 1000));
if ($n < 1000000) return trim($f(intdiv($n, 1000)) . ' ribu ' . $f($n % 1000));
return trim($f(intdiv($n, 1000000)) . ' juta ' . $f($n % 1000000));
};
return $n ? preg_replace('/\s+/', ' ', $f($n)) . ' rupiah' : 'nol rupiah';
}
// 150237 -> "seratus lima puluh ribu dua ratus tiga puluh tujuh rupiah"
Tinggal tampilkan di halaman tagihan ketika statusnya sudah terverifikasi, beri tombol window.print(), dan sembunyikan elemen navigasi lewat @media print.
Ringkasan
- Simpan donasi di tabel sendiri dengan indeks pada
statusdantotal. - Pilih kode unik dari daftar total yang masih menunggu, dan bungkus prosesnya dengan
GET_LOCKagar aman dari klik bersamaan. - Verifikasi dengan satu
UPDATE ... WHERE status IN (...)sehingga tidak bisa terhitung dua kali. - Bebaskan kode unik lewat status kedaluwarsa yang dijalankan WP-Cron dan pemanggilan langsung.
- Pisahkan fungsi data publik yang hanya memilih kolom aman, dan buka tagihan dengan token acak.
- Lengkapi kuitansi dengan angka terbilang agar donatur mendapat bukti yang layak dicetak.
Download source code lengkapnya
Plugin lengkapnya sudah termasuk halaman daftar program dengan filter kategori, form donasi dengan nominal cepat, halaman tagihan dengan hitung mundur dan konfirmasi WhatsApp, dashboard dengan grafik, pencarian berdasarkan angka mutasi, ekspor CSV yang aman, serta data contoh enam program yayasan. Gratis dan siap dipasang.
Download Source Code Plugin Galang Dana & Donasi Online di Papicode
Source code lengkapnya gratis
Lihat semua yang dibahas di artikel ini langsung berjalan.
Tutorial lainnya
Cara Membuat Sistem Tiket Travel dengan Pilih Kursi di Google Apps Script: Layout Armada, Anti Kursi Dobel, dan Kursi Kedaluwarsa
20 Oktober 2026
Cara Membuat Website Donasi Transparan dengan Google Apps Script: Kode Unik Transfer dan Laporan Penyaluran
20 Oktober 2026
Membuat Billing Rental PS dengan Java Swing: Timer Anti Reset, Aturan Tarif, dan Satu Unit Satu Sesi
20 Oktober 2026