Cüzdan
Sadakat & Cashback

Sadakat & Cashback

Bu uçları kim çağırır?Portal / SDK
Taban yol
/api/portal/v1/wallet/me
Kimlik
Publishable key (pk_live_…) → OTP → müşteri oturumu. Oturum son kullanıcıya aittir, merchant'a değil; yalnız o kullanıcının kendi verisine erişir.
Yetki
Oturumun kendisi yetkidir; kullanıcı yalnız kendi kaydına erişir.
Çapraz yüzey
s2s API'de sadakat ucu YOKTUR — ne okuma ne yazma. Son kullanıcı yüzeyi portaldadır; kural, seviye ve rozet ŞEMASI panelde yönetilir (wallet:loyalty:read / wallet:loyalty:manage).

Sadakat motoru dört bileşenden oluşur ve dördü de aynı cüzdan altyapısı üzerinde çalışır:

BileşenNe yapar
Kampanya (cashback)Belirli işlemlerde kullanıcıya para iadesi verir
Puanİşlemlerden puan kazandırır, puan bakiyeye çevrilebilir
Seviye (tier)Kullanıcıyı harcamasına göre kademelere ayırır, ayrıcalık tanır
Rozet (badge)Davranışa bağlı başarımlar verir, ödülle ilişkilendirilebilir

Cashback kampanyaları

Bir kampanya; hangi işlemlerde, ne kadar, kime ve hangi bütçeyle iade verileceğini tanımlar.

AlanAçıklama
assetZorunlu. Kampanyanın tek varlığı — aşağıdaki bütün minor-unit alanlar bu varlık cinsindendir
ruleSetKampanyanın hangi işlemlerde tetikleneceği
calculatorType / calculatorParamsİade tutarının nasıl hesaplanacağı
payerType / payerAccountRefİadeyi kimin finanse ettiği
accrualTiming / accrualDelayDaysİadenin anında mı, gecikmeli mi yatacağı
perOperationCapMinorİşlem başına tavan
perUserCapMinorKullanıcı başına tavan
totalBudgetMinorKampanyanın toplam bütçesi
minTransactionAmountMinorAlt sınır
stackable / exclusiveGroup / priorityBaşka kampanyalarla birlikte çalışıp çalışmayacağı
startsAt / endsAtGeçerlilik penceresi
🚫

Kampanya tek varlıklıdır. asset zorunludur, programın currencies listesinde bulunmak zorundadır ve kampanya oluşturulduktan sonra değiştirilemez (güncelleme yüzeyi yoktur).

Sonucu doğrudan davranışa yansır: kampanyanın varlığından farklı bir varlıkta yapılan işlem kampanyayı tetiklemez. TRY kampanyanız USD bir ödemede çalışmaz — çok para birimli bir programda her varlık için ayrı kampanya açın.

Neden böyle: totalBudgetMinor, perUserCapMinor, perOperationCapMinor ve minTransactionAmountMinor alanlarının hepsi minor birimdedir ve minor birimin ölçeğini ancak bir varlık söyleyebilir. Varlık kolonu olmadan "100 kuruş" ile "100 sent" aynı bütçe sayacına ekleniyordu ve toplam bütçe tavanı anlamsızlaşıyordu.

⚠️

Bütçe koruması: kampanyayı finanse eden hesabın bakiyesi yetmezse kampanya otomatik olarak duraklatılır ve wallet.campaign.paused_insufficient_funds webhook'u düşer. Bu olayı dinleyin — duraklamış bir kampanya sessizce iade vermeyi keser ve kullanıcı şikâyeti olarak size döner.

Kural seti (ruleSet) eksenleri

ruleSet, all / any / not ile birleşen bir JSON koşul ağacıdır; yaprakları kampanyanın hangi işlemde tetikleneceğini söyler:

YaprakNe kısıtlar
operationTypeOperasyon (ör. PAYMENT_BUY) — tek değer veya dizi
currencyVarlık
categoryİşyeri kategorisi (aşağıya bakın)
amountRangeminMinor / maxMinor tutar aralığı
channelKanal
timeWindowGün/saat/hafta sonu penceresi (İstanbul saati)
firstNOperationsKullanıcının ilk N işlemi
frequencyDönem başına en fazla N (DAY / MONTH)
userSegment / segmentTüretilmiş nitelik / statik CSV üyelik

Örnek — yalnız market harcamasında iade, eğlence hariç:

{ "all": [
  { "operationType": "PAYMENT_BUY" },
  { "category": "GROCERY" },
  { "not": { "category": "ENTERTAINMENT" } }
] }
⚠️

category yaprağı, harcama kısıtındaki kategori ekseniyle AYNI kanonik sözlüğü kullanır (GET spend-policy-categories: GROCERY · FUEL · HEALTH …) ve yalnız işyeri taşıyan operasyonda (bugün PAYMENT_BUY) çözülür. Bir cüzdan işleminin (transfer/topup) ya da kategorisi atanmamış bir işyerinin kategorisi yoktur ⇒ category yaprağı fail-closed (eşleşmez, iade vermez). Bu, yanlış kişiye ödeme yapmamak için bilinçlidir.

