Cüzdan
Kurumsal Hesaplar

Kurumsal Hesaplar & Çalışan Yönetimi

Bu uçları kim çağırır?Dış API (s2s)
Taban yol
/api/v1/wallet
Kimlik
Ed25519 istek imzası — X-Key-Id, X-Date, X-Nonce, Digest (body varsa), X-Signature. Zaman penceresi ±5 dk, nonce tek kullanımlık, anahtarda IP allow-list olabilir.
Yetki
Anahtarınız cüzdan ürününü ve çağrının gerektirdiği cüzdan iznini taşımalıdır; izni olmayan çağrı 403 WALLET_INSUFFICIENT_SCOPE alır. Uç başına izinler: Anahtar izinleri.
Çapraz yüzey
Bu uçlar s2s yüzeyindedir. Panelde ayrıca GET corporate-accounts/:id ve PATCH corporate-accounts/:id vardır (wallet:enduser:read / wallet:enduser:manage); bunların s2s karşılığı YOKTUR.

Bir kurumsal hesap, bir işverenin çalışanlarını tek bir çatı altında toplar. Yemek kartı, ulaşım ödeneği, harcırah ve benzeri kurumsal cüzdan senaryolarının taşıyıcısıdır.

⚠️

Bu işlemleri KİM yapar? Bugün program operatörü — yani cüzdan programını işleten kurum — panelden ya da s2s API'den yapar. İşverenin kendi girip çalışanlarını yönetebileceği bir self-servis yüzey YOKTUR. Bir işveren kendi kadrosunu yönetmek isterse, program operatörünün onun adına işlem yapması gerekir.

Kurumsal hesap bir giriş kimliği değildir: kullanıcı adı, parola veya oturum taşımaz. Müşteri portalı son kullanıcıya (cüzdan sahibine) aittir; işverene ait bir portal bulunmuyor.

Kurumsal hesabın kendisi para tutmaz. Para her zaman çalışanın kendi cüzdanındadır; kurumsal hesap yalnız aidiyet ve yetki taşır. Bu ayrım, aşağıdaki "ayırma paraya dokunmaz" davranışının tek sebebidir.

Yeni bir izin ailesi (wallet:corporate:*) icat edilmedi: kurumsal hesap yönetimi son kullanıcı yönetiminin bir parçasıdır ve aynı izin kodlarını kullanır.

s2s yüzeyinde kurumsal hesabın dört ucu da anahtarınızda wallet:bulk:create iznini gerektirir. Bkz. Anahtar izinleri.

Uçlar

Panel yüzeyi (Bearer JWT + RBAC):

POST  corporate-accounts                        # yeni kurumsal hesap
GET   corporate-accounts                        # programId, status, limit, cursor
GET   corporate-accounts/:id                    # detay
PATCH corporate-accounts/:id                    # name / status
POST  corporate-accounts/:id/employees/import   # toplu çalışan ekleme
POST  corporate-accounts/:id/employees/offboard # toplu çalışan ayırma

Sunucudan sunucuya (s2s, imzalı):

POST /api/v1/wallet/corporate-accounts
GET  /api/v1/wallet/corporate-accounts
POST /api/v1/wallet/corporate-accounts/:id/employees/import
POST /api/v1/wallet/corporate-accounts/:id/employees/offboard
⚠️

s2s yüzeyinde detay (GET :id) ve güncelleme (PATCH :id) uçları yoktur — bu ikisi yalnız panelde bulunur. Entegrasyonunuzu kurumsal hesabı s2s'ten okuyacak şekilde kurmayın; liste ucunu (GET corporate-accounts) kullanın.

s2s'teki üç mutasyon ucu (POST corporate-accounts, employees/import, employees/offboard) Idempotency-Key başlığı zorunludur. Bkz. Kimlik Doğrulama.

Kurumsal hesap tanımı

AlanZorunluAçıklama
programId✅Hangi cüzdan programına ait — değiştirilemez
code✅İşveren kodu (1–64). Kaydedilirken kırpılır ve büyük harfe çevrilir; program içinde tekildir — değiştirilemez
name✅Görünen ad (1–160)
status—ACTIVE | ARCHIVED (varsayılan ACTIVE)

