Semua catatanEngineering12 Sep 2026 · 8 menit baca

Satu pencairan, satu transfer.

Gimana job yang dikunci, referensi unik, dan satu pertanyaan ekstra ke penyedia mencegah uang bergerak dua kali.

Cover · Pencairan

Pencairan adalah satu-satunya tempat di produk keuangan di mana bug berarti uang beneran hilang dan nggak bisa ditarik balik. Semua yang ada di sekitarnya pengen jalanin transfer yang sama dua kali: retry, timeout, worker, webhook.

Tiga cara pencairan jalan dua kali

  1. Penyedia pembayaran timeout. Lu retry, padahal request pertama ternyata sudah berhasil.
  2. Dua worker ambil job yang sama di detik yang sama.
  3. Penyedia kirim webhook yang sama dua kali, dan tiap webhook memicu proses lanjutan.

Nggak ada yang aneh dari ketiganya. Semuanya muncul di bulan pertama sistem pencairan mana pun, jadi pengamannya harus dibangun dari awal, bukan ditambal belakangan.

Kunci, kasih nama, tanya dulu

Job-nya unik per pencairan, barisnya dikunci selama diproses, dan tiap transfer dikirim dengan gateway_partner_ref yang unik. Worker kedua cuma menemukan statusnya sudah berubah, lalu pergi.

01Kunci barisnya
02Cek statusnya
03Kirim dengan referensi unik
04Saat retry, tanya penyedia dulu
SendPayout.php · disederhanakan
<?php

class SendPayout implements ShouldQueue, ShouldBeUnique
{
    public function uniqueId(): string
    {
        return "payout:{$this->payoutId}";
    }

    public function handle(): void
    {
        DB::transaction(function () {
            $p = Payout::lockForUpdate()->findOrFail($this->payoutId);

            if ($p->status !== 'pending') {
                return; // someone else already moved it
            }

            $this->provider->send($p->gateway_partner_ref, $p);
        });
    }
}

Langkah terakhir yang paling penting. Setelah timeout, sistem nggak asal kirim ulang. Dia tanya penyedia apakah referensi itu sudah ada, dan cuma kirim lagi kalau belum.

“Sebelum coba ulang, tanya dulu ke penyedia apakah percobaan pertama sudah berhasil.”

Webhook itu retry dari sisi seberang

Ide yang sama berlaku sebaliknya. Setiap webhook masuk dideduplikasi sebelum menyentuh saldo, jadi pesan yang datang dua kali jadi no-op, bukan kredit kedua.

Kalau cuma satu yang diingat: bikin percobaan kedua jadi membosankan. Kode pencairan terbaik adalah kode yang, kalau dijalanin dua kali, nggak ngapa-ngapain di kali kedua.

Pascal AdnanFullstack developer & founder · Jakarta
#payments#laravel#idempotency
Catatan berikutnya · 02 / 10Ledger yang nggak pernah mengedit catatan.