Teknik

Epin Scriptinde Aynı Ödeme Neden İki Kez Sipariş Açar? Tekrarlanan Bildirim (Idempotent) Korumasının Kimsenin Anlatmadığı Perde Arkası

Aynı ödeme bildirimi 3 kez gelince e-pin scripti çift sipariş açar. Idempotency anahtarı, kilit ve teslimat koruması nasıl kurulur, adım adım anlatıyoruz.

Epin Scriptinde Aynı Ödeme Neden İki Kez Sipariş Açar? Tekrarlanan Bildirim (Idempotent) Korumasının Kimsenin Anlatmadığı Perde Arkası

Çoğu işletme sahibi ödeme bildirimini tek yönlü bir mektup sanır: müşteri kartı çeker, sağlayıcı senin sunucuna "tahsil edildi" diye haber gönderir, iş biter. Gerçek hiç böyle değil. O bildirim bir mektup değil, ısrarcı bir kapı zili. Sağlayıcı senden net bir cevap alana kadar aynı zile basmaya devam eder; bazen 3 kez, bazen 40 saniye arayla 8 kez. Eğer kullandığın e-pin scripti her zil sesini yeni bir müşteri sanıyorsa, tek ödemeden üç sipariş, üç stok düşümü ve üç kod teslimi çıkar.

İşin sinsi tarafı şu: bu hata sakin günlerde kendini göstermez. Gün içinde 40 sipariş alırken bildirim tekrarları sıraya girer, script yetişir, kimse bir şey fark etmez. Ama bir yayıncı yayında ürününü söylediği anda eşzamanlı 200 istek gelir ve aynı ödeme paralel iki PHP sürecinde birlikte işlenir. Sabah stok raporuna baktığında 18 kod eksik, kasada karşılığı yok. Bu yazıda o boşluğun tam olarak nerede olduğunu, hangi alandan idempotency anahtarı üretileceğini ve dijital ürün satış scripti tarafında hangi üç katmanın aynı anda kurulması gerektiğini anlatıyorum.

3-8Bir ödeme için sağlayıcının deneyebileceği bildirim sayısı
~40 snTipik yeniden deneme aralığı
200 msYarış durumunun oluştuğu kritik pencere
2 tabloKorumanın oturduğu asgari veri yapısı

Çoğu e-pin scripti "bildirim geldi" diye sipariş açar; sorun tam burada başlar

Piyasadaki basit kurgularda callback dosyası tek bir iş yapar: imzayı doğrular, tutarı karşılaştırır, siparişi ödendi yapar ve teslimatı tetikler. Bu akışın hiçbir yerinde "bu bildirimi daha önce gördüm mü?" sorusu yoktur. Oysa bildirim, ödemenin kendisi değil; ödemenin duyurusudur. Duyuru kopyalanabilir, gecikebilir, sıra değiştirebilir.

Doğru kurgu, bildirimi işlemeden önce onu bir kimlikle karşılamaktır. Sağlayıcının gönderdiği işlem numarası (transaction id, payment id, conversation id) her tekrar denemede aynıdır. İşte anahtarın omurgası bu alandır. Bir e-pin satış yazılımında ilk satır kodun şu olmalı: bu işlem numarası daha önce işlendiyse, hiçbir şey yapma, sağlayıcıya 200 dön ve çık. Bu tek karar, çift teslimatın yüzde doksanını keser.

Sağlayıcı neden aynı bildirimi üç kez gönderir?

Suçlu sağlayıcı değil, tasarım tercihi. Ödeme sistemleri "en az bir kez teslim" (at-least-once) garantisi verir, "tam olarak bir kez" garantisi vermez. Çünkü ağ kopabilir: senin sunucun bildirimi alıp işlemiş olabilir ama cevabı yolda kaybolmuş olabilir. Sağlayıcı bu iki durumu birbirinden ayırt edemez, bu yüzden güvenli olanı seçer ve tekrar dener.

Tekrarı davet eden üç klasik hata var. Birincisi, callback dosyanın HTTP 200 dışında bir şey dönmesi; 302 yönlendirme veya boş sayfa bile başarısız sayılır. İkincisi, cevabı teslimat bittikten sonra dönmek: kod üretimi 6 saniye sürüyorsa sağlayıcının 5 saniyelik zaman aşımı devreye girer ve yeni bildirim yolda demektir. Üçüncüsü, bakım modunda veya opcache yenilenirken 503 dönmek. Bu yüzden sağlıklı bir epin yazılım kurgusunda bildirim alınır, kaydedilir, anında 200 dönülür; ağır iş arka planda yürür.