Kategori işyerine PUT end-users/:id/merchant-category ile atanır; sözlükte olmayan bir kategori yazma anında reddedilir (ölü kural olmaz). Bkz. Harcama Kısıtı — Kategori.

Kart otorizasyonu (CARD_AUTH) bugün gerçek MCC taşımaz — kart tarafı kategori kampanyası kart ihracıyla (gerçek şebeke MCC eşlemesi) gelir.

Kampanya oluşturma, sürümleme, simülasyon ve onay panelden yapılır: Panel — Kampanya & Sadakat.

Kampanyayı canlıya almadan önce panelden simüle edin: geçmiş işlemler üzerinde çalıştırıp ne kadar bütçe yakacağını görün. Kural setindeki bir hata, canlıda bütçeyi saatler içinde tüketebilir.

Son kullanıcı tarafı

GET /api/portal/v1/wallet/me/cashback           # kazanılan iadeler
GET /api/portal/v1/wallet/me/cashback/eligible  # bu kullanıcı için geçerli kampanyalar

eligible ucu, kullanıcıya "şu an hangi kampanyalardan yararlanabilirim" listesini göstermek içindir.

Puan

Puan, cashback'ten farklıdır: para değil, ayrı bir varlıktır ve harcanmak için bakiyeye çevrilir.

Kazanma kuralları

AlanAçıklama
earnKindKazanç tipi (oransal, sabit, çarpan)
ratePpmOransal kazanç
fixedPointsSabit puan
multiplierBpsÇarpan
priorityBirden çok kural eşleşirse sıra
ruleSetKuralın hangi işlemlerde çalışacağı

Puan bakiyesi ve geçmişi

GET /api/portal/v1/wallet/me/loyalty/balance
GET /api/portal/v1/wallet/me/loyalty/history

Puanlar lot mantığıyla tutulur: her kazanç kendi son kullanma tarihiyle bir lot oluşturur. Harcama en eski lottan başlar.

⚠️

Lot son kullanma süresi (lotExpiryDays) tanımlıysa puanlar sessizce yanmaz — wallet.loyalty.expired olayı düşer. Kullanıcıya son kullanma tarihi yaklaşan puanlarını gösterin; "puanlarım kayboldu" şikâyetinin tek çözümü budur.

Puan harcama

POST /api/portal/v1/wallet/me/loyalty/redeem
AlanZorunluAçıklama
loyaltyProgramId✅
walletAccountId✅Puanın çevrileceği hesap
points✅Harcanacak puan
idempotencyKey✅Çift harcama koruması

Çevrim oranı (redemptionRatePointsPerMinor) ve alt sınır (minRedeemPoints) sadakat programında tanımlıdır.

Seviye (tier)

Seviye şeması, kullanıcıyı bir metriğe (ör. dönem harcaması) göre kademelere ayırır.

AlanAçıklama
metricKindHangi metriğe bakılacağı
windowDaysÖlçüm penceresi
demotionGraceDaysDüşüş öncesi tanınan süre
tiers[].code / rank / thresholdMinorKademe kodu, sırası ve eşiği
tiers[].feeScheduleOverrideIdO kademeye özel ücret şeması
tiers[].limitTierBoostIdO kademeye özel limit artışı
tiers[].campaignMultiplierBpsO kademede cashback çarpanı
GET /api/portal/v1/wallet/me/tier

Seviyenin somut karşılığı olmalı: daha düşük ücret, daha yüksek limit veya daha yüksek cashback. Yalnız isim değişen bir kademe sistemi kullanıcıda karşılık bulmaz — üç alan (feeScheduleOverrideId, limitTierBoostId, campaignMultiplierBps) tam bunun içindir.

demotionGraceDays, kullanıcının bir dönem az harcadı diye anında düşmesini engeller.

Rozetler

Rozet, davranışa bağlı bir başarımdır ve bir ödül kampanyasına bağlanabilir.

AlanAçıklama
code / name / description / iconRefTanım
hiddenKazanılana kadar gizli mi
repeatableTekrar kazanılabilir mi
rewardCampaignIdKazanınca tetiklenecek ödül
Kural: triggerEvent / operationType / conditionNe zaman verileceği
GET /api/portal/v1/wallet/me/badges

Webhook'lar

OlayNe zaman
wallet.loyalty.earnedPuan kazanıldı
wallet.loyalty.redeemedPuan harcandı
wallet.loyalty.expiredPuan lotu süresi doldu
wallet.loyalty.tier_changedKullanıcının seviyesi değişti
wallet.badge.grantedRozet verildi
wallet.badge.reward_creditedRozet ödülü hesaba yatırıldı
wallet.campaign.paused_insufficient_fundsKampanya bütçesi bitti, duraklatıldı

Yaygın hatalar

DurumSebepÇözüm
Cashback yatmıyorKampanya bütçesi bitti veya duraklatıldıpaused_insufficient_funds olayını dinleyin
İki kampanya üst üste bindistackable açık, exclusiveGroup tanımsızAynı gruba alıp öncelik verin
Puan beklenmedik şekilde azaldıLot süresi dolduexpired olayını dinleyin, kullanıcıya önceden gösterin
Seviye düşmedi/yükselmediÖlçüm penceresi veya grace süresiwindowDays ve demotionGraceDays değerlerini kontrol edin