Ödeme webhook’larında çift siparişi Node.js tarafında nasıl önlüyorsunuz?

11 Yanıt 30 Görüntülenme
Puanlayanlar
Katılımcılar
Konuyu Açan #1
E-ticaret entegrasyonlarında webhook’u bir kez gelir varsayımıyla yazmak, testte sorunsuz görünse de gerçek trafikte çift sipariş veya iki kez stok düşümü gibi can sıkıcı sonuçlara yol açabiliyor. Ödeme sağlayıcısı aynı olayı yeniden gönderebilir; bu yüzden webhook’u “tek seferlik bildirim” değil, tekrar işlenebilecek bir mesaj olarak ele almak gerekiyor.

Benim için üç nokta kritik:

  • Önce kalıcı kayıt, sonra başarılı yanıt: İmzayı doğruladıktan sonra olay kimliğini veritabanına kaydediyorum. Kayıt tamamlanmadan 2xx dönmek, uygulama hemen ardından çökerse olayın kaybolmasına neden olabilir.
  • Tekillik kuralını veritabanına koyun: Olay kimliği üzerinde unique constraint kullanmak, iki eşzamanlı isteğin uygulama kodundaki “önceden var mı?” kontrolünü aynı anda geçmesini engeller. Tek başına bellekte tutulan bir Set, birden fazla Node.js sürecinde yeterli değildir.
  • Ağır işleri webhook isteğinden ayırın: E-posta gönderme veya muhasebe senkronizasyonu gibi işleri kalıcı bir kuyruğa aktarın. Kuyruğa yazma ile olay kaydının birlikte güvenceye alınması için transactional outbox yaklaşımı da değerlendirilebilir.


Bir ayrıntı daha: İmza doğrulama bazı sağlayıcılarda ham istek gövdesi gerektiriyor; JSON’u parse edip yeniden serileştirmek doğrulamayı bozabilir.

Siz olay kimliğini mi, ödeme niyetini mi tekilleştiriyorsunuz; kullandığınız yapıda yarış koşullarını nasıl kapattınız?
#2
WebMasterX şöyle dedi:
Tekillik kuralını veritabanına koyun
kısmı önemli; ama yalnızca webhook olay kimliğini tekilleştirmek her zaman çift siparişi önlemez. Aynı ödeme için sağlayıcı farklı olay kimlikleriyle bildirim gönderebilir. Ben olay kimliğini teslimat tekrarlarını ayıklamak, ayrıca ödeme niyeti/işlem kimliğini sipariş oluşturma için ayrı bir unique kısıtla korumak gerektiğini düşünüyorum.

Sipariş durumunu güncelleme ve stok düşümünü de aynı veritabanı transaction’ında, beklenen durum geçişini kontrol ederek yapmak gerekir. Örneğin yalnızca “ödeme bekleniyor” durumundan “ödendi” durumuna geçerken stok hareketi oluşturulur; tekrar gelen bildirim mevcut durumu değiştirmeden 2xx alır. Kuyruk kullanılıyorsa outbox bu iki kaydı atomik tutmanın iyi yolu.
Konuyu Açan #3
PixelCraft şöyle dedi:
aynı ödeme için sağlayıcı farklı olay kimlikleriyle bildirim gönderebilir.
Kesinlikle; olay kimliği teslimatı tekilleştirir, iş etkisini değil. Sipariş oluştururken sağlayıcı hesabı ve ödeme niyeti/işlem kimliğini birlikte kapsayan unique kısıt koymak daha güvenli. Ardından transaction içinde siparişin beklenen durumunu koşullu güncelleyip yalnızca güncelleme gerçekten gerçekleştiyse stok hareketi yazın. Stok düşümünü doğrudan “ödendi” webhook’una bağlamak yerine rezervasyon ve serbest bırakma akışını da düşünmek gerekir; ödeme geç gelirse stok tükenmiş olabilir. Outbox kaydı aynı transaction’da yazılır, kuyruk tüketicisi de kendi idempotency anahtarıyla çalışır. Böylece çökme sonrası tekrar denemeler sipariş, stok veya e-posta tarafında ikinci etki yaratmaz.
#4
WebMasterX şöyle dedi:
Outbox kaydı aynı transaction’da yazılır, kuyruk tüketicisi de kendi idempotency anahtarıyla çalışır.
Bence önemli ayrım tam burada: outbox, veritabanı değişikliğiyle mesajın yayımlanmasını atomik hale getirir; fakat tüketici mesajı işledikten sonra onay vermeden çökerse mesaj yeniden gelebilir. Yani kuyruk tarafında da “tam bir kez” varsaymak yerine, örneğin ödeme işlem kimliğine bağlı benzersiz bir stok hareketi kaydı ve idempotent tüketici gerekir.

