Yönlendirme, “bu ödeme hangi POS’tan geçsin?” sorusunun cevabıdır. Poyra’nın cevabı iki iddiada toplanır: karar deterministiktir (aynı ödeme, aynı veriyle her zaman aynı kapıya gider) ve açıklanabilirdir (neden o kapıya gittiği ödemenin üzerine yazılır).

Karar sırası

  1. Aktif hesaplar öncelik sırasıyla toplanır. Hiç yoksa karar boştur ve ödeme routing.no_route ile reddedilir.
  2. Sağlık filtresi: canary yoklamasının Down işaretlediği hesaplar elenir; skipUnhealthy korumasıyla yalnızca Healthy olanlara da daraltabilirsiniz. Kaç hesabın atlandığı karar gerekçesine yazılır.
  3. Kural eşleşmesi: aktif kural dokümanındaki kurallar sırayla denenir, ilk eşleşen kazanır. Koşulsuz kural her zaman eşleşir.
  4. Kural eşleşmediyse hacim bölüşümü: ödeme kimliğinin SHA-256’sından 0–99 arası bir kova türetilir ve kümülatif yüzdeye göre hesap seçilir. Rastgele sayı yoktur — aynı ödeme her değerlendirmede aynı kovaya düşer, karar sonradan yeniden üretilebilir.
  5. O da yoksa doküman stratejisi devreye girer (varsayılan: öncelik sırası).
  6. Sırayı bir strateji kurduysa son söz ölçüm kotasınındır: trafiğin küçük bir kısmı, sinyali olmayan bir adayı başa alır. Kural sabit rota verdiyse ya da hacim bölüşümü seçtiyse kota devreye girmez.

Kural dili

Kural dokümanı JSON’dur ve sürümlenir:
Hesaplar etiketle ya da kimlikle referanslanır; bilinmeyen referans sessizce atlanır. Koşullarda kullanılabilecek olgular:
Kart sinyalleri yalnızca biliniyorsa değerlendirilir. Hosted akışta müşteri BIN girmediyse kart kuralları eşleşmez ve değerlendirme sıradaki kurala ya da stratejiye geçer — “bilinmeyen kart” hiçbir kuralı yanlışlıkla tetiklemez. Kart ülkesi de aynı duruştadır: BIN katalogda yoksa ülke bilinmez ve ülke kuralları eşleşmez. Bilinmeyeni “yurt dışı” saymak, katalogda eksik kalan bir TR kartını taksitsiz bir rotaya yollar, vade farkını sessizce kaybettirirdi.

Kanal: ödeme nereden girdi

Aynı tutar, saha temsilcisinin elindeyken başka, gece koşan bir abonelik yenilemesindeyken başka davranır. channel bu ayrımı rota kararına taşır: Kanal istek gövdesinden alınmaz; çağıran uç noktanın kendisi bildirir. İşyerinin beyanına bırakılsaydı rota kuralı doğrulanamayan bir alana dayanırdı. Bu ayrım işyerinin kendi sitesi ile mobil uygulaması ayrımı değildir — ikisi de aynı API’den girer ve Poyra’ya api görünür; kanal, yalnızca Poyra’nın kendi bilip doğrulayabildiği şeydir. Kanal alanı eklenmeden önce yazılmış ödemelerde kanal boştur ve kanal kuralları o işlemlerde eşleşmez. Eskilere “api sayalım” demek geçmişe uydurma kanal atfetmek olurdu; simülatör onları kanal kuralına yanlışlıkla sokar ve tasarruf tahminini bozardı. Kanal ödemenin üzerinde durur: panelin ödeme detayında görünür, GET /v1/payments/{id}/timeline yanıtında channel alanıyla döner.

Stratejiler

