Perbandingan · Terbit
Webhook melawan halaman kembali sebagai pemicu pemenuhan pesanan: satu kiriman bertanda tangan yang datang tanpa peramban, satu kepulangan yang bisa tidak pernah terjadi dan bisa dipalsukan siapa saja, dan pembagian kerja yang membuat keduanya tetap dipakai
Jawabannya tidak berimbang, dan memang tidak seharusnya berimbang: pemenuhan pesanan dipicu webhook yang tanda tangannya sudah diverifikasi, tidak pernah oleh kepulangan pembeli ke halaman toko. Kepulangan itu navigasi, bukan bukti.
Yang layak dibaca terus bukan aturannya, melainkan sebabnya, karena kedua peristiwa ini terlihat setara dari layar penjual: keduanya terjadi sesudah pembayaran dan keduanya membawa id yang sama. Perbedaannya tidak terlihat sampai satu pembeli menutup aplikasi banknya, atau satu orang menyalin alamat halaman terima kasih dan mengirimnya ke grup.
Yang benar-benar dijamin masing-masing
Halaman kembali adalah return_url yang dikirim saat tagihan dibuat. Setelah pembayaran, halaman pembayaran menampilkan konfirmasi dan sebuah tombol kembali ke alamat itu, dengan ?id=payreq_...&status=succeeded ditambahkan di belakangnya. Yang dijamin peristiwa itu hanya satu hal: sebuah peramban membuka sebuah alamat.
Webhook adalah kiriman POST dari server ke server, yang datang ke setiap endpoint aktif segera setelah pembayaran terkonfirmasi. Setiap kiriman membawa tanda tangan bertimestamp pada header Kasera-Signature-V1 berbentuk t=<unix>,v1=<hex>, yaitu HMAC-SHA256 atas t, titik, dan raw body, dengan pengiriman yang timestampnya melenceng lebih dari lima menit pantas ditolak sebagai upaya pengulangan rekaman lama. Bentuk lengkapnya ada di dokumentasi webhook.
| Pertanyaan | Halaman kembali | Webhook payment.paid |
|---|---|---|
| Siapa yang mengirimnya | Peramban pembeli | Server Kasera Pay |
| Bisa dipalsukan tanpa membayar | Bisa, cukup dengan mengetik alamatnya | Tidak, tanpa signing secret tanda tangannya tidak cocok |
| Bisa tidak pernah terjadi meski sudah dibayar | Bisa, dan sering | Tidak, pengiriman diulang sampai tujuh kali selama sekitar 33 jam |
| Bisa terjadi lebih dari sekali | Bisa, lewat tombol kembali atau muat ulang | Bisa, karena pengiriman bersifat at-least-once, dan setiap event membawa id yang sama untuk di-dedupe |
| Bergantung pada pembeli tetap membuka peramban | Ya, sepenuhnya | Tidak sama sekali |
Tiga kegagalan yang hanya muncul kalau pemicunya kepulangan pembeli
Ketiganya bukan kemungkinan teoretis. Ketiganya bentuk kejadian sehari-hari pada cara bayar yang paling umum di Indonesia, yaitu QRIS dan nomor Virtual Account, justru karena kedua metode itu memindahkan pembeli ke aplikasi lain.
- Pembeli membayar lalu tidak pernah kembali. Pembayaran QRIS selesai di dalam aplikasi e-wallet atau mobile banking, dan pada titik itu tidak ada alasan alami bagi pembeli untuk berpindah balik ke tab toko yang sudah tertutup. Uangnya masuk, pesanannya tidak pernah bergerak, dan tidak ada galat apa pun yang tercatat. Ini kegagalan yang paling mahal karena paling sunyi: yang mengabarkannya adalah pembeli yang menagih barangnya, bukan sistem.
- Halaman terima kasih dijangkau tanpa membayar. Alamat lengkap beserta parameternya terlihat di kolom alamat pembeli yang benar-benar membayar. Menyalinnya, mengganti idnya, atau sekadar membukanya lagi adalah pekerjaan satu detik. Pada penjualan produk digital yang pengirimannya otomatis, akibatnya barang terkirim tanpa uang.
- Pemenuhan berjalan dua kali. Tombol kembali dan muat ulang memanggil alamat yang sama lagi. Tanpa penjagaan, satu pembayaran menghasilkan dua pengiriman, dua surel, atau dua baris stok terpotong.
Pekerjaan yang tetap menjadi milik halaman kembali
Menghapus halaman kembali bukan jawabannya. Pembeli yang selesai membayar butuh layar yang menyatakan apa yang baru saja terjadi dan apa langkah berikutnya, dan tanpa itu tingkat komplain naik tanpa satu pun pembayaran gagal. Yang berubah hanya kewenangannya.
Bentuk yang benar: ambil id dari parameter, abaikan status yang ikut menempel, lalu tanyakan keadaannya dari server sendiri lewat GET /v1/transactions/:id, yang bentuknya ada di dokumentasi mengambil permintaan pembayaran. Panggilan itu memakai kunci API yang tidak pernah ada di peramban, sehingga jawabannya tidak bisa dikarang pembeli. Kalau jawabannya masih pending, yang ditampilkan adalah keadaan sedang diperiksa, bukan gagal: urutan kedatangan webhook dan kepulangan pembeli tidak dijamin, dan keduanya berlomba tanpa pemenang tetap.
Susunan yang memakai keduanya sesuai porsinya
- Pesanan dibuat di sistem penjual lebih dulu, lalu tagihannya dibuat dengan
Idempotency-Keyyang tersimpan bersama pesanan itu. - Pembeli diarahkan ke halaman pembayaran, dengan
return_urlyang menunjuk ke halaman hasil di toko. - Pemenuhan dijalankan di handler webhook, setelah tanda tangan diverifikasi terhadap raw body dan setelah event id diperiksa sebagai pertahanan terhadap kiriman ganda. Verifikasi selalu mendahului perubahan apa pun pada pesanan, karena memverifikasi sesudah mengubah pesanan tidak menghasilkan apa-apa.
- Halaman hasil membaca statusnya sendiri dari server dan hanya menampilkannya. Tidak ada satu pun perubahan data yang dipicu dari sana.
Susunan ini punya sifat yang jarang disebut: keempat langkahnya tetap benar kalau pembeli tidak pernah kembali, kalau pembeli kembali dua kali, dan kalau kiriman webhook sampai tiga kali. Tidak ada satu pun kasus itu yang perlu ditangani sebagai pengecualian.
Kalau endpoint webhook belum bisa dipasang
Webhook membutuhkan URL https yang bisa dijangkau publik, dan itu belum selalu ada saat integrasi sedang dikerjakan di komputer sendiri. Penggantinya bukan kepulangan pembeli, melainkan pembacaan status berkala dari sisi server: sumbernya sama, kewenangannya sama, dan yang hilang hanya kecepatannya. Perbandingan lengkap kedua jalur itu, termasuk anggaran pembacaan berkala yang masuk akal, ada di perbandingan webhook dan pembacaan status berkala, dan cara mengujinya di komputer sendiri ada di tulisan tentang webhook di localhost dan mode tes.
Yang sudah memutuskan ini untuk toko yang tidak menulis kodenya
Penjual yang memakai plugin resmi tidak perlu mengambil keputusan ini sama sekali: pesanan ditandai lunas atas pemberitahuan yang sudah terbukti asli, bukan atas kepulangan pembeli ke halaman toko, dan kiriman yang nominalnya tidak cocok dengan total pesanan membuat pesanan ditahan alih-alih diselesaikan. Bentuk pemasangannya ada di dokumentasi plugin WooCommerce. Keputusan ini milik integrasi yang ditulis sendiri, dan pemilihan antara keduanya dibahas di perbandingan memakai plugin dan menulis integrasi sendiri.
Ringkasnya
- Kepulangan pembeli adalah navigasi; hanya webhook terverifikasi yang merupakan pernyataan.
- Parameter
statuspada halaman kembali tidak ditandatangani dan tidak boleh dibaca sebagai hasil. - Pembeli yang tidak pernah kembali adalah keadaan normal pada QRIS dan Virtual Account, bukan kasus tepi.
- Halaman kembali tetap dipertahankan sebagai layar hasil, dengan statusnya dibaca dari server sendiri.
- Verifikasi tanda tangan mendahului perubahan pesanan, dan event id menjaga dari kiriman ganda.
Pertanyaan yang sering muncul
Kenapa parameter status=succeeded pada return_url tidak bisa dipercaya?
Karena yang membawanya adalah peramban pembeli, dan alamat di peramban bisa diketik siapa saja. Halaman kembali dijangkau dengan menyalin alamatnya, tanpa pembayaran apa pun, dan parameter di belakang tanda tanya tidak ditandatangani. Yang pantas diambil dari sana hanya id permintaan pembayarannya, untuk kemudian ditanyakan sendiri dari server ke GET /v1/transactions/:id.
Pembeli sudah membayar tetapi tidak pernah kembali ke toko. Apakah pesanannya hilang?
Tidak, selama pemenuhannya dipicu webhook. Kiriman payment.paid berjalan dari server ke server dan tidak membutuhkan peramban pembeli sama sekali, jadi pembeli yang menutup aplikasi banknya setelah membayar tetap menghasilkan pesanan yang terpenuhi. Pada integrasi yang memicu dari kepulangan pembeli, kasus ini justru bentuk kegagalan yang paling sering dan paling sunyi.
Apakah halaman kembali masih diperlukan kalau webhook sudah dipasang?
Diperlukan, tetapi tugasnya berbeda. Halaman kembali adalah layar yang dibaca pembeli setelah membayar, dan tanpa itu pembeli ditinggal di halaman pembayaran tanpa tahu harus ke mana. Yang berubah hanyalah kewenangannya: halaman itu menampilkan hasil, bukan memutuskan hasil.
Apa yang ditampilkan halaman kembali kalau webhooknya belum sampai saat pembeli tiba?
Keadaan sedang diperiksa, bukan gagal. Urutan kedatangan keduanya tidak dijamin, dan pembacaan status dari server sendiri lewat GET /v1/transactions/:id menjawab pertanyaan itu tanpa menunggu webhook. Menyatakan pembayaran gagal karena webhook belum tiba adalah sumber komplain yang tidak perlu.
Kalau endpoint webhook belum ada, apakah kepulangan pembeli boleh dipakai sementara?
Tidak sebagai pemicu. Penggantinya yang benar adalah pembacaan status dari sisi server, karena yang dibutuhkan bukan kiriman melainkan sumber yang tidak dipegang pembeli. Webhook membutuhkan URL https publik, dan selama itu belum ada, server yang bertanya sendiri sudah cukup untuk mulai berjalan.