PATCH corporate-accounts/:id yalnız name ve status alanlarını kabul eder.

code büyük harfe normalize edilir: acme ile ACME aynı koddur ve ikincisi tekillik hatası alır. Kendi tarafınızda kodu üretirken bunu varsayın; aksi hâlde "kaydettim ama listede yok" gibi görünen bir çakışma yaşarsınız.

Çalışan bağı

Bir son kullanıcının en fazla bir işvereni olabilir (corporateAccountId).

  • Tek kullanıcı oluştururken: POST end-users gövdesindeki corporateAccountId.
  • Toplu: employees/import.
  • Kadro listesi: GET end-users?corporateAccountId=<id>.
⚠️

Zaten başka bir kuruma bağlı bir kullanıcıyı içe aktarmaya çalışmak WALLET_CORPORATE_LINK_CONFLICT ile reddedilir — bağ sessizce yeni kuruma taşınmaz. Çalışan iş değiştirdiyse önce eski işverenden ayırın, sonra yenisine aktarın.

Toplu içe aktarma

POST corporate-accounts/:id/employees/import

Gövde, satır dizisidir (rows, 1–1000 satır):

Satır alanıZorunluKural
phone✅E.164 benzeri numara, 8–20 karakter (+, rakam, boşluk, parantez, tire)
displayName—En çok 120 karakter
externalCustomerId—En çok 128 karakter — kendi sisteminizdeki kimlik
kycRef—En çok 128 karakter

CSV tarayıcıda ayrıştırılır. Uç, CSV metni değil bir JSON satır dizisi kabul eder; yukarıdaki dört alan CSV kolonlarınızın karşılığıdır. accountType alanı yoktur ve gönderilirse istek reddedilir: içe aktarma her zaman bireysel (PERSONAL) hesap açar.

Hepsi-ya-da-hiçbiri

🚫

İçe aktarma atomiktir. Tek bir satır bile hata verirse hiçbir satır yazılmaz — o ana kadar oluşturulmuş kullanıcılar, cüzdanlar ve hesaplar dâhil her şey geri alınır.

Yanıt bu durumda HTTP 422 ve code: WALLET_BULK_IMPORT_REJECTED olur; rapor details altında döner ve hatasız satırların durumu ROLLED_BACK olarak işaretlenir. "Bir kısmı geçti" diye bir durum yoktur — dosyayı düzeltip tamamını yeniden yükleyin.

Satır durumları

DurumAnlamı
CREATEDNumara sistemde yoktu — kullanıcı ve varsayılan cüzdanı oluşturuldu, kuruma bağlandı
LINKED_EXISTINGKullanıcı vardı ve hiçbir kuruma bağlı değildi — bu kuruma bağlandı
ALREADY_LINKEDKullanıcı zaten bu kuruma bağlıydı — hiçbir yazma yapılmadı
ERRORSatır reddedildi (aşağıdaki kodlar)
ROLLED_BACKSatır geçerliydi ama yazılmadı, çünkü başka bir satır hata verdi

Satır hata kodları: WALLET_CORPORATE_LINK_CONFLICT (başka kuruma bağlı), WALLET_ENDUSER_CLOSED (kullanıcı kapalı), WALLET_VALIDATION (biçim), WALLET_ENDUSER_EXISTS (dosya içinde tekrarlanan kayıt).

Başarılı yanıt

{
  "status": "success",
  "code": "SUCCESS",
  "data": {
    "applied": true,
    "corporateAccountId": "…",
    "totalRows": 120,
    "createdCount": 94,
    "linkedCount": 21,
    "alreadyLinkedCount": 5,
    "errorCount": 0,
    "results": [
      { "index": 0, "phoneMasked": "+90 5** *** ** 41", "status": "CREATED", "endUserId": "…" }
    ]
  }
}

Yanıt ham telefon numarası taşımaz, yalnız phoneMasked. Satırı kendi dosyanızla eşlemek için index veya externalCustomerId alanını kullanın.

