Harcama Kısıtı (Spend Policy)
- Taban yol
/api/panel/v1/wallet- Kimlik
- Panel oturumu (JWT) —
Authorization: Bearer <panel access token>. Token panel girişinden gelir; s2s imza anahtarı bu uçları AÇMAZ. - Yetki
- RBAC izin kodu:
wallet:limit:manage·wallet:enduser:manage - Çapraz yüzey
- Bu sayfadaki uçların s2s API'de karşılığı YOKTUR; kendi panelinizden çağıramazsınız. Bu yetenekler yalnız merchant panelde, panel oturumuyla kullanılabilir.
Harcama kısıtı, bir cüzdandan para çıkışını kural kümesiyle sınırlar: hangi kanalda, hangi kategoride, hangi işyerinde ve hangi saatlerde harcanabilir.
En yaygın kullanımı kurumsal cüzdandır — yemek kartının yalnız restoranlarda, mesai saatlerinde ve belirli işyerlerinde geçmesi gibi. Ancak motor kuruma özel değildir: program kapsamında da tanımlanabilir.
Harcama kısıtı yalnız panelden yönetilir; imzalı s2s yüzeyinde kural yazma/okuma ucu yoktur. Kurallar operasyon anında motor tarafından otomatik değerlendirilir.
Uçlar
GET spend-policy-categories # kategori kataloğu
GET spend-policy-operations # kısıtlanabilir operasyonlar + merchantBearing
GET mcc-categories # gerçek MCC → kategori eşlemesi (salt-okuma)
GET programs/:id/spend-policies # kuralları listele
PUT programs/:id/spend-policies # kuralları yaz (upsert)
PUT end-users/:id/merchant-category # bir işyerine kategori ataKural modeli
Bir kural iki şey söyler: hangi kapsamda geçerli ve ne yapıyor.
| Alan | Açıklama |
|---|---|
mode | ALLOW_LIST (yalnız bunlara izin ver) veya DENY_LIST (bunları engelle) |
corporateAccountId | Doluysa kapsam kurum, boşsa kapsam program |
operations[] | Operasyon/kanal ekseni |
categories[] | Kategori ekseni |
merchantEndUserIds[] | İşyeri ekseni |
timeWindows[] + timeZone | Zaman penceresi ekseni |
status | ACTIVE | INACTIVE |
description | Denetim için açıklama (en çok 240) |
mode değerleri ALLOW_LIST ve DENY_LIST'tir — ALLOW / DENY değil. Ayrı bir "etki" alanı yoktur; etkiyi liste modu taşır.
Bir kural en az bir ekseni kısıtlamak zorundadır. Dört eksenin de boş bırakıldığı bir kural veritabanı düzeyinde reddedilir — hiçbir şeyi kısıtlamayan bir kural ya anlamsızdır ya da (ALLOW modunda) her şeyi serbest bırakır.
Dört eksen
1. Operasyon / kanal
Kısıtlanabilir operasyonlar (GET spend-policy-operations ile okunur — listeyi kendi tarafınıza kopyalamayın):
W2W_TRANSFER · W2IBAN_WITHDRAW · WITHDRAW_BANK · PAYMENT_BUY · BILL_PAY · CASHOUT_ATM · CASHOUT_QR · CRYPTO_SWAP · CARD_AUTH · BULK_PAYOUT_ITEM_IBAN
Kart otorizasyonu (CARD_AUTH) da bu motora bağlıdır. Kart tarafında bir kısıt reddi istisna değil, terminal bir DECLINED kaydı üretir: ihraççı webhook'u bir hatayı geçici arıza sanıp yeniden denerdi.
Cüzdan içi hedefe giden toplu ödeme kalemi (BULK_PAYOUT_ITEM_WALLET) bilinçli olarak listede yoktur — o bir fonlama hareketidir, harcama değil.
2. Kategori
Katalog sabittir ve GET spend-policy-categories ile okunur:
FOOD_BEVERAGE · GROCERY · FUEL · TRANSPORT · HEALTH · PHARMACY · EDUCATION · CLOTHING · ELECTRONICS · ENTERTAINMENT · TRAVEL · STATIONERY · SERVICES · OTHER
Kategori, işyerine PUT end-users/:id/merchant-category ile atanır.
Kategori ekseni bugün yalnız PAYMENT_BUY operasyonunda koşar. Diğer operasyonlarda ortada kategorisi olan bir işyeri yoktur (IBAN çekiminin, fatura ödemesinin ya da ATM çekiminin bir "restoran" karşılığı yok).
Bunun iki yönlü sonucu vardır ve ikisi de sessiz değildir:
- Kategori kısıtlayan bir
ALLOW_LISTkuralı, kategori taşımayan bir kanalı serbest bırakmaz — o kanal varsayılan redde katkı yapar. - Kategori kısıtlayan bir
DENY_LISTkuralı, kategori taşımayan bir kanalı engellemez.
"Yemek kartı yalnız restoranda geçsin" demek istiyorsanız kategori kuralının yanına operasyon eksenini de koymalısınız; aksi hâlde IBAN çekimini kapattığınızı sanırsınız.
Hangi operasyonun kategori/işyeri eksenlerini gerçekten çalıştırdığını GET spend-policy-operations yanıtındaki merchantBearing alanı söyler.
Gerçek MCC eşlemesi
Yukarıdaki kategoriler MCC değildir, ama gerçek şebeke MCC'leri (ISO 18245, 4 hane) bu kategorilere eşlenir. Eşleme tablosu GET mcc-categories ile okunur ve panelde kategori seçerken gösterilir:
[
{ "mcc": "3000-3299", "category": "TRAVEL", "isRange": true },
{ "mcc": "5411", "category": "GROCERY", "isRange": false },
{ "mcc": "5812", "category": "FOOD_BEVERAGE", "isRange": false }
]Satırlar ya tek bir kod ya da ISO blok aralığıdır (isRange). Eşleme platform genelinde tek kaynaktır: aynı tablo dolandırıcılık kurallarında da kullanılır.
Tabloda olmayan bir MCC hiçbir kategoriye düşmez — bilinçli olarak OTHER'a indirgenmez.
OTHER "operatör baktı ve diğer dedi" demektir ve bir ALLOW_LIST'e eklendiğinde geçer. Sınıflandırılmamış bir MCC'yi OTHER saymak, tanımadığınız her işyerini sessizce izin listenizden geçirirdi. Bilinmeyen MCC = kategori yok ⇒ kategori ekseni fail-closed davranır.
Kart otorizasyonunda (CARD_AUTH) kategori ekseni bugün koşmaz — kart tarafında gerçek MCC issuer entegrasyonuyla akmaya başlayacak; o güne dek kart harcaması yalnız operasyon ekseninden ve limitlerden kısıtlanır. Eşleme tablosu hazır bekler: kart MCC'si aktığında aynı kategorilere çözülür.
3. İşyeri
merchantEndUserIds[], kısıtın uygulanacağı işyeri son kullanıcı kimliklerinin listesidir. Kategori ekseniyle aynı merchantBearing kuralına tabidir.
4. Zaman penceresi
Pencere; ISO hafta günü (1 = Pazartesi … 7 = Pazar) ve gün içi [başlangıç, bitiş) aralığı ile tanımlanır. Bir kuraldaki pencereler VEYA'lanır: herhangi biri tutarsa eksen geçer.
Her kural kendi IANA saat dilimini (timeZone) taşır ve pencere varsa bu alan ZORUNLUDUR. Varsayılan YOKTUR.
Sunucunun saat dilimine, kullanıcının cihazına ya da UTC'ye düşülmez. Aynı mutlak an, farklı timeZone taşıyan iki kuralda farklı karar üretir — istenen davranış budur: bir kurum İstanbul mesaisine, bir başkası Londra mesaisine göre çalışır.
Pencere tanımlı ama saat dilimi eksik bir satır fail-closed değerlendirilir (eksen geçmez). Sessiz bir UTC varsayımı yoktur.
Europe/Istanbul gibi geçerli bir IANA kimliği verilmelidir; geçersiz kimlik yazma anında reddedilir. Pencere temizlendiğinde saat dilimi de temizlenir.
Değerlendirme kuralları
Kural içinde: VE
Bir kuraldaki kısıtlanmış eksenlerin hepsi aynı anda tutmalıdır. Operasyon, kategori, işyeri ve zaman sırayla sınanır; ilk tutmayan eksende kural eşleşmez.
Kısıtlanmamış (verilmemiş) eksen sınanmaz.
Kurallar arasında: DENY > ALLOW
DENY_LIST her zaman kazanır ve özgüllük önceliği yoktur. Tüm DENY_LIST kuralları, herhangi bir ALLOW_LIST kuralı değerlendirilmeden önce koşar. Dar bir işyeri temelli DENY_LIST, geniş bir kategori temelli ALLOW_LIST'i yener — "daha spesifik kural kazanır" mantığı yoktur.
Kapsamlar arasında: KESİŞİM
İki kapsam vardır: program (corporateAccountId boş) ve kurum (corporateAccountId dolu). Kullanıcı için yalnız programın kuralları ve kendi kurumunun kuralları yüklenir.
Aynı kapsam içindeki ALLOW_LIST kuralları birleşir (VEYA); farklı kapsamlar KESİŞİR (VE).
ALLOW_LIST kuralı bulunan her kapsamda en az bir eşleşme gerekir. Yani bir kurum, program operatörünün dar ALLOW listesini genişletemez — dar kapsam geniş kapsamı gevşetemez, yalnız daha da daraltabilir.
Tek havuz olsaydı bir kurumun geniş bir ALLOW kuralı yazması, program operatörünün koyduğu sınırı kaldırırdı.
"Politika yok" ile "boş liste" aynı şey DEĞİLDİR
Bu ayrım yazılmazsa operatör tam tersini yapar:
- Hiç
ACTIVEkural yoksa motor ATILDIR. Hiçbir eksen çözümlenmez bile, her operasyon geçer.INACTIVEbir kural yok sayılır; başka bir programın kuralı bağlamaz. - Boş bir dizi (
[]) ise "kısıtsız" DEĞİL, "hiçbir şeyle eşleşme" demektir ve fail-closed okunur. Boş listeli birALLOW_LISTkuralı hiçbir zaman eşleşmez ⇒ o kapsamda her şey reddedilir.
Boş dizi yazmak zaten üç katmanda birden (istek doğrulama, servis ve veritabanı kısıtı) reddedilir — ama davranışı bilmek, "listeyi boşaltayım, kısıt kalksın" refleksini engeller. Kısıtı kaldırmak istiyorsanız kuralı INACTIVE yapın veya silin; listesini boşaltmayın.
Çözülemeyen eksen = fail-closed
Bir operasyon kategori/işyeri taşıyorsa ama değer çözülemiyorsa ve en az bir kural o ekseni kısıtlıyorsa, işlem CATEGORY_UNRESOLVED / MERCHANT_UNRESOLVED gerekçesiyle reddedilir. Hiçbir kural o ekseni kısıtlamıyorsa ret yoktur.
Ret gerekçeleri: DENIED_BY_RULE · NO_MATCHING_ALLOW_RULE · CATEGORY_UNRESOLVED · MERCHANT_UNRESOLVED.
Motorun zincirdeki yeri
Harcama kısıtı, KYC ve yetenek kapılarıyla birlikte; ücret hesabından ve limit rezervasyonundan ÖNCE koşar — yani para bloke edilmeden önce. Reddedilen bir işlem hiçbir bakiyeyi tutmaz.
Limitlerle karıştırmayın: limit "ne kadar", harcama kısıtı "nerede/ne zaman/hangi kanalda" sorusunu cevaplar. İkisi ayrı motorlardır ve ikisi de geçilmelidir. Bkz. Limitler.
Kontrol listesi
- Kategori kuralının yanında operasyon ekseni de tanımlı (yoksa yalnız
PAYMENT_BUYetkilenir) - Her zaman penceresi kuralında
timeZoneaçıkça verilmiş - Kısıt kaldırırken liste boşaltılmıyor, kural
INACTIVEyapılıyor - Kurum kurallarının programı gevşetemeyeceği ürün tasarımında varsayılmış
-
DENY_LISTkurallarının her zaman kazandığı operatöre anlatılmış - Kısıt reddi kullanıcıya "bakiye yetersiz" değil, gerçek sebebiyle gösteriliyor