İçeriğe geç
Hüseyin Bozdemir

Rehber

Ödeme sistemi nasıl tasarlanır? Sahadan backend rehberi

Ödeme sistemi tasarlarken idempotency, outbox pattern, log maskeleme ve IDOR: sahadan beş yazıyı ödeme yolculuğu sırasına dizen .NET backend rehberi.

Ödeme sistemleri, lise zamanında heves ettiğim bir alandı. Ödeme sistemleri teknolojik anlamda her şeyi barındıran bir mekanizma olduğu için, onları anlatmak hem ödemeyi hem de teknik altyapıyı anlatmak demek.

Bu rehberi okuduğunda ödeme sistemlerindeki teknikleri ve altyapıları anlayacaksın. Fakat burada ödeme kuruluşlarının ya da bankaların teknik altyapısını anlatmak gibi bir amacım yok; genel olarak ödeme sistemlerinde ne kullanıldığını göstereceğim.

Bölüm 01

Idempotency: para iki kez çekilmesin

Idempotency, aynı istek bir kez de gelse on kez de gelse sistemde bıraktığı etkinin aynı kalmasıdır. Ödeme API'lerinde bir adım fazlası gerekir: ikinci istek, ilk isteğin cevabını da almalı. Tekrarı yalnızca reddeden bir sistem parayı korur ama kullanıcıyı korumaz; parası çekilen kullanıcı ekranda "işlem başarısız" görür.

Tekrar yalnızca çift tıklamadan gelmez: istemci, gateway, mesaj kuyruğu ve arka plan işleri de aynı ödemeyi yeniden gönderir. Yazıda kilidin neden yetmediğini, idempotency key ile saklanan cevabı, consumer tarafındaki tekrarı ve bankadan hiç cevap gelmediğinde ödemeyi hangi durumda tutacağını anlatıyorum.

Yazının tamamı · 13 dk okumaIdempotency nedir? Ödemede para iki kez çekilmesin

Bölüm 02

Idempotency key istemcide nerede üretilir?

Sunucuda idempotency'yi kursan bile istemci her tıklamada yeni bir anahtar üretiyorsa hiçbir şey korunmaz: iki tıklama, iki anahtar, iki ödeme. Kısa cevap: anahtar butona basışta değil, ödeme niyeti oluştuğu anda, yani ödeme ekranı açılırken üretilir.

Tutarın ya da taksidin ekranda değişebildiği akışlarda anahtar snapshot anında doğar; aynı anahtarla farklı gövde gelirse sunucu 422 döner. Sayfa yenilemesine karşı korumayı anahtara yüklemek yerine ekran açılırken siparişin ödenip ödenmediğini sormak daha sağlam. Yazıda bunun koddaki hali ve ağ sekmesinden nasıl fark edileceği var.

Yazının tamamı · 4 dk okumaIdempotency key istemcide nerede üretilir?

Bölüm 03

Outbox pattern: kuyruk yokken arka plan işi

Ödeme kaydedildi, sıra bildirimde ya da bir sonraki adımda. Bu yan etkiyi isteğin içinde yaparsan kullanıcı mail sunucusunu bekler; hata verirse ya işlemi boşuna başarısız sayarsın ya da sessizce kaybedersin. Outbox pattern, yapılacak işi iş verisiyle aynı transaction'da bir tabloya yazmak ve ayrı bir süreçle oradan işlemektir; ikisi ya birlikte kaydedilir ya hiç.

Yazıda outbox'ı broker'ın önünde bir ara durak olarak değil, RabbitMQ yokken kuyruğun kendisi olarak kullanmayı anlatıyorum: EF Core tablosu ve BackgroundService ile. Tablo kuyruğa dönüşünce kuyruğun dertleri de gelir: aynı satırı iki işçinin alması, başarısız işler ve sonucu belirsiz kalan para çıkışı gibi hiç tekrar denenmemesi gereken işler.

Yazının tamamı · 12 dk okumaOutbox pattern nedir? .NET ve EF Core ile

Bölüm 04

Log maskeleme: kart numarası log'a düşmesin