Kural sabit bir rota vermek yerine sıralamayı bir stratejiye bırakabilir: Performans sinyalleri son 7 günden hesaplanır ve en az 20 örnek yoksa güvenilmez sayılır. Sinyali olmayan hesap elenmez, sıralamada sona alınır — yeni açılan bir POS “veri yok” diye dışlanmaz ama öne de geçmez. Eşitlikte giriş sırası korunur; karar her koşulda deterministiktir.

Ölçüm kotası: kazanan her şeyi almasın

Ölçülen sinyale dayanan stratejilerin (best_success, fastest, balanced) sessiz bir arızası vardır: kazanan POS tüm trafiği alır, kaybeden hiç örnek toplayamaz. Yedi günlük pencere boşalınca kaybedenin sinyali yok olur, sinyalsiz aday da sona alınır — banka toparlasa bile geri dönemez. Karar bir kez verilmiş ve kilitlenmiştir. guards.explorePercent bu kilidi açar: trafiğin bu kadarlık kısmı (varsayılan %10, tavan %50, 0 = kapalı) ölçümü olmayan bir adayı sıranın başına alır. Kova yine ödeme kimliğinden deterministik türetilir, ama hacim bölüşümünden ayrı tuzlanır — yoksa “kotaya düşen işlem” ile “bölüşüm kovası” hep aynı ödemeler olurdu. Kotanın bedeli bilinçli olarak sınırlıdır:
  • Kota yalnız sırayı stratejinin kurduğu durumda çalışır. Kural sabit rota verdiyse ya da hacim bölüşümü seçtiyse dokunmaz; ikisi de işyerinin açık talimatıdır ve kota onları ezmemelidir.
  • Havuz yalnızca ölçülmeye muhtaç adaylardan kurulur: hiç ölçülmemiş ya da penceresi boşalmış hesaplar. Böyle bir aday yoksa kota harcanmaz.
  • Kotaya düşen denemede kazanan POS hemen arkada durur: ölçüm başarısız olursa yük devretme onu yakalar. Ölçüm bedava değildir ama tahsilatı riske atmaz.
  • maxAttempts 1 ise kota kendiliğinden kapanır — arkasında kurtarma olmayan bir ölçüm, tahsilatı kumara çevirirdi.
Kota kararın gerekçesinde açıkça yazar — hangi kovaya düştüğü dahil:
Ölçüm kotası: sinyali olmayan Akbank POS öne alındı (kova %4 < %10; sıra stratejisi: en yüksek başarı oranı).

Hacim taahhüdü: bankaya verilen söz

Türkiye’de indirimli komisyon oranı çoğu zaman bir söz karşılığında alınır: “bu bankaya ayda şu kadar ciro geçireceğim”. Taahhüt tutmazsa banka indirimli oranı geri çeker — ve bu genelde ay bittikten sonra, ekstre gelince öğrenilir. Poyra taahhüdü gün gün takip eder ve rota kararına bağlar. Taahhüt POS hesabı başına tanımlanır:
GET /v1/routing/commitments her taahhüdü gerçekleşen hacmiyle birlikte döner: achievedMinor, kapatılması gereken açık gapMinor ve dönemin bitimine kalan daysLeft. Dönem takvim ayıdır ve Türkiye saatine (UTC+3) göre hesaplanır; hacim yalnızca tahsil edilmiş işlemlerden sayılır — banka da taahhüdü geçen ciroyla ölçer, denenen tutarla değil. Sayılan tutar karttan çekilen tutardır (vade farkı dahil), çünkü ekstrede görünen rakam odur. İadeler düşülmez: banka taahhüt sayımında iadeyi genelde ayrı işler; düşüldüğünü varsaymak taahhüdü olduğundan az gösterir ve rota gereğinden fazla hacmi o POS’a yığardı. commitment stratejisi adayları aciliyete göre sıralar: kalan açık ÷ kalan gün. Üç günde 60.000 ₺ kapatmak, on sekiz günde 120.000 ₺ kapatmaktan aciledir. Taahhüdü olmayan ya da taahhüdünü tutmuş hesabın aciliyeti sıfırdır: açığı olanların arkasına düşer ama elenmez — açık kapanınca hesap kendiliğinden öncelik sırasına döner, bunun için ayrı bir kural yazmak gerekmez. DELETE /v1/routing/commitments/{id} taahhüdü pasife çeker, silmez: geçmiş rota kararlarının gerekçesi o kayda dayanır.
Taahhüt, komisyon anlaşmasından ayrı bir kayıttır. Anlaşma mutabakatta durur çünkü denetimi yapan odur; taahhüdü ise yalnız rota okur.

