Sadakat & Cashback
- 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şen | Ne 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.
| Alan | Açıklama |
|---|---|
asset | Zorunlu. Kampanyanın tek varlığı — aşağıdaki bütün minor-unit alanlar bu varlık cinsindendir |
ruleSet | Kampanyanı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 |
perUserCapMinor | Kullanıcı başına tavan |
totalBudgetMinor | Kampanyanın toplam bütçesi |
minTransactionAmountMinor | Alt sınır |
stackable / exclusiveGroup / priority | Başka kampanyalarla birlikte çalışıp çalışmayacağı |
startsAt / endsAt | Geç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:
| Yaprak | Ne kısıtlar |
|---|---|
operationType | Operasyon (ör. PAYMENT_BUY) — tek değer veya dizi |
currency | Varlık |
category | İşyeri kategorisi (aşağıya bakın) |
amountRange | minMinor / maxMinor tutar aralığı |
channel | Kanal |
timeWindow | Gün/saat/hafta sonu penceresi (İstanbul saati) |
firstNOperations | Kullanıcının ilk N işlemi |
frequency | Dönem başına en fazla N (DAY / MONTH) |
userSegment / segment | Tü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 kampanyalareligible 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ı
| Alan | Açıklama |
|---|---|
earnKind | Kazanç tipi (oransal, sabit, çarpan) |
ratePpm | Oransal kazanç |
fixedPoints | Sabit puan |
multiplierBps | Çarpan |
priority | Birden çok kural eşleşirse sıra |
ruleSet | Kuralı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/historyPuanlar 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| Alan | Zorunlu | Açı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.
| Alan | Açıklama |
|---|---|
metricKind | Hangi metriğe bakılacağı |
windowDays | Ölçüm penceresi |
demotionGraceDays | Düşüş öncesi tanınan süre |
tiers[].code / rank / thresholdMinor | Kademe kodu, sırası ve eşiği |
tiers[].feeScheduleOverrideId | O kademeye özel ücret şeması |
tiers[].limitTierBoostId | O kademeye özel limit artışı |
tiers[].campaignMultiplierBps | O kademede cashback çarpanı |
GET /api/portal/v1/wallet/me/tierSeviyenin 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.
| Alan | Açıklama |
|---|---|
code / name / description / iconRef | Tanım |
hidden | Kazanılana kadar gizli mi |
repeatable | Tekrar kazanılabilir mi |
rewardCampaignId | Kazanınca tetiklenecek ödül |
Kural: triggerEvent / operationType / condition | Ne zaman verileceği |
GET /api/portal/v1/wallet/me/badgesWebhook'lar
| Olay | Ne zaman |
|---|---|
wallet.loyalty.earned | Puan kazanıldı |
wallet.loyalty.redeemed | Puan harcandı |
wallet.loyalty.expired | Puan lotu süresi doldu |
wallet.loyalty.tier_changed | Kullanıcının seviyesi değişti |
wallet.badge.granted | Rozet verildi |
wallet.badge.reward_credited | Rozet ödülü hesaba yatırıldı |
wallet.campaign.paused_insufficient_funds | Kampanya bütçesi bitti, duraklatıldı |
Yaygın hatalar
| Durum | Sebep | Çözüm |
|---|---|---|
| Cashback yatmıyor | Kampanya bütçesi bitti veya duraklatıldı | paused_insufficient_funds olayını dinleyin |
| İki kampanya üst üste bindi | stackable açık, exclusiveGroup tanımsız | Aynı gruba alıp öncelik verin |
| Puan beklenmedik şekilde azaldı | Lot süresi doldu | expired olayını dinleyin, kullanıcıya önceden gösterin |
| Seviye düşmedi/yükselmedi | Ölçüm penceresi veya grace süresi | windowDays ve demotionGraceDays değerlerini kontrol edin |