İçeriğe geç
wedevit

1 Eylül 2026 · 9 dk okuma · yazılım

İlhan Buğra Aslan

Para çekildi ama sipariş oluşmadı: ödeme entegrasyonunun asıl zor kısımları


Ödeme entegrasyonunda zor olan kısım kartı çekmek değil. Sağlayıcının örnek kodunu yapıştırıp test kartıyla başarılı bir işlem görmek yarım gün sürer. Zor olan, o işlemden sonra paranın durumu ile sipariş kaydının durumunun aynı kalması. "Ödeme geçti ama sipariş yok" ya da "sipariş var ama para gelmemiş" vakalarının neredeyse tamamı üç yerden çıkıyor: siparişi tarayıcının geri döndüğü sayfada oluşturmak, aynı isteğin iki kez işlenmesine karşı hiçbir koruma koymamak ve günlük mutabakat yapmamak. Üçü de entegrasyonun ilk haftasında çözülür. Sonradan çözmek, yanına veri temizliğini de getirir.

Tarayıcının geri dönmesi ödemenin sonucu değildir

Yaygın akış şöyle: kullanıcı 3D Secure ekranına gider, bankada doğrulamayı tamamlar, /odeme/basarili adresine döner ve sipariş o sayfada oluşturulur. Test ortamında kusursuz çalışır, çünkü test ortamında kimse sekmeyi kapatmaz. Gerçek hayatta kullanıcı doğrulama SMS'ini beklerken tarayıcıyı arka plana atar, mobil bankacılık uygulamasına geçer, geri döndüğünde sekme tazelenmiştir. Ya da bağlantı tam 3D sayfasından çıkarken kopar. Banka tarafında para çekilmiştir, sizin veritabanınızda hiçbir şey yoktur.

Doğru kurgu şu: geri dönüş sayfası yalnızca kullanıcıya durumu gösterir, kaydı değiştirmez. Kaydı değiştiren tek şey sunucudan sunucuya gelen bildirim (webhook) ve sizin sağlayıcıya sorduğunuz doğrulama sorgusudur. Kullanıcı döndüğünde sonuç henüz elinizde değilse "ödemeniz doğrulanıyor" ekranı gösterip birkaç saniye sorgulayın. Bekletmek, yanlış bilgi göstermekten iyidir.

Webhook en az bir kez gelir ve sırayla gelmez

Ödeme sağlayıcılarının bildirim modeli "at least once" teslimattır. Aynı olay iki kez gelebilir, sonraki olay öncekinden önce gelebilir. Stripe kendi dokümanında olayları üretildikleri sırayla teslim etmeyi garanti etmediğini açıkça yazıyor ve mükerrer teslimatı ayırt etmek için created alanını değil olay kimliğini kullanmanızı söylüyor. Teslim edilemeyen olaylar canlı ortamda üç güne kadar, giderek seyrelen aralıklarla yeniden deneniyor. Uç noktanız beş dakika kapalı kalırsa olaylar kaybolmaz, sonra topluca gelir. Kodunuzun buna hazır olması gerekir.