totalRows her zaman gönderdiğiniz satır sayısıdır; reddedilen bir yüklemede results yalnız hatalı satırları taşıyabilir, dolayısıyla results.length ile totalRows eşit olmayabilir.

⚠️

İçe aktarma, kurumsal hesap ve program ACTIVE değilse reddedilir.

Çalışan ayırma (offboard)

POST corporate-accounts/:id/employees/offboard
Gövde alanıZorunluKural
rows[]✅1–1000 satır
rows[].endUserId◑UUID — externalCustomerId ile tam olarak biri verilir
rows[].externalCustomerId◑En çok 128 karakter
reason—En çok 240 karakter — yalnız denetim kaydı, karara etkisi yok

Satır durumları: OFFBOARDED · ALREADY_OFFBOARDED · ERROR · ROLLED_BACK. Satır hata kodları: WALLET_CORPORATE_NOT_MEMBER, WALLET_NOT_FOUND, WALLET_VALIDATION, WALLET_ENDUSER_EXISTS.

Ayırma da hepsi-ya-da-hiçbiridir; reddedilen bir çağrı HTTP 422 ve code: WALLET_BULK_OFFBOARD_REJECTED döner.

Ayırma paraya DOKUNMAZ

🚫

Ayırma bir para işlemi değildir. Çalışanın bakiyesi kendisinde kalır ve çalışan harcamaya devam eder. Kesilen tek şey, kurumun o çalışan üzerindeki yetkisidir.

Somut olarak ayırma yalnız kurumsal bağ alanlarını günceller. Ayırmadan sonra:

  • Kullanıcının durumu ACTIVE kalır — dondurulmaz.
  • Cüzdanları ve hesapları ACTIVE kalır.
  • Ledger'a hiçbir kayıt yazılmaz, bakiye okunmaz bile.
  • Kartlar dondurulmaz.

Bu bilinçli bir karardır: elektronik para hamilinin geri ödeme hakkı koşulsuzdur, dolayısıyla bir işveren ilişkisinin sona ermesi hamilin parasına el konulmasının gerekçesi olamaz. Bakiyeyi geri alan, süpüren veya kuruma aktaran bir uç yoktur ve eklenmemelidir.

⚠️

Ürün metinlerinizde ve panel ekranınızda ayırmayı "bakiye geri alınır" izlenimi verecek şekilde anlatmayın. Doğru cümle: "Çalışan kurum kadrosundan çıkarılır; mevcut bakiyesi kendisinde kalır."

Kurumun yüklediği paranın geri alınmasını gerektiren bir ticari düzeniniz varsa bunu yükleme tarafında çözün (ör. dönemsel yükleme), ayırma ucunda değil.

Ayırma, içe aktarmadan farklı olarak kurumsal hesabın veya programın ACTIVE olmasını gerektirmez. Bir yetkinin geri alınması, o yetkiyi veren kabın durumu yüzünden engellenmemelidir — arşivlenmiş bir işveren de kadrosunu serbest bırakabilir.

Aynı çalışanı ikinci kez ayırmak hata değildir: ALREADY_OFFBOARDED döner.

Kurumsal harcama kısıtı

Bir kurumun çalışanlarına özel harcama kuralları (hangi kategoride, hangi kanalda, hangi saatlerde harcanabilir) ayrı bir motorla tanımlanır: Harcama Kısıtı.

Kontrol listesi

  • code üretiminiz büyük harf normalizasyonunu varsayıyor
  • İçe aktarma yanıtındaki applied alanı kontrol ediliyor; 422 gövdesindeki details.results operatöre gösteriliyor
  • ROLLED_BACK satırlar kullanıcıya "başarısız" değil "yazılmadı" olarak anlatılıyor
  • Ayırma ekranında bakiyeye dair yanıltıcı ifade yok
  • İş değiştiren çalışan için önce ayırma, sonra içe aktarma sırası uygulanıyor
  • s2s mutasyonlarında Idempotency-Key gönderiliyor