Çoğu satıcı çift teslimat sorununu bir sunucu arızası sanır. Hosting firmasını arar, PHP sürümünü yükseltir, önbelleği temizler; sonra aynı olay bir hafta sonra yine tekrarlar. Çünkü sorun hiçbir zaman sunucuda değildi. Sorun, aynı ödeme bildiriminin iki kez gelmesi halinde sistemin bunu "yeni bir sipariş" olarak okumasıydı. Yani eksik olan şey donanım değil, kimlik. Her siparişin kendine ait, tekrarlandığında aynı kalan bir parmak izi yoksa, ikinci bildirim senin için taze bir müşteri gibi görünür ve kasadan ikinci kod çıkar.
Bu parmak izinin adı idempotency anahtarı. Kulağa akademik geliyor ama pratikte tek bir işi var: "Bu isteği daha önce işledim mi?" sorusunu milisaniyeler içinde kesin biçimde yanıtlamak. İyi kurulmuş bir e-pin scripti bu anahtarı sipariş doğmadan önce üretir, ödeme sağlayıcısına taşır, geri dönen her bildirimde aynı anahtarı arar. Kötü kurulmuş bir sistem ise anahtarı hiç üretmez ya da tam olarak tekrarlanmayacak biçimde üretir; ki bu ikincisi birinciden daha tehlikelidir, çünkü sana koruma varmış hissi verir.
Anahtarı kim üretir: tarayıcı mı, sunucu mu?
Burada verilen ilk karar, sonraki her şeyi belirler. Anahtarı tarayıcıda üretirsen, kullanıcının sekmeyi yenilemesi ya da JavaScript'in yeniden yüklenmesi yeni bir anahtar doğurur; koruma buharlaşır. Anahtarı yalnızca sunucuda üretirsen, ağ kopmasında kullanıcı tekrar denediğinde sunucu yine yeni bir anahtar üretir ve aynı delik açılır. Doğru yaklaşım ikili: anahtar sunucuda üretilir, oturuma bağlanır ve forma gömülür. Böylece tarayıcı aynı sayfayı kaç kez gönderse de taşıdığı anahtar aynıdır; sunucu ise anahtarın gerçekten kendi ürettiği bir değer olduğunu doğrulayabilir. Dijital ürün satış scripti tarafında bu, sepet oluşturulurken yazılan tek bir satırdır ama çift teslimat riskinin yarısını o satır kapatır.
Rastgele mi, türetilmiş mi? İkisi aynı şey değil
Rastgele anahtar (UUIDv4 gibi) üretmek kolaydır ve çakışma riski pratikte sıfırdır. Ama rastgele anahtarın bir zaafı var: aynı kullanıcı aynı sepeti iki farklı sekmede açarsa iki farklı rastgele anahtar doğar ve sistem ikisini de meşru sayar. Türetilmiş anahtar ise sipariş içeriğinden hesaplanır: kullanıcı kimliği, ürün kimliği, adet, tutar, para birimi ve kısa bir zaman penceresi birleştirilip özetlenir. Aynı içerik aynı pencerede tekrar gelirse aynı anahtar çıkar, yani ikinci sekme de aynı kapıya çarpar. Olgun bir e-pin satış yazılımı genelde ikisini birlikte kullanır: kullanıcı akışında türetilmiş anahtar, sağlayıcı bildiriminde ise sağlayıcının kendi işlem kimliğiyle eşlenen rastgele anahtar.
| Strateji | Nasıl üretilir | Çift sekmeyi durdurur mu | Tekrar gelen bildirimi durdurur mu | Risk |
|---|---|---|---|---|
| UUIDv4 (saf rastgele) | Sunucuda tek seferlik üretim | Hayır | Evet | Çoklu sekmede boşluk |
| Türetilmiş özet | SHA-256(kullanıcı+ürün+adet+tutar+pencere) | Evet | Evet | Pencere çok uzunsa gerçek 2. alım bloklanır |
| Sağlayıcı işlem kimliği | Ödeme tarafından gelen benzersiz referans | Hayır | Evet | Ödeme öncesi aşamayı korumaz |
| Artan sayaç | Veritabanı auto-increment | Hayır | Hayır | Tahmin edilebilir, tekrarda yeni değer |
| Hibrit (önerilen) | Türetilmiş anahtar + sağlayıcı referansı eşlemesi | Evet | Evet | Kurulum biraz daha uzun |
Anahtarı neyden besleyeceksin: girdi listesini yanlış seçmek
Türetilmiş anahtarın kalitesi, içine koyduğun alanların kalitesidir. İçine oturum kimliği koyarsan kullanıcı çıkış yapıp girdiğinde koruma kırılır. İçine tam zaman damgası koyarsan her saniye yeni anahtar doğar ve hiçbir şeyi korumazsın. İçine kupon kodunu koymazsan indirimli ve indirimsiz aynı sepet farklı tutarda iki sipariş üretebilir. Sağlıklı girdi kümesi şöyle görünür: kullanıcı kimliği, ürün kimliği, varyant, adet, kuruş cinsinden tutar, para birimi ve 90-180 saniyelik yuvarlanmış zaman kovası. Zaman kovasını yuvarlaman şart: floor(unix/120) gibi bir ifade, iki dakika içindeki tekrar denemelerini aynı kovada tutar, üçüncü dakikada gerçekten yeni bir alım yapmak isteyen müşteriyi ise serbest bırakır.
Çift teslimat olaylarının kök nedeni dağılımı
- Tekrar gelen ödeme bildirimi 45%
- Kullanıcının çift gönderimi 25%
- Elle yapılan yönetici işlemi 20%
- Kuyruk/iş tekrarı 10%
Anahtarı nereye yazacaksın: tabloyu kilitlemezsen anahtar işe yaramaz
Anahtarı ürettin, peki nerede tutuyorsun? Eğer "önce var mı diye bakıyorum, yoksa yazıyorum" diyorsan koruma yalnızca yavaş trafiğe dayanır. İki istek aynı milisaniyede geldiğinde ikisi de "yok" cevabını alır ve ikisi de yazar. Doğrusu, kararı uygulamaya değil veritabanına verdirmektir: anahtar kolonuna UNIQUE indeks koy ve yazmayı dene. İkinci yazma hata döndürürse bu bir arıza değil, koruma çalıştı demektir; o isteği reddetmek yerine ilk siparişin sonucunu döndürürsün. Müşteri iki kez tıkladığını fark etmez, sen de iki kod harcamazsın. Epin yazılım katmanında bu davranışa "yanıt tekrarı" denir ve iyi bir uygulamanın ayırt edici işaretidir.
Anahtarın ömrü ne kadar olmalı?
Sonsuza kadar saklanan anahtar, bir yıl sonra aynı ürünü yeniden almak isteyen müşteriyi bloklar. Bir dakika sonra silinen anahtar ise geciken bildirimi kaçırır. Pratikte iki katmanlı bir ömür işe yarar: türetilmiş anahtar zaman kovasıyla birlikte kendiliğinden eskir, sağlayıcı referansına bağlı kayıt ise en az 30 gün saklanır; çünkü ödeme sağlayıcıları iade ve itiraz bildirimlerini günler sonra tekrar gönderebilir. Kayıtta yalnızca anahtarı değil, o anahtarla üretilen sonucun özetini de tut: hangi sipariş açıldı, hangi kod teslim edildi, hangi tutar tahsil edildi. Böylece tekrar gelen istek karşısında sıfırdan iş yapmak zorunda kalmazsın.
Test etmediğin koruma, koruma değildir
Idempotency en sinsi konulardan biridir, çünkü doğru çalıştığında hiçbir şey olmaz. Bu yüzden onu görünür kılmak gerekir. Aynı gövdeyi aynı anahtarla üst üste on kez gönderen küçük bir betik yaz; sonunda tek sipariş ve tek teslimat görmelisin. Ardından anahtarı değiştirip aynı gövdeyi gönder; bu kez iki sipariş görmen normaldir. Son olarak ödeme bildirimini elle iki kez tetikle. Bu üç testi geçen bir e-pin satış scripti, gerçek yükte de ayakta kalır. Loglara da anahtarı yaz; bir gün "bu müşteriye iki kod mu gitti" tartışması yaşarsan tek arama ile bitirirsin.
| Kontrol | Beklenen durum | Sıklık | Durum |
|---|---|---|---|
| Anahtar sunucuda üretiliyor | Sepet açılışında, oturuma bağlı | Bir kez kurulum | Zorunlu |
| UNIQUE indeks mevcut | idempotency_key kolonunda | Bir kez kurulum | Zorunlu |
| Tutar kuruş cinsinden | Ondalık yuvarlama farkı yok | Her sürümde | Zorunlu |
| Zaman kovası yuvarlanmış | 90-180 sn aralığı | Ayda bir gözden geçir | Önerilir |
| Yanıt tekrarı çalışıyor | Aynı anahtar aynı sonucu döndürür | Her sürümde | Zorunlu |
| Bildirim kaydı 30 gün | İade/itiraz için saklanıyor | Üç ayda bir | Önerilir |
| Yükleme testi yapıldı | 10 eşzamanlı istek, 1 teslimat | Her büyük güncelleme | Zorunlu |
Anahtarı üretirken tutarı kuruş cinsinden tam sayı olarak kullan. 149,90 gibi ondalıklı bir değer farklı katmanlarda farklı yuvarlanır ve aynı sipariş için iki ayrı anahtar doğurur; korumanın en sessiz kaçağı tam olarak budur.
Hazır bir altyapıda bu iş nasıl duruyor?
Bütün bunları kendi kodunda kurmak mümkün, ama her ödeme sağlayıcısı için ayrı eşleme, her ürün tipi için ayrı pencere ve her yönetici işlemi için ayrı koruma yazmak zaman alır. Pinova'nın sipariş katmanı bu işi kurulumla birlikte hazır getirir: sepet açılışında türetilmiş anahtar, ödeme dönüşünde sağlayıcı referansı eşlemesi, veritabanı seviyesinde tekillik ve tekrar gelen isteğe aynı yanıtı döndüren bir akış. Sen dijital ürün satış yazılımı tarafında ürün ve fiyat yönetmeye odaklanırken çift teslimat tartışması gündeminden düşer. Modüllerin ayrıntısını özellikler bölümünde, paket farklarını ise fiyat tablosunda görebilirsin.
Çift teslimatı bir kez kapat, bir daha açma
Pinova'nın sipariş ve teslimat katmanı idempotency korumasını kurulumdan itibaren açık getirir. Kendi kodunu kurcalamadan aynı güvenliği al.
Idempotency anahtarını müşteri görebilir mi, sorun olur mu?
Görebilir, sorun olmaz. Anahtar bir sır değil, bir kimliktir. Yine de tahmin edilemez olması iyidir; böylece kimse başkasının siparişinin sonucunu kendi anahtarıyla çekmeye çalışmaz. Sunucu tarafında anahtarı her zaman kullanıcı kimliğiyle birlikte doğrula.
Zaman penceresi kaç saniye olmalı?
90-180 saniye çoğu satıcı için doğru aralıktır. Bu süre, çift tıklama ve tekrar gelen bildirimleri yakalar ama gerçekten ikinci bir kod almak isteyen müşteriyi engellemez. Toptan satış yapıyorsan pencereyi kısalt, tek adetlik ürünlerde uzat.
Anahtar varsa ayrıca kilit gerekir mi?
Evet. Anahtar "bu istek daha önce geldi mi" sorusunu yanıtlar; kilit ise aynı anda gelen iki isteğin aynı stok satırına dokunmasını engeller. İkisi birbirinin yerine geçmez, birlikte çalışırlar.
Mevcut sistemime sonradan ekleyebilir miyim?
Ekleyebilirsin. Kolon ve UNIQUE indeks ile başla, sonra sepet açılışına üretimi, en sonda bildirim tarafına eşlemeyi bağla. Geçiş sırasında anahtarı boş gelen eski siparişleri ayrı ele almayı unutma.
Idempotency, müşterinin hiç fark etmediği ama senin kasanda doğrudan görünen bir disiplindir. Bir kez doğru kurduğunda "acaba iki kod mu gitti" sorusunu bir daha sormazsın. Bu korumanın gerçek bir panelde nasıl göründüğünü, sipariş kaydında anahtarın nerede durduğunu ve tekrar gelen bildirimin nasıl sessizce yutulduğunu görmek istersen canlı demo üzerinden kendi test siparişini açıp aynı isteği iki kez göndermeyi dene; tek teslimat gördüğünde neyi kazandığını anlayacaksın.