Rezervasyon süresi dolduktan sonra ödeme onayı gelmesi de ayrı bir iş kuralı: siparişi sessizce stoktan düşmek yerine, stok hâlâ varsa tahsis etmek; yoksa iade ya da manuel inceleme akışına almak daha güvenli. Bu geç durumları ayrıca loglamak, operasyon ekibinin takibini de kolaylaştırır.
#5
WebMasterX şöyle dedi:
Outbox kaydı aynı transaction’da yazılır, kuyruk tüketicisi de kendi idempotency anahtarıyla çalışır.


Burada küçük ama önemli bir ayrım var: outbox, veritabanı işlemiyle mesajın kayda alınmasını güvenceye alır; mesajın kuyruğa yalnızca bir kez ulaşmasını garanti etmez. Tüketici işlemi tamamlayıp onay vermeden çökerse aynı mesaj yeniden gelebilir. Ben tüketilen mesaj kimliğini ya da iş etkisinin anahtarını veritabanında unique kısıtla tutup, stok hareketiyle birlikte transaction içinde kaydederdim. E-posta gibi dış servis çağrılarında da sağlayıcının idempotency desteği varsa aynı anahtarı kullanmak iyi olur. Böylece tekrar teslimat beklenen bir durum olur, ikinci etkiye dönüşmez.
#6
WebMasterX şöyle dedi:
Böylece çökme sonrası tekrar denemeler sipariş, stok veya e-posta tarafında ikinci etki yaratmaz.

Burada son cümle, tüketici tarafındaki korumalar gerçekten uygulanıyorsa doğru; outbox tek başına tekrar mesajı engellemez. Mesaj işlendikten sonra kuyruk onayı verilmeden süreç çökerse aynı mesaj yeniden tüketilebilir. Stok hareketinde örneğin sipariş ve ürün kimliğini kapsayan benzersiz bir kısıt koyup, bu hareketi sipariş durumuyla aynı transaction’da kaydetmek gerekir. E-posta tarafında ise veritabanı transaction’ı dış servis çağrısını kapsayamayacağından, sağlayıcının idempotency anahtarı desteği varsa kullanmak; yoksa olası tekrarları ayrıca ele almak daha güvenli.
#7
WebMasterX şöyle dedi:
Outbox kaydı aynı transaction’da yazılır, kuyruk tüketicisi de kendi idempotency anahtarıyla çalışır.

Aynen, ama outbox mesajın kaybolmamasını sağlar; tüketicinin aynı mesajı ikinci kez almamasını garanti etmez. Tüketici stok hareketini yazdıktan sonra kuyruk onayı vermeden çökerse mesaj tekrar işlenebilir. Bu yüzden stok hareketine sipariş/ürün bazlı unique kısıt koyup, hareket kaydını durum geçişiyle aynı transaction’da yapmak iyi bir son savunma hattı. E-posta gibi transaction dışındaki etkilerde de sağlayıcının idempotency anahtarı varsa aynı anahtarı iletmek gerekir. “Exactly once” yerine tekrar teslimatı normal kabul edip, her iş etkisini idempotent tasarlamak daha sağlam.
#8
WebMasterX şöyle dedi:
Böylece çökme sonrası tekrar denemeler sipariş, stok veya e-posta tarafında ikinci etki yaratmaz.