Bundan üç pratik kural çıkıyor. Birincisi imza doğrulama: sağlayıcı gövdeyi paylaşılan gizli anahtarla imzalar (Stripe'ta Stripe-Signature başlığı, HMAC-SHA256) ve doğrulama ham gövde üzerinden yapılır, JSON'a çevirip yeniden serileştirilmiş hali üzerinden değil. Framework'ünüz gövdeye dokunuyorsa imza tutmaz. İmzadaki zaman damgasını da kontrol edin; resmi kütüphanelerin varsayılan toleransı 5 dakikadır ve bu, ele geçirilmiş bir isteğin günler sonra tekrar oynatılmasını engeller. İkincisi, ağır işi yapmadan önce hızlı 2xx dönün ve işi kuyruğa alın. Üçüncüsü, mükerrerliği veritabanı seviyesinde engelleyin.

Son madde çoğu ekipte eksik kalıyor. "Bu olay işlendi mi diye bak, işlenmediyse işle" mantığı yetmez, çünkü aynı olayın iki kopyası neredeyse hep aynı anda gelir ve iki istek de kontrolü aynı anda geçer. Çözüm tek satır: payment_events tablosunda sağlayıcının olay kimliği üzerinde UNIQUE kısıt, ve işlemeyi o insert ile aynı transaction içinde yapmak. Insert çakışırsa olay zaten işlenmiştir, sessizce 200 dönün. Hata dönerseniz sağlayıcı günlerce yeniden denemeye devam eder.

Idempotency giden isteklerde de gerekir

Sağlayıcıya "şu tutarı çek" isteğini gönderdiniz ve zaman aşımı aldınız. İşlem geçti mi, bilmiyorsunuz. Zaman aşımı isteğin ulaşmadığı anlamına gelmez, cevabın size dönmediği anlamına gelir. Körlemesine tekrar denerseniz müşteriden iki kez tahsilat yapabilirsiniz. Çözüm istemcinin ürettiği bir idempotency anahtarı: aynı anahtarla gelen ikinci istek yeni işlem başlatmaz, ilkinin sonucunu döner. Başlığın standartlaşma süreci IETF HTTPAPI çalışma grubunda Idempotency-Key adıyla devam ediyor; henüz RFC değil, taslak aşamasında, ama sağlayıcıların çoğu bu ismi zaten kullanıyor.

Anahtarı nasıl ürettiğiniz işin can alıcı yeri. Her denemede yeni bir UUID üretmek hiçbir işe yaramaz, çünkü tekrar denemeler farklı anahtar taşır ve sağlayıcı bunları ayrı işlem sanar. Anahtar iş anlamına bağlanmalı: sipariş kimliği ile ödeme denemesi numarasının birleşimi iyi bir başlangıç. Böylece ağ hatası sonrası tekrar deneme aynı anahtarla gider, ama müşteri gerçekten ikinci kez ödemeye kalktığında anahtar değişir.

Ödeme bir boolean değil, durum makinesidir

orders.is_paid sütunu ilk üç ay sorunsuz çalışır, ilk kısmi iadede çöker. Bir kart ödemesinin gerçek durumları en az şunlardır: başlatıldı, doğrulama bekliyor, provizyon alındı, tahsil edildi, kısmen tahsil edildi, iptal edildi, iade edildi, kısmen iade edildi, itiraz edildi. Provizyon ile tahsilat arasındaki ayrım stoklu satışta özellikle işe yarar: kartı bloke edip malı gönderirken tahsil edersiniz, stok çıkmazsa iptal edersiniz. İptal, iadeden farklı olarak müşterinin ekstresinde iz bırakmaz ve komisyon tarafı da daha temiz kalır.

Ödemeyi siparişten ayrı bir varlık olarak modelleyin ve geçişleri yalnızca izinli yönlerde bırakın. Bir siparişin birden fazla ödemesi olabilir: kısmi tahsilat, sonradan eklenen kalem, başarısız denemeden sonra farklı kartla tekrar. Bir ödemenin de birden fazla iadesi olabilir. Bu ilişkileri bire bir kurarsanız ilk kısmi iadede şema değiştirmek zorunda kalırsınız.

Parayı float tutmayın

Tutarları tam sayı olarak, para biriminin en küçük biriminde saklayın: 149,90 TL veritabanında 14990 kuruştur. Yanına para birimi kodunu da yazın. Tek para birimiyle başlayan projelerin çoğu ikincisini birkaç yıl içinde ekliyor ve o noktada geçmiş kayıtlarda "bu tutar hangi para birimindeydi" sorusu cevapsız kalıyor. Kur çevirisi varsa hangi anın kurunu kullandığınızı da kaydedin; sipariş anı ile tahsilat anı arasında fark oluşur ve bu farkı muhasebe mutlaka sorar.

Yuvarlama kuralınızı da yazılı hale getirin. 100 TL'yi üç taksite bölünce 33,33 + 33,33 + 33,34 çıkar. Artan kuruşun hangi tarafa yazılacağına dair açık bir kuralınız yoksa, kısmi iade geldiğinde toplam tutmaz ve farkı bulmak yarım gününüzü alır.

Türkiye'ye özgü üç şey

Taksit, entegrasyonun en çok hata üreten yeri, çünkü müşterinin ödediği tutar ile hesabınıza geçen tutar aynı değil. Taksit seçenekleri kartın BIN'ine ve bankasına göre değişir, vade farkı uygulanıyorsa sepet tutarı da değişir, komisyon oranı taksit sayısıyla birlikte artar. Sepette gösterdiğiniz taksit tablosunu sağlayıcının BIN sorgusundan canlı almazsanız kullanıcı ödeme ekranında başka bir rakam görür ve o noktada sepeti bırakır.

İkincisi çoklu sanal POS. Birden fazla bankayla çalışan işletmelerde işlem, kartın bankasına göre farklı sanal POS'a yönlenir. Bu, "hangi POS ile geçti" bilgisini işlem kaydında tutmayı zorunlu kılar. O alan yoksa iade ederken ve mutabakat yaparken işlemi bulamazsınız.

Üçüncüsü hukuki bir sınır. Kendi ürününüzü satıyorsanız mesele yok. Satıcılar adına para toplayıp sonra onlara aktaran bir pazaryeri kuruyorsanız, üçüncü kişiler adına tahsilat 6493 sayılı kanun kapsamında düzenlenmiş bir faaliyettir ve tahsilatın lisanslı bir ödeme kuruluşu üzerinden yürümesi gerekir. Parayı kendi hesabınızda biriktirip elle dağıtmak teknik olarak kolay, hukuken seçenek değil. Mimariyi kurmadan önce bu kararı verin.

Kart verisini uzak tutun, ama iframe kullanınca bittiğini sanmayın

Kart numarasını kendi formunuzda toplamak PCI DSS kapsamınızı en ağır seviyeye çıkarır. Doğru yaklaşım sağlayıcının barındırdığı alanları veya iframe'i kullanmak. Burada 2025'te değişen bir şey var: PCI DSS v4.0.1'in SAQ A formu (Şubat 2025 revizyonu), gömülü ödeme sayfası ya da iframe kullanan işyerlerinden "sitemin, ödeme sistemini etkileyebilecek script saldırılarına açık olmadığını doğruladım" kriterini karşılamasını istiyor. Konseyin konuyla ilgili SSS'sine göre bu kriter, kullanıcıyı tamamen sağlayıcının sayfasına yönlendiren işyerleri için geçerli değil, sadece gömülü çözüm kullananlar için geçerli. İframe kullanmak sizi script yönetiminden muaf tutmuyor.

Pratikte en sık gözden kaçan şey kart alanlarının üçüncü parti araçlara sızması. Oturum kaydı (session replay) araçları, hata izleme SDK'ları ve pazarlama scriptleri varsayılan ayarlarıyla form içeriğini yakalayabiliyor. Ödeme sayfasında çalışan üçüncü parti script listesini çıkarın, gerekmeyeni kaldırın, kalanları maskeleme açık çalıştırın ve log çıktılarında kart numarası deseni arayan bir testi CI hattınıza ekleyin. Bu testi bir kere yazarsınız, on yıl çalışır.

Ödeme akışını nasıl test edersiniz

Ödeme kodunun mutlu yolu zaten çalışır. Canlıda patlayan şey, kimsenin denemediği hata yollarıdır. Sağlayıcının sandbox ortamında şu senaryoları otomatik teste bağlayın: kullanıcının 3D doğrulamayı iptal etmesi, yetersiz bakiye reddi, tahsilat isteğinde zaman aşımı sonrası aynı idempotency anahtarıyla tekrar deneme, aynı webhook olayının iki kez teslim edilmesi, olayların ters sırada gelmesi (iade bildirimi tahsilat bildiriminden önce) ve imzası geçersiz bir isteğin reddedilmesi.

Sonuncusu en kolay unutulanı. Webhook uç noktanıza imzasız düz bir POST atın. 400 veya 401 dönmüyorsa o uç nokta, internete açık bir "siparişi ödendi yap" düğmesidir. Bu denemeyi entegrasyonu yazan kişiye değil, başka birine yaptırın.

Mutabakat olmadan hiçbirinin çalıştığını bilemezsiniz

Yukarıdakilerin hepsini doğru yapsanız bile, doğru yaptığınızı gösteren tek şey günlük mutabakattır. Üç kaynağı karşılaştırın: kendi ödeme tablonuz, sağlayıcının işlem dökümü ve bankadan gelen hesaba geçiş dosyası. Farklar üç kovaya düşer. Sizde başarılı görünüp sağlayıcıda karşılığı olmayan işlemler. Sağlayıcıda tahsil edilmiş görünüp sizde kaydı olmayanlar, ki bunlar müşterinin parasını alıp malı göndermediğiniz vakalardır ve en pahalısı budur. Bir de tutarı tutmayanlar; genelde komisyon, vade farkı veya kısmi iade kaynaklı çıkar.

Bunu Excel'de elle yapmayın. Gecelik bir işe bağlayın, fark sıfır değilse alarm üretsin. Yanına iki iş metriği alarmı koyun: "provizyon alınmış ama 30 dakikadır tahsil edilmemiş" ve "ödemesi başarılı ama siparişi oluşmamış". Bu iki sorgu, hata oranı grafiklerinden çok daha erken haber verir, çünkü sistem teknik olarak hatasız çalışırken de bu iki durum oluşabilir.

İtiraz geldiğinde kanıt elinizde olsun

Chargeback bir yazılım sorunu değil, ama yazılımın hazırlıklı olması gereken bir süreç. Kart hamilinin itiraz açma penceresi ihtilaf türüne göre değişmekle birlikte çoğu durumda 120 güne kadar uzayabiliyor. İşyerinin cevap süresi ise haftalarla ölçülüyor: Visa'da tipik olarak 30, Mastercard'da 45 gün, ve süreyi kaçırırsanız itiraz otomatik olarak aleyhinize sonuçlanıyor. Sayacı kendi hesabınıza göre değil, bankanızdan veya sağlayıcınızdan gelen bildirimdeki tarihe göre işletin.

Yazılım tarafında yapılacak iş, itirazı savunacak kanıtı işlem anında toplamak: 3D Secure doğrulama sonucu ve referansı, sipariş ve teslimat zaman damgaları, IP ve cihaz bilgisi, kargo takip kaydı, müşteriyle yazışma. Altı ay sonra bunları log'lardan toplamaya çalışmak genelde başarısız olur. İşlem anında tek bir kayda yazın.

Nereden başlanır

Zaten canlı bir entegrasyonunuz varsa bir günlük kontrol yeterli. Dört soruyu sorun: son 30 günde sağlayıcıda başarılı görünüp sizde karşılığı olmayan işlem var mı, aynı olay kimliği iki kez işlenmiş mi, webhook uç noktanız imzayı gerçekten doğruluyor mu, ve sipariş kaydı tarayıcının döndüğü sayfada mı oluşuyor. Dördünden biri bile çıkarsa aramanız gereken şey yeni bir ödeme sağlayıcısı değil, bu listenin üstündeki maddeler. Dördünü de bir öğleden sonrada kontrol edebilirsiniz; düzeltmesi ise çoğunlukla birkaç günlük iştir.


Bu konuda yardıma mı ihtiyacınız var?

iletişime geçtüm yazılar