Cüzdan modülü, e-pin satan bir sitenin en masum görünen parçasıdır. Kullanıcı tablosuna bir bakiye sütunu eklersin, para yatırınca artırırsın, alışverişte azaltırsın. İlk bin işlemde kimse bir şey fark etmez. Sonra bir gün destek kutusuna şu mesaj düşer: bakiyem 240 lira görünüyordu, sipariş verdim, hem kodu aldım hem bakiyem düşmedi. Ve o mesajı gönderen kişi, bunu ikinci kez denemeyi öğrenmiş demektir.
Bakiye bir sayı değildir; bir hareketler toplamıdır. Bu cümleyi mimarinin merkezine koymadığın her cüzdan, büyüdükçe hata üretir. Çünkü tek sütunlu yaklaşımda para nereden geldi, nereye gitti, hangi sipariş yüzünden azaldı sorularının cevabı hiçbir yerde yazılı değildir. Sağlam bir e-pin satış yazılımı cüzdanı muhasebe defteri gibi kurar: hareketler değişmez, bakiye bu hareketlerden türetilir. Aşağıda bu defter mimarisini, eşzamanlılık kilitlerini, idempotency anahtarını ve gecelik mutabakatı dijital ürün satış scripti bağlamında tek tek açıyorum.
Bakiye Sütunu Neden Bir Gün Yalan Söylemeye Başlıyor?
Tek sütun, bir sonucu saklar ama sebebini saklamaz. Sonuç bozulduğunda geri dönüp onarabileceğin bir kaynak kalmaz. Hataların çoğu da tam olarak burada doğar: bir iade sırasında sütun elle düzeltilir, bir kampanyada toplu güncelleme yapılır, bir hata sonrası destek personeli bakiyeyi gözle uygun bir sayıya çeker. Her müdahale, sistemin gerçekten ne kadar borçlu olduğunu biraz daha bulanıklaştırır. Doğru yapı ise iki katmanlıdır: değişmez bir hareket defteri (wallet_transactions) ve performans için tutulan, ama defterden doğrulanabilen bir özet bakiye. Özet bozulsa bile defterden yeniden hesaplayabilirsin; defter yoksa hiçbir şeyi kurtaramazsın.
Defteri Nasıl Kuracaksın? Alan Alan Şema
Hareket tablosunun her satırı tek bir olayı temsil eder ve asla güncellenmez. Yanlış bir hareket varsa silinmez; ters kayıtla dengelenir. Aşağıdaki şema, epin yazılım kurulumlarında güvenle kullanabileceğin asgari alan setini gösteriyor.
| Alan | Tip | Amaç | Kural |
|---|---|---|---|
| user_id | bigint | Cüzdan sahibi | İndeksli, zorunlu |
| direction | enum | credit / debit | İşaret ayrı tutulur |
| amount_minor | bigint | Kuruş cinsinden tutar | Asla float değil |
| balance_after | bigint | İşlem sonrası bakiye | Denetim için yazılır |
| type | enum | deposit, purchase, refund, adjust, bonus | Raporun temeli |
| ref_type / ref_id | varchar | Sipariş veya ödeme bağı | Birlikte indeksli |
| idem_key | varchar | Tekrarı engelleyen anahtar | Tekil (unique) indeks |
| created_at | datetime | Zaman damgası | Değiştirilemez |
İki İstek Aynı Anda Gelirse Bakiyen Eksiye Düşer mi?
Cüzdanın en klasik açığı yarış koşuludur. Kullanıcı sepeti iki sekmede açar, ikisine de aynı saniyede basar. Kod önce bakiyeyi okur, yeterli bulur, sonra düşer. İki istek de okumayı aynı anda yaptığı için ikisi de yeterli görür ve iki teslimat gerçekleşir. Çözüm uygulama katmanında değil, veritabanı katmanındadır: harcamayı bir transaction içine al, cüzdan satırını satır düzeyinde kilitle (ör. SELECT ... FOR UPDATE), bakiyeyi kilit altında oku, hareketi yaz, özeti güncelle ve işlemi kapat. Alternatif olarak koşullu güncelleme kullanabilirsin: bakiyeyi yalnızca mevcut bakiye >= tutar koşuluyla düşen tek bir UPDATE ifadesi çalıştır ve etkilenen satır sayısı sıfırsa işlemi reddet. İkisinden birini seç, ama ikisinden birini mutlaka uygula.
Bakiye Uyuşmazlığı Sebeplerinin Dağılımı
- Eşzamanlı çift harcama 35%
- Eksik ters kayıt ve iade 30%
- Elle bakiye düzeltmesi 25%
- Kur ve yuvarlama farkı 10%
Aynı Ödeme İki Kez Bildirilirse Ne Olacak?
Ödeme sağlayıcıları bildirimleri tekrar gönderir; bu bir arıza değil, tasarımın parçasıdır. Bu yüzden para yatırma hareketinin idempotent olması şart. Yöntem basit: sağlayıcının işlem numarasından türettiğin bir anahtarı hareket tablosunda tekil indeksle. İkinci bildirim geldiğinde ekleme denemesi çakışır, kodun bunu hata değil zaten işlendi olarak yorumlar ve bakiyeye ikinci kez yazmaz. Aynı disiplin siparişte de geçerlidir: harcama hareketinin anahtarı sipariş kimliğine bağlanır, böylece tekrar eden istek yeni bir düşüm üretmez. Bu tek kısıt, e-pin işinde en sık görülen bedava bakiye istismarını kökten kapatır.
Bloke, İade ve Ters Kayıt: Paranın Ara Durumları
Cüzdan yalnız artı ve eksiden oluşmuyor. Sipariş manuel onay bekliyorsa, tedarikçi tarafında teslimat gecikmişse ya da risk kontrolü devreye girmişse para ne müşteride ne de sende sayılmalıdır. Bunun adı bloke bakiye: tutar cüzdanda durur ama harcanabilir bakiyeden düşülür. Sipariş tamamlandığında bloke düşüme dönüşür, iptal olduğunda bloke serbest kalır. İade tarafında kural tektir: hiçbir hareketi güncellemez veya silmezsin; ters yönde yeni bir hareket yazar, ref alanıyla orijinaline bağlarsın. Böylece defter hem müşterinin hem senin gözünde anlatılabilir bir hikâye olur. Elle düzeltme gerekiyorsa bunu ayrı bir adjust türüyle, yetkili kullanıcı kimliğiyle ve serbest metinli gerekçesiyle yaz; sessiz düzeltme bırakmak, ileride çözülemeyecek bir sapmanın tohumudur.
Gecelik Mutabakat Olmadan Sapmayı Nasıl Göreceksin?
Defter varsa doğrulama da olmalı. Her gece çalışan bir görev, kullanıcı başına defter toplamını hesaplayıp özet bakiyeyle karşılaştırsın; fark varsa alarm üretsin ve o cüzdanı otomatik olarak incelemeye alsın. Buna iki kontrol daha ekle: cüzdan toplamlarının ödeme sağlayıcısı tahsilat toplamlarıyla günlük uyumu ve negatif bakiye taraması. Aşağıdaki liste, bu izleme setini kurarken sırayla geçmen gereken kontrolleri topluyor.
| Kontrol | Nasıl doğrulanır | Sıklık | Aksiyon |
|---|---|---|---|
| Defter - özet uyumu | Kullanıcı başı toplam karşılaştırması | Gecelik | Sapmada cüzdanı dondur |
| Negatif bakiye | bakiye < 0 sorgusu | Saatlik | İncelemeye al |
| İdempotency kısıtı | Tekil indeks testi | Her sürümde | Dağıtımı durdur |
| Kilit davranışı | Eşzamanlı yük testi | Her sürümde | Çift harcama sıfır olmalı |
| Elle düzeltme oranı | adjust hareket sayısı | Haftalık | Kök sebebi ara |
| Bloke yaşlanması | Uzun süre açık bloke listesi | Günlük | Serbest bırak veya kapat |
| Sağlayıcı mutabakatı | Tahsilat toplamı - deposit toplamı | Günlük | Farkı raporla |
Tutarları kuruş cinsinden tam sayı sakla. Ondalıklı tiplerle yapılan toplamalarda oluşan mikro yuvarlama farkları, binlerce hareket sonrası mutabakatı imkânsız hale getirir.
Cüzdanı defter mimarisiyle kurulu bir altyapıda kullan
Pinova, hareket defteri, bloke bakiye, ters kayıt ve gecelik mutabakat mantığını hazır getiren lisanslı bir e-pin satış yönetim yazılımıdır. Bakiye mantığını sıfırdan yazmakla vakit kaybetmezsin.
Özet bakiye sütununu tamamen kaldırsam olmaz mı?
Küçük hacimde olur ama her sayfa yüklemesinde bütün hareketleri toplamak pahalıdır. Doğru yaklaşım, özeti performans için tutmak ve defterden düzenli olarak doğrulamaktır.
Cüzdan bakiyesini nakde çevirme talebi nasıl yönetilmeli?
Talep anında tutar bloke edilir, yetkili onayından sonra çıkış hareketi yazılır. Onay bekleyen tutar hiçbir zaman harcanabilir bakiyede görünmemeli.
Bonus ve kupon bakiyesi aynı defterde mi durmalı?
Aynı defterde ama ayrı türde ve tercihen ayrı bir bakiye kovasında. Bonusun çekilebilir olmaması gerekiyorsa bunu tür bazında ayırmak tek sağlıklı yol.
Çoklu para birimi desteklerken nasıl davranmalıyım?
Her para birimi için ayrı cüzdan ve ayrı defter tut; dönüşümü iki ayrı hareket olarak yaz ve kullanılan kuru hareket satırına kaydet.
Cüzdan modülü, e-pin işinde müşterinin sana duyduğu güvenin somutlaştığı yer; oradaki tek bir sapma, yüzlerce başarılı teslimatın itibarını götürür. Defter mimarisini, bloke mantığını ve mutabakat raporlarını hazır bir temelde kullanmak istiyorsan özellik listesine göz at, lisans planlarını fiyat bölümünden karşılaştır ve hareket kayıtlarının nasıl tutulduğunu somut görmek için canlı demo üzerinde bakiye yükleyip bir sipariş tamamla; her hareketin deftere nasıl düştüğünü adım adım izleyebilirsin.