SenaryoKorumasız scriptIdempotent scriptKayıp riski
Aynı işlem numarası 2. kez geldiYeni sipariş satırı açarKaydı bulur, işlemez, 200 dönerÇift kod teslimi
Paralel iki istek, 150 ms araylaİkisi de "ödenmedi" görürKilit ikinciyi bekletirStoktan iki düşüm
Teslimat sırasında zaman aşımıSağlayıcı başarısız sayar, tekrar denerCevap önce, teslimat kuyruktaZincirleme tekrar
İptal bildirimi ödeme bildiriminden önce düştüSırayı kabul eder, durum bozulurZaman damgası ile sıralarYanlış durum, yanlış iade
Aynı tutar, farklı işlem numarasıTekrar sanıp bloklarİki ayrı ödeme olarak işlerGerçek siparişi kaybetme

Idempotency anahtarı hangi alandan üretilmeli?

Buradaki en yaygın yanlış, anahtarı tutar ve e-postadan üretmek. Aynı müşteri aynı ürünü beş dakika sonra tekrar alabilir; tutar ve e-posta aynıdır ama bu gerçek ikinci bir satıştır. Anahtarı böyle kurarsan tekrarları engellerken ciro kaybedersin.

Doğrusu, sağlayıcının kendi işlem kimliğini birincil anahtar yapmaktır. Buna sağlayıcı kodunu da ekle, çünkü iki farklı sağlayıcı aynı numarayı üretebilir. Yani anahtar saglayici_kodu + islem_no biçiminde birleşik olsun ve veritabanında UNIQUE kısıtı taşısın. Bu kısıtı koyduğun anda korumanın son kalesini uygulama katmanından veritabanına indirmiş olursun: kodun hata yapsa bile ikinci kayıt fiziksel olarak yazılamaz. Tabloya bir de ham bildirim gövdesini ve alınma zamanını yaz; üç ay sonra çıkacak bir itirazda tek kanıtın bu olacak.

Çift teslimat vakalarının kök nedeni

  • Tekrar bildirimi kontrolü hiç yok 45%
  • Kontrol var ama kilit yok (yarış) 25%
  • Cevap teslimattan sonra dönüyor 20%
  • Yanlış anahtar alanı seçilmiş 10%

Kilit olmadan idempotency çalışmaz: yarış durumu

Kontrolü yazdın, tabloya UNIQUE koydun, rahatladın. Sonra iki bildirim 150 milisaniye arayla geliyor. İki PHP süreci aynı anda "bu işlem işlendi mi?" diye soruyor, ikisi de "hayır" cevabı alıyor, ikisi de teslimata giriyor. UNIQUE kısıtı ikinci kaydı reddeder ama o ana kadar stoktan iki kod çekilmiş olabilir. Kontrol ile yazma arasındaki o boşluk, bütün hatanın yaşadığı yerdir.

Çözüm, işlemi tek bir veritabanı transaction'ına almak ve sipariş satırını SELECT ... FOR UPDATE ile kilitlemek. İkinci süreç kilidi bekler, birincinin işi bitince tekrar okur, durumu ödendi görür ve hiçbir şey yapmadan çıkar. Alternatif olarak uygulama seviyesinde adlandırılmış kilit (GET_LOCK) kullanılabilir. Hangisini seçersen seç, kritik olan şudur: kontrol ve durum güncellemesi aynı atomik blokta olmalı. Bu ayrımı yapmayan bir e-pin satış scripti "idempotent" iddiasını yalnızca kâğıt üzerinde taşır.

Kilidi siparişe değil, işleme kur

Kilit anahtarını sipariş numarasından değil ödeme işlem numarasından türetirsen, aynı siparişe ait iki farklı taksit bildirimi birbirini gereksiz bekletmez.

Stok düşümü ve kod teslimi tekrarlanırsa ne kaybedersin?

Zarar üç kalemde birikir. Doğrudan maliyet: ikinci kod bedava gitti, alış fiyatı senin cebinden çıktı. Mutabakat maliyeti: sağlayıcı ekstresinde tek tahsilat var, panelinde iki satış var; ay sonunda rakamları tutturmak için saatler harcanır. En pahalısı ise itibar maliyeti: müşteri iki kod aldığını söylemez, ama kod stoğu bittiği için bekleyen üç müşteri "para aldı ürün vermedi" yorumu yazar.

