Blog · Terbit
Satu pesanan dibayar dua kali: menemukan kedua barisnya, dan satu aturan yang membuat tagihan kedaluwarsa masih bisa berubah menjadi lunas
Seorang pembeli mengabari bahwa uangnya terpotong dua kali untuk satu pesanan. Urutan yang menyelesaikannya ada empat langkah, dan langkah pertama bukan mengembalikan uang: pastikan yang masuk memang dua pembayaran, temukan kedua barisnya, pastikan keduanya berstatus succeeded, baru kembalikan yang kedua. Melewati langkah pertama adalah cara paling umum mengirim uang yang sebenarnya tidak pernah diterima.
Penyebabnya juga jarang seperti yang diduga. Pembayaran ganda di toko kecil hampir selalu lahir dari tagihan pengganti yang diterbitkan terlalu cepat, bukan dari pembeli yang menekan tombol dua kali.
Langkah satu: dua kabar belum tentu dua pembayaran
Sebelum melihat daftar transaksi, singkirkan dulu kemungkinan yang paling sering disalahartikan penjual dengan integrasi sendiri. Pengiriman webhook bersifat at-least-once: event payment.paid yang sama bisa tiba lebih dari sekali, dan itu memang perilaku yang dijanjikan, bukan gangguan. Dua notifikasi masuk ke sistem toko tidak berarti dua kali uang masuk.
Pembedanya ada di id event, yang juga dikirim di header Kasera-Event-Id. Dua kiriman dengan id event yang sama adalah satu kejadian yang dikirim ulang. Dua kiriman dengan id event berbeda yang menunjuk id permintaan pembayaran berbeda adalah dua pembayaran. Penerima webhook yang menyimpan id event pada kolom unik menutup seluruh golongan kekeliruan ini sendiri, dan rinciannya ada di dokumentasi webhook.
Langkah dua: menemukan kedua barisnya
Di dashboard, kotak pencarian pada halaman Transaksi mencari keterangan dan nomor pesanan, jadi nomor pesanan yang sama diketik sekali akan memunculkan seluruh barisnya sekaligus. Satu hal yang menjebak di layar itu: saringan status bawaannya bukan “semua”. Tampilan awal hanya menyalakan pending dan succeeded, sehingga baris expired, failed, dan canceled tidak ikut terlihat. Untuk menelusuri pembayaran ganda, nyalakan seluruh status lebih dulu, karena justru baris yang kedaluwarsa itulah yang menjelaskan bagaimana tagihan kedua bisa lahir.
Rentang tanggalnya juga perlu dilebarkan melewati hari kejadian. Tagihan pertama bisa dibuat kemarin dan baru lunas hari ini, dan alasannya ada di bagian berikutnya. Kalau barisnya perlu dikirim ke pihak lain atau dihitung di luar dashboard, tombol Unduh CSV mengekspor persis daftar yang sedang tersaring. Untuk integrasi sendiri, endpoint daftar permintaan pembayaran menyediakan saringan external_id dan merchant_ref, dua label yang biasanya sudah berisi nomor pesanan toko.
Aturan yang membuat tagihan kedaluwarsa masih bisa menjadi lunas
Ini bagian yang paling sering mengejutkan, dan penyebab pembayaran ganda paling banyak di lapangan. Dari lima status yang ada, empat di antaranya akhir. Tetapi ada satu perpindahan yang tetap diizinkan setelah status akhir tercapai: expired masih boleh berubah menjadi succeeded. Tidak ada perpindahan balik lainnya. failed dan canceled benar-benar tertutup.
Alasannya sederhana dan berpihak pada pembeli: kalau uangnya diterima penyedia jasa pembayaran berlisensi sebelum penghitung waktu di sisi ini berbunyi, uang itu menang. Menyatakan seseorang belum membayar padahal uangnya sudah berpindah adalah kesalahan yang jauh lebih mahal daripada sebuah tagihan yang lunas terlambat.
Akibatnya pada praktik sehari-hari: baris yang terbaca expired sore ini belum tentu tertutup selamanya. Urutan yang melahirkan pembayaran ganda hampir selalu persis begini. Tautan pertama lewat masa berlakunya. Penjual melihatnya kedaluwarsa, menganggap pembayarannya batal, lalu menerbitkan tautan pengganti. Pembeli membayar tautan kedua. Beberapa saat kemudian pembayaran pertama, yang ternyata sudah diterima tepat sebelum tenggatnya, masuk sebagai succeeded. Dua baris lunas untuk satu pesanan, dan tidak satu pun berasal dari kesalahan pembeli.
Satu hal lagi yang memperbesar peluangnya: kedaluwarsa tidak pernah dikabarkan lewat webhook. Sistem toko yang hanya mendengarkan event tidak akan pernah diberi tahu bahwa tautan pertama habis masa berlakunya, tetapi akan tetap menerima payment.paid untuk tautan itu kalau uangnya jadi masuk. Artikel tentang masa berlaku tagihan membahas angka dan batasnya.
Tiga penyebab lainnya
Tautan pengganti yang diterbitkan terlalu cepat. Bentuk yang sama seperti di atas tetapi tanpa kedaluwarsa: pembeli bilang pembayarannya gagal, penjual langsung mengirim tautan baru, lalu keduanya dibayar. Pemeriksaan status tautan pertama sebelum menerbitkan yang kedua menutup hampir seluruh kasus ini, dan artikel tentang pembeli yang mengaku sudah membayar membahas pemeriksaan itu langkah demi langkah.
Halaman pembayaran yang dipakai berulang. Satu halaman melahirkan banyak pembayaran tanpa nomor pesanan yang menempel pada masing-masing. Dua pembayaran dengan nominal sama dari orang yang sama pada halaman semacam ini benar-benar tidak bisa dibedakan dari luar. Mencocokkannya butuh catatan sisi toko, bukan catatan sisi pembayaran.
Sistem toko membuat dua permintaan. Ini bukan pembayaran ganda melainkan tagihan ganda, dan sumbernya berbeda: percobaan ulang pada pembuatan permintaan tanpa header Idempotency-Key. Yang perlu diluruskan sekalian, karena keliru dipahami hampir setiap kali: hanya header itu yang mencegah duplikat. external_id dan merchant_ref adalah label yang disimpan, dikembalikan, dan bisa disaring, bukan klaim bahwa dua permintaan adalah satu pembayaran. Dua pembuatan dengan external_id sama tetap dua permintaan pembayaran.
Langkah tiga: pastikan keduanya benar-benar lunas
Sebelum mengirim apa pun kembali, buka kedua barisnya dan pastikan keduanya membaca succeeded. Baris yang masih pending bukan uang yang diterima, dan baris yang terbaca expired berada di satu-satunya status akhir yang masih bisa berubah, jadi menunggu beberapa saat lebih murah daripada mengembalikan uang yang belum pernah masuk. Bukti transfer dari pembeli tidak pernah menutup pertanyaan ini, karena yang menentukan adalah status pada barisnya.
Biaya yang hangus, dan kenapa nominal kecil tidak layak dikembalikan
Pengembalian dana tidak mengembalikan biaya transaksinya. Pada pembayaran kedua senilai Rp 150.000, yang hangus adalah:
| Metode | Biaya transaksi | Jalur pengembalian |
|---|---|---|
| QRIS | Rp 1.300 | Transfer manual |
| Virtual Account | Rp 5.000 | Transfer manual |
| Kartu | Rp 6.700 | POST /v1/refunds |
Hanya kartu yang punya jalur pengembalian otomatis. QRIS dan Virtual Account selalu kembali lewat transfer dari rekening penjual sendiri, dan pada baris itu masih ada biaya transfer keluar yang ditentukan bank pengirim. Kalau uangnya diambil lebih dulu dari saldo lewat pencairan, ongkos pencairannya Rp 3.000 per permintaan. Prosedur lengkapnya ada di artikel tentang pengembalian dana.
Hitungan itu mengubah kebijakan toko pada nominal kecil. Pada pesanan Rp 25.000 lewat QRIS, biaya yang hangus Rp 425 ditambah biaya transfer keluar bisa melampaui margin pesanannya. Menawarkan barang senilai pembayaran kedua, atau saldo belanja, sering lebih masuk akal daripada transfer balik, selama pembeli setuju dan pilihan transfer tetap terbuka kalau tidak.
Balasan yang menutup percakapannya
Balasan yang baik menyebut tiga hal: bahwa dua pembayaran benar ditemukan, nomor pesanan dan nominal masing-masing, serta kapan uangnya kembali. Contoh yang bisa disalin: “Pesanan ORD-1042 tercatat dua pembayaran masuk, masing-masing Rp 150.000 pada pukul 14.02 dan 14.19. Pembayaran kedua dikembalikan penuh ke rekening pengirim hari ini, dan buktinya menyusul di chat ini. Pesanannya tetap berjalan seperti biasa.”
Menyebut nominal dan jam membuat pembeli bisa mencocokkannya sendiri, dan itu yang menghentikan rangkaian pertanyaan susulan. Menyatakan pesanannya tetap berjalan menghilangkan kekhawatiran yang paling nyata baginya, yaitu bahwa pengembalian dana berarti pesanannya dibatalkan.
Mengurangi kejadiannya
- Periksa status tautan lama sebelum menerbitkan tautan pengganti, dan ingat bahwa
expiredbelum tentu berarti selesai. - Terbitkan tautan baru, jangan memperpanjang yang lama, dan pakai
Idempotency-Keybaru untuk penerbitan ulang yang memang disengaja. - Setel masa berlaku sesuai kebiasaan pembeli. Masa berlaku yang terlalu pendek adalah pabrik tautan pengganti, dan tautan pengganti adalah pabrik pembayaran ganda.
- Simpan id event webhook pada kolom unik, supaya pengiriman ulang tidak pernah terbaca sebagai pembayaran baru oleh sistem toko.
- Cocokkan daftar transaksi dengan catatan pesanan setiap hari, bukan setiap bulan. Panduan rekonsiliasi harian memuat urutannya.
Ringkasnya
- Dua webhook bukan dua pembayaran. Bedakan lewat id event lebih dulu.
- Saringan status bawaan di daftar transaksi menyembunyikan tiga status. Nyalakan semuanya saat menelusuri.
expiredadalah satu-satunya status akhir yang masih bisa menjadisucceeded, dan dari situlah pembayaran ganda biasanya lahir.- Pastikan kedua baris
succeededsebelum mengembalikan apa pun. - Biaya transaksi tidak ikut kembali, jadi pencegahannya lebih murah daripada prosedurnya.