Yük devretme zinciri

Nihai zincir üç parçadan kurulur: seçilen rota + dokümandaki fallback listesi + kalan tüm uygun adaylar. guards.maxAttempts (varsayılan 2) kaç adayın fiilen deneneceğini sınırlar. Devretmenin ne zaman mümkün olduğu akışa bağlıdır:
  • Hosted akışta yalnızca başlatma aşamasında. Bankaya form üretilirken hesap erişilemezse sıradaki adaya geçilir. Müşteri bankanın sayfasına gittikten sonra artık dönüş beklenir; geriye sarma yoktur.
  • Direct akışta banka cevabına göre. Yalnızca yeniden denenebilir kodlarda (connector_unavailable, issuer_unavailable, processing_error) sıradaki POS denenir. Kart reddi devretmez: limit yetersizliği kartın kararıdır, başka POS’tan denemek sonucu değiştirmez ve kart deneme desenine benzer.
  • Taksidi desteklemeyen ya da o taksit için şeması olmayan hesap deneme bile açılmadan atlanır.
Her devretme adımı ayrı bir attempt ve failover: true işaretli bir attempt.failed olayı bırakır — zincir zaman çizelgesinde eksiksiz görünür.

Açıklanabilirlik: “neden bu bankaya gitti?”

Karar iki katmanda saklanır. İnsan için gerekçe cümlesi:
Kural eşleşti: yüksek tutar → İş POS — strateji: en düşük komisyon
Hacim bölüşümü: kova %37 → İş POS
Strateji: en yüksek başarı oranı — İş POS başarı oranı %97,2
Makine için de kararın tamamı ödemenin üzerine JSON olarak yazılır: eşleşen kural ve sürümü, strateji, değerlendirilen adaylar ve her adayın o anki sinyalleri (beklenen komisyon, başarı oranı, ortanca gecikme). Kaydın içinde karar anında bilinen kart da durur (card düğümü; null = kart o an bilinmiyordu). Simülatör geçmişi bu düğümle oynatır: hosted akışta kart, karar verildikten sonra bankadan öğrenilir — maskeli karttan geriye doğru türetilen bir kart, gerçekte hiç eşleşmemiş kuralları eşleşmiş gösterirdi. BIN bu düğüme ilk altı haneye kırpılarak yazılır. GET /v1/payments/{id}/timeline bu kaydı olay zinciriyle birlikte döner; panelin ödeme detayı da aynı kaydı gösterir. Aylar sonra “bu işlem neden Garanti’den geçti?” sorusunun cevabı, o günkü verilerle birlikte ödemenin üzerindedir.

Kural sürümleme ve simülasyon

Kurallar silinmez, sürümlenir:
  • POST /v1/routing/rules yeni sürümü pasif oluşturur.
  • POST /v1/routing/rules/{id}/activate anında etkinleştirir; geri almak, eski sürümü yeniden etkinleştirmektir.
  • GET /v1/routing/rules/active ve GET /v1/routing/rules okuma uçlarıdır.
Canlıya almadan önce POST /v1/routing/simulate, aday dokümanı geçmiş işlemler üzerinde koşturur ve mevcut kuralla farkını gösterir — hangi işlemler başka kapıya giderdi, maliyet nasıl değişirdi. Panelde bu işin görsel karşılığı Yönlendirme tasarımcısı ekranıdır.