Kart numarasını her endpoint'te regex'le maskelemek yaygın yöntem; çağırmayı unutan tek bir endpoint numarayı log'a açık düşürür. Log maskeleme, hassas değeri log'a yazılmadan önce okunamaz hale getirmektir; kartta genelde ilk 6 ve son 4 hane kalır. Sorun maskelemenin kendisi değil, birinin hatırlamasına bağlı olması.

Önerim varsayılanı tersine çevirmek: kart numarası string olarak dolaşmasın, ToString'i her zaman maskeli dönen kendi tipini taşısın; açık değer yalnızca bankaya giden istekte, adıyla çağrılan bir metotla alınsın. .NET'in Redaction aracı iyi bir katman ama tek başına yetmiyor; regex'in doğru yeri, entegrasyon testinde bütün log çıktısını tarayan son kontrol.

Yazının tamamı · 5 dk okumaLog maskeleme: .NET'te kart numarası sızmasın

Bölüm 05

IDOR: [Authorize] neden yetmez?

IDOR, giriş yapmış bir kullanıcının istekteki id'yi değiştirerek başkasının kaydına erişebilmesidir. Token geçerli, [Authorize] yerinde, rol kontrolü geçiyor; ama kimse "bu kart bu kişiye mi ait" diye sormuyor. Kimlik doğrulama "kimsin" der, aitlik "bu senin mi" diye sorar; ikisi ayrı kontroldür ve ikisinin de geçmesi gerekir.

Herkes id'yi kontrol etmesi gerektiğini bilir; asıl soru bu kontrolü, yarın yeni bir endpoint yazan birinin unutamayacağı yere nasıl koyacağın. Yazıda kaynağın sahibini entity'nin kendisine söyleten yapıyı, token'da taşınan hiyerarşik aitliği, body'deki sahip id'sine neden güvenilmeyeceğini ve 403 ile 404 arasındaki seçimi anlatıyorum.

Yazının tamamı · 14 dk okumaIDOR: ASP.NET Core'da [Authorize] neden yetmez?

Araçlar

Sırada

Bu rehbere eklenecek, henüz yazılmamış konular.

  • Banka timeout ve retry

    Cevabı gelmeyen çağrıyı ne zaman, kaç kez ve hangi referansla tekrar denemeli.

  • Mutabakat

    Gün sonunda bankanın kaydıyla kendi kaydın tutmuyorsa farkı nasıl bulur, nasıl kapatırsın.

  • 3-D Secure akışı

    Kullanıcının bankaya gidip döndüğü akışta durum nerede tutulur, yarıda kalan işlem ne olur.

  • Webhook güvenliği

    Bankadan ya da ödeme sağlayıcıdan gelen bildirimin gerçekten ondan geldiğini ve tekrar olmadığını doğrulamak.

Sık sorulan sorular

Idempotency key istemcide mi sunucuda mı üretilir?

İstemcide, ödeme niyeti oluştuğu anda; sunucu yalnızca saklar ve tanır. Anahtarı sunucu üretirse ilk isteği tekrarına bağlayacak bir şey kalmaz, çünkü tekrar eden istek anahtarı henüz bilmez. Aynı anahtar farklı bir gövdeyle gelirse bu tekrar değil, istemci hatasıdır ve 422 döner.

Banka timeout verdiğinde ödeme başarısız sayılmalı mı?

Hayır. Timeout bir sonuç değil, cevabın gelmemesidir; para çekilmiş olabilir. Ödemeyi "bankaya iletildi" gibi ara bir durumda tut, kendi referansınla bankaya sonucu sor ve sonuç bilinene kadar aynı fatura için yeni ödeme açma. Kullanıcıya da "başarısız" değil, "sonuç bekleniyor" de.

Kart numarası log'a hiç yazılmamalı mı?

Açık hali hiç yazılmamalı. PCI DSS gösterimde en fazla BIN ve son dört haneye izin verir; log'da daha azını göstermek daha güvenli. Bunu endpoint başına regex'e değil, ToString'i maskeli dönen bir tipe ve log çıktısını tarayan bir teste bağla; HttpLogging'in gövde kaydı ödeme endpoint'lerinde kapalı kalsın.