Çoğu satıcı bakiye tutarsızlığını bir hesap hatası sanır. "Bayi 500 lira yüklemiş, 480 lira harcamış, panelde 40 lira görünüyor; demek ki bir yerde iki kez yükleme yapılmış" diye düşünür ve kayıtları elle düzeltir. Oysa kayıt yanlış girilmedi. Olan şey, bakiyenin kod içinde okunup hesaplanıp geri yazılmasıydı. Aynı bayi iki sekmeden iki sipariş verdiğinde iki süreç de bakiyeyi 500 okudu, ikisi de kendi tutarını düşüp sonucu yazdı ve ikinci yazma birincinin etkisini sildi. Bayi iki ürün aldı, bakiyesinden bir ürünün tutarı düştü. Kimse hile yapmadı; sistem yoktan para üretti.
Bu, dijital ürün satan her işletmenin eninde sonunda çarptığı duvardır ve düzeltmesi de sandığından basittir: bakiyeyi uygulama değil veritabanı hesaplamalı. Yani "yeni değeri yaz" değil, "şu kadar düş" demelisin. Buna atomiklik denir ve doğru kurgulanmış bir e-pin satış scripti ile eline tutuşturulan derme çatma bir betik arasındaki en keskin sınır burasıdır. Çünkü stok kaybını fark edersin, bakiye kaybını genellikle fark etmezsin.
Neden "oku, hesapla, yaz" üçlüsü bir para kaçağıdır?
Bu üçlünün sorunu okuma ile yazma arasındaki zamandır. O aralıkta veritabanı başka bir sürecin de aynı satırı okumasına izin verir, çünkü basit bir okuma hiçbir şeyi kilitlemez. Yazma anında ise her süreç elindeki eski değerden yola çıkarak yeni bir toplam üretir. Sonuç, son yazanın kazandığı bir tabloya dönüşür. Dahası bu kayıp asimetriktir: düşme işlemlerinde kayıp sana zarar verir (para düşmez ama ürün gider), yükleme işlemlerinde kayıp müşteriye zarar verir (yükleme kaybolur). İkisi de güven kaybıdır. Çözüm, kararı tek bir ifadeye sıkıştırmaktır: UPDATE ... SET balance = balance - :tutar WHERE id = :id AND balance >= :tutar. Bu ifade hem düşmeyi hem yeterlilik kontrolünü aynı anda yapar ve etkilenen satır sayısı sana sonucun ne olduğunu söyler.
Etkilenen satır sayısı neden en değerli bilgi?
Yukarıdaki ifade bir satır etkilediyse para düşmüştür ve siparişi açabilirsin. Sıfır satır etkilediyse iki olasılık vardır: bakiye yetersizdi ya da satır yoktu. Her iki durumda da teslimata geçmemelisin. Buradaki güzellik, ayrı bir "bakiye yeterli mi" sorgusuna gerek kalmamasıdır; çünkü ayrı sorgu yine bir zaman boşluğu yaratır. Yeterlilik kontrolünü güncellemenin içine koyduğunda bakiyenin eksiye düşmesi teorik olarak imkânsız hale gelir. Birçok işletme bu tek satırlık değişiklikle yıllardır peşinde koştuğu "eksi bakiye" kayıtlarından kurtulur. Olgun bir dijital ürün satış yazılımı bu deseni cüzdan, komisyon ve iade akışlarının tamamında aynı biçimde uygular.
| Yaklaşım | Sorgu şekli | Eşzamanlı güvenli | Eksi bakiye riski | Denetim izi |
|---|---|---|---|---|
| Oku-hesapla-yaz | SELECT + UPDATE (mutlak değer) | Hayır | Yüksek | Yok |
| Koşullu atomik düşme | UPDATE ... balance-:t WHERE balance>=:t | Evet | Yok | Kısmi |
| Satır kilidi + güncelleme | SELECT FOR UPDATE + UPDATE | Evet | Yok | Kısmi |
| Defter (ledger) yaklaşımı | INSERT hareket + türetilmiş bakiye | Evet | Yok | Tam |
| Hibrit (önerilen) | Atomik düşme + hareket kaydı, tek işlemde | Evet | Yok | Tam |
Transaction'ı nerede başlatıp nerede bitirmelisin?
Atomik güncelleme tek başına bakiyeyi korur ama siparişi korumaz. Çünkü teslimat genellikle birkaç yazma içerir: bakiye düşer, sipariş açılır, stok satırı işaretlenir, hareket kaydı yazılır. Bunların biri olur diğeri olmazsa tablolar arasındaki gerçeklik kopar. Bu yüzden bu dört yazmanın tamamı tek bir işlem sınırının içinde olmalı; hata halinde hepsi birlikte geri alınmalı. Kritik kural şudur: dış dünyaya dokunan hiçbir iş işlem sınırının içinde olmamalı. Sağlayıcı API'sine kod istemek, e-posta göndermek, webhook tetiklemek işlem dışında kalmalı. Aksi halde yavaş bir API çağrısı veritabanı kilitlerini saniyelerce tutar ve kampanya saatinde tüm panel taş kesilir.
Bakiye tutarsızlıklarının kaynağı
- Oku-yaz yarışı 40%
- Yarı tamamlanmış teslimat 30%
- İade/iptal akışı 20%
- Elle düzeltme 10%
Satır kilidi ne zaman gerekir, ne zaman gereksiz yüktür?
Tek bir bakiye satırını değiştiriyorsan koşullu atomik güncelleme fazlasıyla yeterlidir; ayrıca kilit almak gereksiz gecikme ekler. Ancak karar bir satırdan fazlasına bakıyorsa kilit şart olur. Örnek: bayinin bakiyesi yetiyorsa ürünün fiyatını kademeli komisyonla hesaplayıp iki farklı hesaba bölüştürmek istiyorsun. Burada okuduğun değerlere göre karar veriyorsun, dolayısıyla okuduğun satırların karar sonlanana kadar değişmemesi gerekir. Bu durumda ilgili satırları işlem içinde kilitleyerek oku, hesabını yap, yaz ve çık. Kilitleri her zaman aynı sırayla al (örneğin her zaman küçük kimlikten büyüğe); farklı sıralarda kilit alan iki süreç karşılıklı bekleyip birbirini kilitler ve bu hatayı üretimde teşhis etmek çok zordur.
Neden bakiyeyi tek bir sayı olarak saklamak yeterli değil?
Tek sayı sana bugünkü durumu söyler, ama dünkü durumun nasıl oluştuğunu söylemez. Bir bayi "bende 300 lira vardı" dediğinde elinde kanıt olmalı. Bu yüzden her bakiye değişikliği bir hareket kaydı doğurmalı: tarih, tutar, yön, sebep, ilişkili sipariş, işlemi yapan kullanıcı ve işlem sonrası bakiye. Bu kayıt hem müşteri tartışmalarını bitirir hem de mutabakat yapmanı sağlar: hareketlerin toplamı ile bakiye kolonunun eşleşmediği an, bir yerde kod dışı bir müdahale olduğunu anlarsın. Ciddi bir epin yazılım kurulumunda bu mutabakat günlük olarak otomatik koşar ve sapma bulduğunda seni uyarır. Hareket kaydını ayrıca değiştirilemez tutmak iyidir: düzeltme, eski satırı güncellemekle değil ters yönlü yeni bir satır yazmakla yapılır.
Bakiyeyi kuruş cinsinden tam sayı tut. Ondalıklı tiplerde 0,1 + 0,2 işlemi beklediğin sonucu vermez ve binlerce hareketten sonra kuruşluk sapmalar birikerek mutabakatı bozar. Ekranda bölerek gösterirsin, veritabanında asla.
İade ve iptal akışını unutursan koruma yarım kalır
Herkes düşme tarafını düşünür, geri yükleme tarafını çoğu kişi sonradan ekler ve tam orada delik açılır. Teslimat başarısız olduğunda parayı geri yüklemen gerekir; ama aynı başarısızlık bildirimi iki kez gelirse iki kez yükleme yaparsın ve bu kez müşteri yoktan para kazanır. Bu yüzden her geri yükleme de bir kimliğe bağlanmalı: aynı sipariş için aynı sebeple ikinci bir iade hareketi yazılamamalı. Pratikte bunu hareket tablosuna konan bir tekillik kuralı ile çözersin (sipariş kimliği + hareket tipi). Aynı mantık kısmi iadelerde, kupon geri alımında ve komisyon düzeltmesinde de geçerli. E-pin scripti seçerken bu akışların panelde nasıl göründüğüne bakmak, özellik listesinden çok daha fazla şey anlatır.
| Kontrol maddesi | Doğrulama yöntemi | Hedef | Durum |
|---|---|---|---|
| Bakiye tam sayı (kuruş) | Şema incelemesi | Yuvarlama kaybı yok | Kritik |
| Koşullu atomik düşme | Sorgu gözden geçirme | Eksi bakiye imkânsız | Kritik |
| Tek işlem sınırı | Kod izleme | Yarım teslimat yok | Kritik |
| Dış çağrı işlem dışında | Kilit süresi ölçümü | Panel kilitlenmiyor | Yüksek |
| Hareket kaydı zorunlu | Her değişimde satır var mı | Tam denetim izi | Yüksek |
| İade tekilliği | Aynı iade iki kez denenir | Çift yükleme yok | Kritik |
| Günlük mutabakat | Hareket toplamı = bakiye | Sapma uyarısı | Orta |
| Kilit sırası sabit | Kod incelemesi | Karşılıklı bekleme yok | Orta |
Kendi cüzdanını yazmak mı, hazır bir çekirdek mi?
Cüzdan yazmak ilk bakışta bir tablo ve iki sorgudan ibaret görünür. Gerçekte ise bayi hiyerarşisi, kademeli fiyat, komisyon paylaşımı, kısmi iade, manuel düzeltme, mutabakat raporu ve her birinin eşzamanlılık altında doğru davranması gerekir. Pinova'nın cüzdan çekirdeği bunları hazır getirir: kuruş tabanlı saklama, koşullu atomik hareketler, tek işlem sınırında sipariş-stok-bakiye bütünlüğü, değiştirilemez hareket defteri ve tekillik korumalı iade akışı. Yani e-pin yazılımı tarafında para mantığını yeniden keşfetmek yerine fiyatlandırma ve büyüme tarafına bakabilirsin. Modül ayrıntıları için özellikler, lisans karşılaştırması için fiyat bölümüne, satın alma için ödeme sayfasına göz atabilirsin.
Cüzdanın artık yoktan para üretmesin
Pinova'nın bakiye çekirdeği atomik hareketler, işlem bütünlüğü ve değiştirilemez hareket defteriyle gelir. Mutabakatı raporla, tartışmayı bitir.
Bakiyeyi her seferinde hareketlerden hesaplasam olmaz mı?
Doğruluk açısından harika, performans açısından zor. Yüz binlerce hareket biriktiğinde her sayfa yüklemesinde toplam almak yavaşlar. Doğru kurgu ikisini birden tutmaktır: hızlı okuma için bakiye kolonu, doğruluk için hareket defteri ve aralarındaki farkı kontrol eden günlük mutabakat.
MySQL yerine dosya veya önbellekte bakiye tutabilir miyim?
Tutmamalısın. Önbellek eşzamanlı güvenlik ve kalıcılık garantisi vermez; sunucu yeniden başladığında ya da iki işçi aynı anda yazdığında para kaybolur. Bakiye her zaman işlem desteği olan kalıcı bir depoda durmalı.
Eksi bakiyeye izin vermem gereken durumlar var mı?
Bayilerine kredi limiti tanıyorsan olabilir. O zaman koşulu sıfır yerine limitle kur: bakiye eksi limitin altına düşemesin. Önemli olan sınırın veritabanı koşulunda yaşaması, kodda bir kontrol olarak kalmaması.
Mevcut sistemimde tutarsızlık var, nasıl temizlerim?
Önce atomik güncellemeye geç, sonra hareket defterini devreye al ve o günü sıfır noktası kabul et. Geçmiş için her hesabın farkını ters hareketle kapat; eski satırları silmek yerine düzeltme hareketi yazmak denetlenebilirliği korur.
Para mantığı, bir satış sisteminde en son fark edilen ama en pahalı hataların yaşadığı yerdir. Atomik güncelleme, tek işlem sınırı ve değiştirilemez hareket defteri üçlüsü kurulduğunda bakiye tartışmaları biter; kalan tek iş büyümeye bakmaktır. Bu üçlünün gerçek bir panelde nasıl göründüğünü, bir bayi hesabında hareket defterinin nasıl okunduğunu ve mutabakat raporunun neyi yakaladığını merak ediyorsan canlı demo üzerinde bakiye yükleyip sipariş vererek kendi hareket satırlarını takip et.