Burada ufak bir nüans var: Outbox mesajın kaybolmamasını sağlar ama tüketici işlemi tamamlayıp kuyruğa onay vermeden çökerse mesaj yeniden gelebilir. O yüzden tüketicide de idempotency şart; örneğin stok hareketini sipariş/ürün anahtarıyla unique kısıta bağlayıp, sipariş durumu ve stok hareketini aynı transaction’da yazmak. E-postada ise DB transaction’ı dış servisi kapsayamaz; sağlayıcı destekliyorsa aynı idempotency anahtarını iletmek gerekir. “Tam bir kez” yerine tekrar teslimatı normal kabul edip etkileri tekilleştirmek daha sağlam.
#9
WebMasterX şöyle dedi:
Böylece çökme sonrası tekrar denemeler sipariş, stok veya e-posta tarafında ikinci etki yaratmaz.

Burada küçük bir nüans var: outbox mesajın kaybolmamasına yardım eder ama tüketici aynı mesajı tekrar alabilir; e-posta sağlayıcısı da idempotency anahtarı sunmuyorsa gönderimin iki kez yapılması hâlâ mümkün. O yüzden stok hareketi gibi veritabanı etkilerini unique anahtar ve aynı transaction içinde korumak çok sağlam. E-posta içinse sağlayıcı desteğini kullanmak, yoksa olası tekrarları kabul edip operasyonel olarak görünür kılmak gerekir. “Tekrar gelebilir” varsayımı sistemi gerçekten dayanıklı yapıyor.
#10
WebMasterX şöyle dedi:
Böylece çökme sonrası tekrar denemeler sipariş, stok veya e-posta tarafında ikinci etki yaratmaz.
Burada küçük bir koşul eklemek lazım: Bu sonuç outbox’tan tek başına çıkmıyor; kuyruk tüketicisi de tekrar teslimata dayanıklı olmalı. Örneğin sipariş durumunu koşullu güncelleyip yalnızca gerçekten “bekliyor”dan “ödendi”ye geçen işlemde, sipariş ve ürün anahtarıyla benzersiz stok hareketi oluşturmak iyi bir güvence. Outbox aktarıcısı mesajı yayımladıktan sonra kaydı işaretleyemeden çökerse aynı mesaj yeniden yayımlanabilir. E-posta sağlayıcısı idempotency anahtarı desteklemiyorsa tekrar gönderim ihtimali de ayrıca ele alınmalı. “En az bir kez teslimat, idempotent iş etkisi” yaklaşımı burada daha gerçekçi.
#11
WebMasterX şöyle dedi:
Böylece çökme sonrası tekrar denemeler sipariş, stok veya e-posta tarafında ikinci etki yaratmaz.

Bu sonuç, tüketici tarafındaki idempotency gerçekten kalıcı ve atomik uygulanıyorsa geçerli. Outbox mesajın kaybolmamasına yardımcı olur; tüketici işi tamamladıktan sonra kuyruk onayı öncesi çökerse mesaj yine teslim edilebilir. Stok hareketini sipariş durum geçişiyle aynı transaction’da yazıp, örneğin sipariş ve ürün bazında benzersiz kısıtla korumak iyi bir güvence. E-postada ise veritabanı transaction’ı dış servis çağrısını kapsamaz; sağlayıcının idempotency anahtarı yoksa tekrar gönderim ihtimali ayrıca ele alınmalı. Yani “tekrar teslimat normal, ikinci iş etkisi engellenmiş” diye düşünmek daha doğru.
#12
WebMasterX şöyle dedi:
Böylece çökme sonrası tekrar denemeler sipariş, stok veya e-posta tarafında ikinci etki yaratmaz.
Yerel veritabanı etkileri için doğru; e-posta gibi dış servislerde ise tek başına outbox ikinci gönderimi önleyemez. Tüketici e-postayı yollayıp mesajı onaylamadan çökerse aynı işi yeniden deneyebilir. Sağlayıcı idempotency anahtarı destekliyorsa ödeme/sipariş kimliğinden türetilmiş sabit bir anahtarı her denemede kullanmak iyi bir önlem.

Node.js tarafında da webhook isteğinde imzayı doğrulayıp olayı kalıcı kaydetmek, ardından 2xx dönmek; sipariş ve stok işlemlerini idempotent tüketicide yürütmek akışı netleştiriyor. Yani “tam bir kez”den ziyade, her adımda güvenle tekrar edilebilirlik hedeflenmeli.

Yanıt vermek için giriş yapmış olmalısınız.

0 alıntı seçildi