Kredi kartı ekstresini koda dökmek: kendi finans panelimi nasıl yazdım
Bankaların uygulamaları kendi kartlarını iyi gösteriyor ama birden fazla hesabı, abonelikleri ve taksitleri tek bakışta görmek zor. Bu yüzden bu sitenin arkasında yalnız bana açık bir panel yazdım. Bu yazı, kredi kartının kurallarını koda dökerken aldığım kararlar hakkında.
Yığın
- Next.js 15, App Router: sayfalar sunucu bileşeni, formlar server action.
- Drizzle ORM + libSQL: tek kullanıcılı bir uygulama için Postgres fazla geldi. Kendi sunucumda çalışan libSQL, SQLite'ın sadeliğini ağ üzerinden erişimle birleştiriyor.
- Vercel: her gönderimde yayın, günde iki kez çalışan zamanlanmış görevler.
Hesap kesimi: her şeyin çıpası
Kredi kartında neredeyse her kural hesap kesim tarihine bağlı: taksitin hangi ekstreye düşeceği, faizsiz süre, asgari ödeme. İlk kararım, kartı bir "hesap kesim günü" ve bir "son ödeme günü" ile modellemek oldu. Ayın 31'i gibi kısa aylarda olmayan günler, o ayın son gününe kayar.
/** Bugün ya da sonrasındaki ilk "ayın X'i" */
export const nextMonthDay = (day: number, from: string) => {
let [y, m] = from.split("-").map(Number);
const d = Number(from.slice(8, 10));
if (d > Math.min(day, daysIn(y, m))) { m++; if (m > 12) { m = 1; y++; } }
return isoOf(y, m, Math.min(day, daysIn(y, m)));
};
Taksitler kesim gününde düşer
Taksitleri başta her ayın başında hareket olarak yazıyordum. Bu, bankanın ekstresiyle tutmuyordu: taksit, alışverişin yapıldığı güne değil, hesap kesim tarihine yansır. Motoru, her taksiti kartın bir sonraki kesim gününde "otomatik" kaynaklı bir harcama olarak yazacak şekilde değiştirdim.
Aynı taksitin iki kez yazılmaması için her kaydın bir anahtarı var; zamanlanmış görev iki kez çalışsa bile sonuç değişmiyor. Kısacası: aynı istek iki kez gelir, sistem buna hazır olmalı.
Güncel dönem borcu: bankayla aynı rakam
Panelimdeki kart borcu, bankanın gösterdiği "güncel dönem borcu" ile tutmuyordu. Fark, bankanın bir sonraki ekstreye düşecek taksiti de borca eklemesinden kaynaklanıyordu. Sonunda kartı üç rakamla gösterdim:
const borc = balanceAt(kart.id, sonKesim); // kesinleşmiş ekstre
const guncel = borc + buDonemTaksit; // bankanın "güncel dönem borcu"
const kullanilabilir = limit - borc - kalanTaksitler; // taksitlerin tamamı limitten düşer
Bu rakamların ne anlama geldiğini finans tarafından ayrı bir yazıda anlattım.
Otomatik ödeme ve bildirimler
Kartın son ödeme gününde, verdiğim talimata göre vadesiz hesaptan karta transfer kaydı düşüyor. Sabah çalışan görev aynı zamanda telefonuma bildirim gönderiyor: o gün antrenman varsa, kartın son ödemesine üç gün kaldıysa ya da bir arkadaşımın doğum günüyse. Bildirimler Web Push ile geliyor; ana ekrana eklenen site, iPhone'da uygulama gibi bildirim alabiliyor.
Neyi saklamıyorum
Kart numarası, CVV, IBAN ya da banka şifresi bu sistemin hiçbir yerinde yok. Panel yalnız tutar, tarih ve açıklama tutuyor; banka ekstresini içe aktarırken de yalnız bu alanlar okunuyor. Kişisel bir araçta bile en güvenli veri, hiç tutulmayan veridir.
Hesaplama araçları
Panel için yazdığım hesap mantığının bir kısmını herkesin kullanabileceği araçlara dönüştürdüm: asgari ödeme, gecikme faizi, taksit ve hesap kesim hesaplayıcıları. Hepsi tarayıcıda çalışıyor; girdiğiniz tutarlar hiçbir yere gönderilmiyor.