Bu yüzden teslimat katmanına ikinci bir bekçi konur. Her kod tahsisi, hangi ödeme işlem numarası için yapıldığını taşısın. Teslimat fonksiyonu çalışmadan önce "bu işlem numarası için zaten kod tahsis edilmiş mi?" diye sorsun. Böylece ödeme katmanında bir kaçak olsa bile stok katmanı ikinci kez açılmaz. Katmanlı savunmanın anlamı budır: tek bir kontrol noktasına güvenmemek.

Doğru kurulum neye benzer: üç katmanlı savunma

Pinova tarafında bu iş üç ayrı yerde birlikte kuruluyor. Birinci katman giriş kapısı: gelen her bildirim önce ham hâliyle log tablosuna yazılır, birleşik anahtar UNIQUE çarparsa istek daha içeriye hiç girmez ve anında 200 döner. İkinci katman durum makinesi: sipariş yalnızca tanımlı geçişleri kabul eder, ödendi durumundaki bir sipariş tekrar ödendi olamaz, bu yüzden gecikmiş bildirim etkisiz kalır. Üçüncü katman teslimat defteri: her kod, tek bir ödeme işlemine bağlı olarak rezerve edilir ve o bağ bir daha kurulamaz.

Bunun üstüne bir de gözlem gerekir: tekrar sayacı. Bir işlem için ikiden fazla bildirim geldiyse panelde uyarı çıksın. Bu genelde senin tarafında bir zaman aşımı olduğunu söyler ve sorun büyümeden görülür. Bir dijital ürün satış yazılımında bu sayaç, sessizce büyüyen problemleri erken yakalayan en ucuz sensördür.

KontrolNasıl test edilirBeklenen sonuç
Birleşik UNIQUE anahtar var mıAynı bildirimi curl ile 3 kez gönder1 sipariş, 3 kez HTTP 200
Kilit doğru çalışıyor muAynı bildirimi eşzamanlı 10 istekle basTek teslimat, tek stok düşümü
Cevap hızıCallback yanıt süresini ölç1 saniyenin altında 200
Sıra bağımsızlığıİptal bildirimini önce gönderDurum bozulmaz, kayıt tutarlı
Teslimat defteriAynı işlem için ikinci tahsis deneReddedilir, log'a yazılır
Farklı ödeme ayrımıAynı tutar, yeni işlem no gönderYeni sipariş normal açılır

Bildirim tekrarını kendin yazmak zorunda değilsin

Idempotency anahtarı, transaction kilidi ve teslimat defteri Pinova'da hazır kurulu gelir; sen ürün ve fiyat tarafına bakarsın.

Idempotency anahtarını kendim üretsem olur mu?

Olur ama sağlayıcı işlem numarasını mutlaka içermeli. Kendi ürettiğin rastgele bir değer her istekte değişeceği için tekrarları yakalamaz.

Sağlayıcıya neden hemen 200 dönmem gerekiyor?

Çünkü cevabı geciktirdiğin her saniye yeni bir tekrar bildirimi davet eder. Teslimatı arka plana al, cevabı önce ver.

UNIQUE kısıtı tek başına yetmez mi?

Yetmez. Kayıt çakışmasını engeller ama o ana kadar yapılmış stok düşümünü geri almaz. Kilit ve teslimat kontrolü de gerekir.

Eski siparişlerde çift teslimat olup olmadığını nasıl anlarım?

Aynı ödeme işlem numarasına bağlı birden fazla teslimat kaydını sorgula. Sonuç boş değilse geçmişte kaçak yaşanmış demektir.

Bildirim tekrarı kaçınılmazdır; çift teslimat değil. Aradaki farkı belirleyen tek şey, scriptinin o zili kaç kez çaldığını sayıp saymadığıdır. Kurulu bir sistemde bu koruma katmanlarının nasıl göründüğünü kendi gözünle görmek istersen canlı demo üzerinden sipariş akışını izleyebilir, özellikler listesindeki ödeme altyapısı maddelerine ve fiyat tablosuna göz atabilirsin.

Daha Fazlasını Keşfedin

Dijital ürün satışı ve otomasyon üzerine güncel rehberlere göz atın.

Tüm Yazıları Gör →
Bu yazıyı paylaşın
Bu yazıyı beğendiniz mi?
Teşekkürler!
← Tüm yazılar