Akışlar
İstek Yaşam Döngüsü
Bir istek nerede başlar, hangi durakları geçer, nerede biter? Aşağıda sistemin ana akışlarını baştan sona, adım adım anlatıyoruz. Teknik isimler parantezde duruyor ama anlatım günlük Türkçe — her adımı bilmeniz gerekmez, merak ederseniz derinleşebilirsiniz.
Tekrarlayan desen: Kullanıcının konuştuğu API (FinevoConnect YÖS), bir yazma banka/kurum işi gerektiğinde (rıza kurma / ödeme gönderme) bunu doğrudan yapmaz. Bunun yerine "şunu yap" diye bir niyet (SyncIntent) olarak veritabanına yazar; arka plandaki batch işçi bu niyeti alıp bankayla/kurumla konuşur ve sonucu veritabanına yazar. Böylece API kullanıcıya hızlı yanıt verir, asıl iş güvenle arka planda yürür. İstisna: kullanıcı ekranda canlı okuma(fresh=true) istediğinde, niyet kutusu atlanır; API batch'i senkron çağırıp taze veriyi anında döner (bkz. bölüm 4). Okuma başarısız olursa kullanıcı yine dener, o yüzden dayanıklılık (outbox) gerekmez.
1) Genel istek boru hattı (middleware)
Her HTTP isteği, asıl işi yapan controller'a ulaşmadan önce sırayla şu duraklardan geçer. Her durak (middleware) tek bir işten sorumludur ve isteği bir sonrakine devreder:
Correlation ID üret
CorrelationIdMiddleware
Gelen her isteğe, o isteğe özel benzersiz bir kimlik (correlation_id, yani 'takip numarası') verilir. Bu numara isteğin bıraktığı tüm log satırlarında görünür. Bir istekte sorun olduğunda, bu numarayı arayarak isteğin geçtiği her durağı baştan sona takip edebilirsiniz. Kargodaki takip numarası gibi düşünün.
Hata kalkanı
ExceptionHandlingMiddleware
Bu noktadan sonrası bir güvenlik ağıyla sarılır. İlerideki herhangi bir adımda beklenmedik bir hata çıkarsa, hata dışarı taşmadan burada yakalanır. Yakalanan hata, standart bir hata yanıtına (RFC 7807 ProblemDetails formatı — hata kodu, başlık, açıklama ve takip numarası içeren düzenli bir yapı) çevrilir. Böylece kullanıcı çirkin bir çökme yerine anlaşılır bir mesaj alır.
İstek/yanıt logla
RequestLoggingMiddleware
İsteğin yöntemi (GET/POST), yolu, sonuç durumu (200/404 gibi) ve ne kadar sürdüğü kaydedilir. İstek ve yanıt gövdeleri de loglanır, ancak şifre ve token gibi hassas alanlar önce redakte edilir (yıldızlanır) — gizli veri loga düşmez. Loglar düz metin değil yapılandırılmış (JSON) tutulur, böylece sonradan aranıp filtrelenebilir.
Dil çöz
LocaleResolutionMiddleware
İsteğin Accept-Language başlığına bakılarak kullanıcının dili (tr veya en) belirlenir. Başlık yoksa kullanıcının kayıtlı tercih dili, o da yoksa varsayılan Türkçe kullanılır. Bu sayede bir hata olduğunda mesaj kullanıcının anladığı dilde döner.
Kimlik doğrula (JWT)
Authentication
İstekte bir JWT token (giriş yapınca verilen, kim olduğunuzu kanıtlayan imzalı erişim token'ı) varsa geçerliliği kontrol edilir: imzası doğru mu, süresi dolmuş mu, bu API için mi üretilmiş. Korumalı uçlar (örneğin hesap listesi) geçerli bir token olmadan cevap vermez. Sadece giriş/kayıt gibi açık uçlar token istemez.
Yetki kontrolü
Authorization
Token içinden kullanıcı kimliği (userId) okunur. Bu kimlik olmadan korumalı bir uca ulaşılamaz. Ayrıca her kullanıcı yalnızca kendi verisine erişebilir — başka bir kullanıcının hesabını istemek 404 (bulunamadı) döner, 'bu sizin değil' demek yerine kaydın varlığını bile ele vermeyiz (BOLA koruması).
Girdi doğrula
ValidationFilter
İsteğin gövdesi iş kurallarına göre denetlenir (FluentValidation kütüphanesiyle): IBAN geçerli mi, tutar sıfırdan büyük mü, zorunlu alanlar dolu mu gibi. Bir şey eksik veya hatalıysa istek daha işlem görmeden 400 (hatalı istek) ile geri çevrilir. Yanıtta hangi alanın neden hatalı olduğu tek tek belirtilir.
Tekrar koruması
IdempotencyFilter
Yazma isteklerinde (POST/PUT/DELETE) Idempotency-Key başlığı zorunludur. Bu başlık, aynı isteğin yanlışlıkla iki kez gönderilmesini (örneğin kullanıcı 'Öde' düğmesine iki kez basarsa) yakalar. İkinci çağrı işi tekrar yapmaz; ilk çağrının kaydedilmiş yanıtını aynen döndürür. Böylece bir ödeme iki kez gerçekleşmez.
Controller çalışır
İş mantığı
Tüm bu kontrollerden geçen istek nihayet ilgili servise ulaşır ve asıl iş burada yapılır (hesap okunur, ödeme oluşturulur vb.). İş bitince yanıt hazırlanır ve istek, geldiği duraklardan ters sırada geçerek geri döner (loglama, hata kalkanı yanıtı sarar). Kullanıcıya nihai HTTP yanıtı ulaşır.
2) Banka/Kurum bağlama / rıza oluşturma
En temel akış: kullanıcıyı bir bankaya/kuruma bağlamak, yani onun izniyle banka/kurum verisine erişim kurmak. İki dünya — hızlı yanıt veren API ile bankayla/kurumla konuşan batch — burada birlikte çalışır:
Kullanıcı bağlantı başlatır
POST /hhs-connections
Kullanıcı hangi bankaya/kuruma ve hangi izinlerle (hesap bilgisi / bakiye / işlem geçmişi / kart) bağlanmak istediğini gönderir. Ayrıca onay yönteminin yönlendirmeli mi (redirect — kullanıcı bankanın/kurumun sayfasına gidip döner) yoksa ayrık mı (decoupled — kullanıcı banka/kurum mobil uygulamasından SMS/bildirimle onaylar) olacağı seçilir. Bu istek henüz sadece bir talep; banka/kurum bu anda haberdar değildir.
Rıza yazılır, batch senkron çağrılır
HhsConnectionService.CreateAsync
Bağlantı kaydı ve seçilen izinler veritabanına yazılır; hemen ardından API, batch'in iç ucunu SENKRON çağırır (iç RPC). Kullanıcının konuştuğu API (FinevoConnect YÖS) bankaya/kuruma doğrudan hiç bağlanmaz — banka/kurum çağrısını batch yapar. Rızaya ömür boyu sabit kalacak bir X-Group-ID (rıza grup numarası) atanır.
Batch iç ucu isteği alır
POST /internal/consent-create (iç RPC)
Bu uç dış dünyaya kapalıdır; yalnız iç ağdan ve gizli bir başlıkla (X-Internal-Secret) erişilir. Kullanıcı yanıtı beklerken çalıştığı için burada niyet kutusu/kuyruk KULLANILMAZ — istek anlık gidip anlık döner. (Niyet kutusu, kullanıcının beklemediği işlerde — ödeme gönderimi, periyodik yenileme — devrededir.)
Bankaya/Kuruma rıza çağrısı
batch → HHS
Batch bankaya/kuruma (HHS — bankanın/kurumun veri paylaşım sistemi) güvenli, imzalı bir istek atar: mTLS (iki tarafın da sertifikayla kimliğini kanıtladığı şifreli kanal) + JWS (isteğin dijital imzası). Banka/Kurum bir rıza numarası (rizaNo) ve kullanıcının onaylayacağı bir onay adresi (authorizationUrl) döndürür. Banka/Kurum isteği reddederse bağlantı 'başarısız' işaretlenir ve akış burada durur.
Onay adresi kullanıcıya döner
RPC yanıtı
Yönlendirmeli akışta banka/kurumdan gelen onay adresi, RPC yanıtıyla API'ye ve oradan AYNI HTTP yanıtında kullanıcıya döner — araya bekleme girmez. Kullanıcı bu adrese giderek bankanın/kurumun onay ekranını görür. Ayrık akışta ise adres yerine kullanıcı doğrudan banka/kurum uygulamasından işlemi onaylar.
Kullanıcı GKD ekranında onaylar → callback
GET /hhs-connections/callback (redirect) · /{id}/callback (poll)
Kullanıcı bankanın/kurumun GKD ekranında (Güçlü Kimlik Doğrulama — bankanın/kurumun kullanıcıyı şifre/SMS/biyometri ile onaylattığı ekran) kimliğini doğrular ve izni verir. İki tamamlama yolu vardır: (1) Yönlendirme modunda banka/kurum, API'ye bir geri dönüş (callback) yapar — GET /hhs-connections/callback — ve içinde onay durumu + yetki kodu (yetKod) gelir. Callback işlendikten sonra kullanıcının tarayıcısı, bağlantı oluşturulurken istekle verilen consumerSuccessUrl/consumerErrorUrl adresine (verilmemişse ortam varsayılanına) 302 ile gönderilir. (2) Ayrık modda banka/kurum geri yönlendirmez; uygulama GET /hhs-connections/{id}/callback ucunu çağırarak bağlantının tamamlanıp tamamlanmadığını yoklar (yetKod bankadan/kurumdan sorulur). Her iki yol da aynı aktivasyon adımına bağlanır.
Aktivasyon senkron çağrılır
HandleCallbackAsync → POST /internal/authorization-complete
API, callback'teki yetki kodu (yetKod) ile batch'in iç ucunu SENKRON çağırır: 'onay geldi, şimdi token'a çevir'. API burada da bankayla/kurumla konuşmaz. Batch'e o an ulaşılamazsa aynı iş bir 'AuthorizationComplete' niyeti olarak niyet kutusuna YEDEKLENİR ve arka planda tamamlanır — onay asla kaybolmaz.
Token alınır, bağlantı 'bağlı' olur
ConsentActivationService
İşçi, elindeki yetki kodunu bankada/kurumda gerçek bir erişim token'ına (token — bankadan/kurumdan alınan, kullanıcı adına veri çekme yetkisi veren dijital anahtar) çevirir. Token şifrelenerek saklanır (düz metin olarak asla tutulmaz). Bağlantının durumu artık 'Connected' (bağlı) olur — yani kullanıcı adına bankadan/kurumdan veri çekmeye hazırdır.
İlk veri ANINDA çekilir
AuthorizationComplete → RunInitialFetchAsync (inline)
Bağlantı 'bağlı' olduğu ANDA, daha aktivasyon işlemi bitmeden, ilk veri çekimi aynı işlem içinde SENKRON (inline) yapılır: kullanıcının hesapları, bakiyeleri, son işlemleri ve kartları bankadan/kurumdan alınıp veritabanına yazılır. Böylece kullanıcı onay ekranından döndüğünde hesap/kart verisi zaten hazırdır — kısa bir 'boş liste' gecikmesi yaşanmaz. Eğer bu inline çekim bir hata alırsa (ör. banka/kurum geçici hatası / kota), güvenli bir yedek olarak eski asenkron 'InitialFetch' niyeti kutuya bırakılır ve işçi veriyi biraz sonra çeker. Sonraki güncellemeler periyodik yenileme veya canlı okuma (fresh=true) ile gelir.
Sonuç: Bağlantı "Connected" (bağlı) olur ve hesap/kart/işlem verisi hazırdır. Kullanıcı GET /hhs-connections/{id} ile bağlantı durumunu, GET /accountsile de hesaplarını ve bakiyelerini görebilir.
Callback sonrası kullanıcı nereye döner? Dört oluşturma akışının hepsi (bağlantı + üç ödeme ailesi) istekte opsiyonel consumerSuccessUrl / consumerErrorUrl alanlarını kabul eder. Adresler oluşturma anında doğrulanır (mutlak URL, en fazla 2048 karakter, https — http yalnız localhost; host, çağıran uygulamanın izinli host listesinde birebir bulunmalı) ve ilgili kayda yazılır. Banka/kurum callback'i geldiğinde API, kayıttaki adresleri state koduyla, callback'i tüketmeden önce salt-okunur çözer ve kullanıcıyı aynen o adrese (302) gönderir — istekle gelen adres olduğu gibi kullanılır. Alanlar verilmemişse (veya state bulunamazsa) ortam ayarındaki varsayılan adreslere düşülür; ödeme ailelerinde {kind} şablonu yalnız bu ortam varsayılanlarında uygulanır. Hata durumunda sıralama: kayıttaki hata adresi → ortam hata adresi → başarı adresi.
3) Ödeme emri
Ödeme akışı, rıza akışına çok benzer; ama önemli bir farkı vardır: her ödeme kendi başına bir rızadır. Yani para göndermek için önceden kurulmuş bir banka/kurum bağlantısına ihtiyaç yoktur, her ödeme kendi onayını alır:
Ödeme oluşturulur
POST /payment-orders
Kullanıcı alıcı IBAN'ı, tutarı ve açıklamayı gönderir. Önemli fark: ödeme için önceden kurulmuş bir banka/kurum bağlantısı GEREKMEZ — her ödeme kendi başına ayrı bir rızadır (para göndermek için ayrı onay alınır). Yanlışlıkla çift ödemeyi önlemek için Idempotency-Key başlığı zorunludur.
Ödeme + rıza + niyet yazılır
PaymentOrderService.CreateAsync
Ödeme için yeni bir 'ödeme tipi' rıza (bağlantı) kaydı, ödeme emrinin kendisi ve bankaya/kuruma gönderim niyeti tek bir veritabanı işleminde birlikte yazılır. Bu işlem serileştirilebilir (serializable) seviyede yapılır — yani aynı anda gelen iki isteğin birbirine karışması engellenir. Yine bankaya/kuruma bu anda çağrı yapılmaz.
Bankada/Kurumda ödeme rızası
PaymentOrderSubmitHandler → HHS
İşçi bankaya/kuruma ödeme rızası (ödeme-emri-rizasi) oluşturur. Banka/Kurum, tıpkı hesap bağlamada olduğu gibi bir onay adresi döndürür. Kullanıcı bu adreste ödemeyi onaylayacaktır.
Kullanıcı onaylar → callback
GET /payment-orders/callback
Kullanıcı ödemeyi banka/kurum ekranında (GKD) onayladığında banka/kurum bir callback yapar. Bu geri dönüş, ödemeyi tamamlamak için bir aktivasyon niyeti oluşturur — hesap bağlamadaki 'AuthorizationComplete' adımının ödeme karşılığıdır. İş yine batch'e devredilir.
Ödeme talimatı gönderilir
PaymentInstructionCompletionFlow
İşçi token'ı alır ve asıl ödeme emrini bankaya/kuruma iletir. Banka/Kurum bir ödeme referansı, güncel durum ve hangi ödeme sistemiyle (odmStm → H=Havale, F=FAST, E=EFT) yürütüleceğini döndürür. Ödeme sistemini banka/kurum kendisi seçer; YÖS (bizim taraf) bunu dayatmaz.
Durum takip edilir
GET /payment-orders/{id}?fresh=true
Kullanıcı ödemenin akıbetini merak edip sorgular. ?fresh=true eklerse (bkz. bölüm 4) sistem bankadan/kurumdan o anki canlı durumu çeker — gönderildi / başarılı / reddedildi gibi. fresh olmadan sorarsa en son kaydedilmiş durum döner, ki bu da genelde yeterlidir.
4) Canlı veri çekme (?fresh=true) — kullanıcı-present senkron yol
Gerçek kullanıcı ekranın başındayken "şu anki" veriyi istediğinde (PSU=E), sistem niyet kutusunu kullanmaz. API, batch'in iç ucunu doğrudan senkron çağırır; batch anında bankaya/kuruma sorar ve taze veri aynı yanıtta geri döner. Okuma işlemi kalıcılık/dayanıklılık gerektirmediği için niyet kutusu burada gereksiz bir yük olurdu; yazma işleri (rıza/ödeme) ise güvence için niyet kutusunda kalır:
Canlı istek gelir
GET .../{id}?fresh=true
Gerçek kullanıcı, ekranın başındayken en güncel veriyi ister ve isteğe ?fresh=true ekler. Bu bir 'kullanıcı-present' sorgudur, yani kod tarafında PSU=E olarak işaretlenir (PSU = ödeme hizmeti kullanıcısı; E = kullanıcı şu an burada). Örneğin kullanıcı ekranda 'bakiyeyi tazele' düğmesine basarsa bu yol devreye girer.
Tazelik penceresi kontrolü
LiveRefreshCoordinator
Önce verinin ne kadar taze olduğuna bakılır. Eğer veri çok yeni ise (kısa bir 'tazelik penceresi' içinde alınmışsa) doğrudan yerel veritabanından döner — bankayı/kurumu gereksiz yere yormamak için yeni çağrı yapılmaz. Aynı anda aynı kaynağa gelen birden fazla tazeleme isteği tek bir çağrıda birleştirilir (tekrarları emer), böylece banka/kurum üst üste sorulmaz.
Batch'e SENKRON RPC
ILiveQueryClient → POST /internal/live-query
Veri eskiyse API, batch'in iç ucunu SENKRON çağırır (RPC — bir programın diğerini doğrudan çağırıp yanıtını beklemesi). Bu iç uç dış dünyaya kapalıdır, yalnız iç ağdan ve gizli bir başlıkla (X-Internal-Secret) erişilir. Burada niyet kutusu veya kuyruk KULLANILMAZ; okuma dayanıklılık gerektirmediği için anlık gidip anlık dönülür.
Batch anlık BKM sorgusu
LiveQueryService → ConnectionSyncService
Batch, bankaya/kuruma PSU=E ile hemen sorar (hesap / bakiye / işlem / kart — hangisi istendiyse). Kullanıcı-present olduğu için bu sorguya istek-adedi KOTASI uygulanmaz; kullanıcı beklerken 'saatlik sınıra takıldın' denmez. Batch, mevcut yenileme mantığını yeniden kullanır — özel bir kod yolu yoktur. Gelen sonuç ayrıca veritabanına yazılır, böylece önbellek de sıcak kalır.
Taze veri döner (Live)
DetachAll + yeniden oku
Batch işini bitirip dönünce API, veritabanından yeni yazılan taze veriyi okur ve yanıtı Freshness=Live (canlı) etiketiyle döndürür — hepsi aynı HTTP isteği içinde. Eğer batch'e ulaşılamaz veya banka/kurum hata verirse istek çökmez; API elindeki son önbellek verisini Freshness=Stale (bayat) etiketiyle döner ve arka planda yenilemeyi tetikler. Kullanıcı hiçbir zaman hata ekranı görmez, en kötü ihtimalle biraz eski veri görür.
Neden E için kota yok? Kota, bankanın/kurumun "bana saatte şu kadar soru sorabilirsin" sınırıdır ve bankayı/kurumu otonom botların yormasından korur. BKM bu istek-adedi limitini yalnız otonom/periyodik (H) sorgulara uygular; kullanıcı-present (E) ve olay-tetikli (O) sorgular sınırsızdır. Bu yüzden gerçek bir kullanıcı ekranda tazeleme istediğinde bankaya/kuruma serbestçe gidilebilir. Güvenlik yine korunur: API sertifika taşımaz, bankaya/kuruma kendisi bağlanmaz — sadece batch'i çağırır.
5) psuInitiated — banka/kurum çağrısı kimin adına?
Bankaya/Kuruma giden her sorgu, o sorgunun kaynağını belirten bir psuInitiated kodu taşır. Yani banka/kurum, isteğin ardında canlı bir kullanıcı mı yoksa arka plan botu mu olduğunu bu koddan anlar. Kod, bankanın/kurumun oran-sınırı (kota) uygulayıp uygulamayacağını belirler:
| Kod | Ne zaman | Oran sınırı |
|---|
| E | Kullanıcı ekranın başında, veriyi kendisi tetikledi (fresh=true) | Yok — serbest gidilir |
| H | Batch, kullanıcı yokken otomatik/periyodik arka plan yenilemesi yapıyor | Var — kotalı (bankayı/kurumu korur) |
| O | Banka/Kurum bir olay bildirdi (KaynakGuncellendi), sistem ona tepki veriyor | Yok — serbest gidilir |
Kota sayacı (HhsQueryQuota) yalnız H sorgularını sayar; E (kullanıcı) ve O (banka/kurum olayı) sorguları muaftır — çünkü ikisinde de gecikme doğrudan bir kullanıcıyı bekletir.
6) Niyet kutusu (SyncIntent Outbox) neden güvenli?
Tek işlemde yazılır
Asıl kayıt değişikliği (rıza/ödeme) ile niyet, aynı veritabanı işleminde birlikte yazılır. Ya ikisi de olur ya hiçbiri — "kayıt oldu ama niyet yazılmadı" gibi yarım bir durum asla oluşmaz.
En az bir kez işlenir
İşçi tam iş sırasında çökse bile niyet kutuda durur ve tekrar denenir — hiçbir iş sessizce kaybolmaz. "OutboxConsumed" işaretiyle de aynı niyetin iki kez işlenmesi (çift ödeme gibi) engellenir.
⏳
Akıllı yeniden deneme
Banka/Kurum geçici olarak yanıt vermezse, giderek artan aralıklarla tekrar denenir (üst üste yüklenmez). Kota dolmuşsa iş ertelenir. Düzelmeyen kalıcı bir hataysa "dead" (ölü) işaretlenir ve bir insan bakana kadar bekletilir.
7) İzlenebilirlik başlıkları
Bir isteği ve ilgili banka/kurum çağrılarını sonradan izleyebilmek için üç ayrı kimlik kullanılır. Her biri farklı bir kapsamı işaretler:
| Başlık | Ne işe yarar | Kapsam |
|---|
correlation_id | Tek bir HTTP isteğinin tüm log satırlarını birbirine bağlar. Her istekte yeniden üretilir; sorun ararken bu numarayı takip edersiniz. | İstek başına |
X-Group-ID | Bir rızaya ait tüm banka/kurum çağrılarını tek grupta toplar; bankalar/kurumlar bu numaraya bakar. Rıza oluşturulurken üretilir ve o rıza yaşadığı sürece hiç değişmez (connection.GroupId). | Rıza ömrü boyunca |
X-Request-ID | Bankaya/Kuruma giden her tekil işlem için ayrıdır. Aynı işlem tekrar denenirse aynı kalır (banka/kurum çift işlemez), farklı işlemde farklı olur. | İşlem başına |