Escrow bir özellik değil. Bir state machine.
Pazaryeri tarifinde escrow tek satır: iş bitene kadar parayı tut. Sperare'de bu satırı yazmak, ürünün geri kalanının toplamından uzun sürdü.
Sperare'de kullanıcı bir göçmenlik danışmanı buluyor, parayı yatırıyor, iş adım adım ilerliyor ve her adım onaylandıkça para serbest kalıyor. Ürün tarifinde bu bir cümle.
Kodda bir cümle değil. Çünkü buradaki hata modu "yavaş çalıştı" değil. Buradaki hata modu "birinin parası kayboldu".
İlk hatam: bunu bir durum alanı sanmak
İlk sürümde escrow durumu sipariş tablosunda bir alandı. beklemede, serbest, iade. Temiz görünüyordu.
Sorun şu ki bir durum alanı geçmişi unutur. Sadece şu anki değeri bilir. Para ise geçmişi unutamaz — "bu tutar neden bu hesapta" sorusunun cevabı her zaman elinde olmak zorunda.
Doğrusu iki parça: değişmez bir defter ve o defterin üstünde çalışan bir durum makinesi. Fonlama, kilometre taşı onayı, serbest bırakma, iade, itiraz kilidi, ters transfer — her geçişin kendi ön koşulu var ve her geçiş silinmeyen bir kayıt bırakıyor. Şu anki durum saklanan bir değer değil, defterden türeyen bir sonuç.
Aynı isteği iki kez almak normaldir
Bunu ödeme sistemleriyle çalışmadan önce ciddiye almıyordum. Ödeme sağlayıcısının webhook'u iki kez geliyor. İstemcinin isteği zaman aşımına uğruyor ve tekrar deniyor. Kullanıcı butona iki kez basıyor.
Bu senaryoların hiçbiri istisna değil, hepsi normal işleyişin parçası. Ve aynı işlem iki kez işlenirse para iki kez hareket ediyor.
Bu yüzden para hareketi yaratan her işlem bir idempotency anahtarı taşıyor. Aynı anahtar ikinci kez geldiğinde sistem işlemi tekrar çalıştırmıyor, ilk sonucun kopyasını döndürüyor. Yeniden denemek güvenli hâle geliyor — ki dağıtık bir sistemde yeniden denemenin güvenli olmadığı her yer, er ya da geç bir olay kaydına dönüşüyor.
Bir ödeme akışının olgunluğunu tek soruyla ölçebilirsin: aynı isteği iki kez gönderdiğimde ne oluyor?
Webhook'lar sana sıra garantisi vermez
İkinci ders: olaylar gönderildikleri sırayla gelmiyor. "Ödeme başarılı" bazen "ödeme oluşturuldu"dan önce düşüyor kuyruğa.
Naif bir uygulama bunu geçersiz bir geçiş sayıp reddediyor ve tutarlılığı kaybediyor. Durum makinesi ise erken gelen olayı reddetmiyor — bekletiyor, öncülü geldiğinde ikisini birden işliyor. Yani sıra dışı olay bir hata değil, tasarlaman gereken bir durum.
Gece sonunda kim haklı
Son parça mutabakat. Kendi defterimizde yazan ile ödeme sağlayıcısının kayıtlarında yazanı düzenli olarak karşılaştırıyoruz. Fark varsa alarm üretiyor.
Bunun kulağa fazla ihtiyatlı geldiğini biliyorum. Ama bir gün bir fark çıktığında, o farkı sabah kullanıcıdan öğrenmek ile gece sistemden öğrenmek arasındaki mesafe, ürüne duyulan güvenin tamamı.
Escrow'un zor kısmı parayı tutmak değil. "Şu anda bu para kimin" sorusuna, sistemin her katmanında, her an aynı cevabı verebilmek.