## ARENA BATTLE — SİTE HESABI VE TRANSFER CÜZDANI

## 1. Oyuncu kimliği ve giriş akışı
Oyuncu operatör sitesinde kendi hesabına giriş yapar, Arena Battle'a tıklar.
Operatör SUNUCUSU o oturumdaki değişmeyen üye ID'si ile launch oluşturur.
Iframe veya dosyalı modül aynı hesaba açılır; ikinci bir kullanıcı adı sorulmaz.
Browser'dan `externalId`, kullanıcı adı veya bakiye kabul edilmez.

Admin > Siteler > Site kimliği ve erişim > Kalıcı oyuncu kodu öneki alanına
örneğin cio yazılır. Yeni hesaplar cio1001, cio1002... olarak adlandırılır.
`publicId` hesap başına bir kez atanır; tenant + `externalId` eşlemesi kalıcıdır.
Aynı üyeyi başka nick ile tekrar göndermek yeni hesap veya isim oluşturmaz.
Aynı `externalId` farklı sitelerde ayrı hesap, bakiye ve geçmişe sahiptir.
Önek değişikliği yalnız yeni hesapları etkiler. Eski oyuncu adları/sonuçları
korunur; eski hesaplar da kalıcı `publicId` kazanır. `publicId` tek başına küresel
kimlik değildir: raporlarda site ID'si ile birlikte kullanılır.
Önek boş bırakılan eski entegrasyonlarda yeni oyuncu için name zorunludur.
Önek ayarlıysa name gönderilmeden hesap açılır ve otomatik kod isim olur.
Misafir/isime göre giriş, operatör entegrasyonlarında kapalı tutulur.

## 2. Transfer cüzdanı ve harcanabilir bakiye
Bu bölüm transfer cüzdanı modelini anlatır. Siteyle aynı harcanabilir bakiyeyi kullanmak için SEAMLESS-WALLET.txt rehberindeki ayrı callback sözleşmesini uygulayın.
Operatörün kendi veritabanındaki kullanılabilir bakiye ile sağlayıcıdaki oyun
bakiyesi ayrı hesaplanır. Birini diğerinin üzerine kopyalamayın.

Örnek: operatörde 50.000 TL var, oyuna 10.000 TL aktarılacak.
- Operatörün asıl cüzdanında 10.000 TL önce atomik düşülür/rezerve edilir.
- Aynı değişmeyen işlem referansıyla sağlayıcıya 10.000 TL credit gönderilir.
- Site kullanılabilir = 40.000 TL; oyun kullanılabilir = 10.000 TL.
- 2.000 TL'lik odaya girişte oyun kullanılabilir 8.000 TL, kilitli 2.000 TL.
- İptal edilen bekleyen oda rezervi oyun cüzdanına geri döner.
- Sonuçtaki ödül oyun cüzdanına eklenir; site başlığı API'den tekrar okuyarak
  güncellenir. Para operatörün diğer oyunlarında harcanacaksa cashout gerekir.
- Cashout: önce sağlayıcı kullanılabilir bakiyesinden debit kesinleşir,
  sonra operatör cüzdanına tam bir kez credit yazılır.
Toplam görüntüsü gerekiyorsa `siteAvailableMinor` + `gamingAvailableMinor` kullanın;
bu toplamın tamamını operatörün diğer ürünlerinde harcanabilir kabul etmeyin.
Kilitli tutarı toplam kullanılabilir içine eklemeyin.

## 3. Sunucu cüzdan modülü
Node 22.16+; `sdk/slot-battles-wallet.mjs` kalıcı SQLite işlem günlüğü içerir.
Bu modül REFERANS operatör cüzdanının sahibidir. Zaten cüzdanı olan bir site,
aynı bakiyeyi bu SQLite'a kopyalayıp iki bağımsız harcanabilir bakiye yaratmamalı:
modülün kısa yerel transaction'larını kendi asıl cüzdan/ledger veritabanına uyarlar.
Opening balance yalnız yerel test hesabı ilk oluşturulurken verilir; her girişte
başlangıç parası verilmez. Örnek dosya gerçek bir operatörün hesabına bağlı değildir.

  import { SlotBattlesClient } from './slot-battles-sdk.mjs';
  import { SlotBattlesWallet } from './slot-battles-wallet.mjs';
  const client = new SlotBattlesClient({
    origin: process.env.SLOT_BATTLES_ORIGIN,
    apiKey: process.env.SLOT_BATTLES_KEY
  });
  const wallet = new SlotBattlesWallet({
    file: '/private/site-wallet.sqlite', client, siteId: 'betcio'
  });
  // Bu ID, güvenilir backend üye oturumundan gelir; istek gövdesinden değil.
  const externalId = String(authenticatedMember.id);
  await client.player({ externalId }); // admin'de cio öneki ayarlı
  // Kendi kalıcı sipariş kaydınızdan referansı alın; retry'da değiştirmeyin.
  await wallet.fund({externalId, amountMinor: 1000000,
                     reference: durableFundingOrder.reference});
  const launch = await client.launch({externalId,
    origin: 'https://YOUR_SITE', mode: 'iframe', route: 'lobby'});

`wallet.createAccount({externalId,openingBalanceMinor})` referans yerel hesabı
idempotent açar; tekrar çağrı başlangıç bakiyesini eklemez veya sıfırlamaz.
fund/cashout aynı referansla aynı oyuncu/yön/tutarı yinelerse tek para etkisi vardır.
Farklı veriyle referans tekrarına `REFERENCE_CONFLICT` döner.
`WALLET_PENDING` geldiğinde sonuç belirsizdir: iade/başarı göstermeyin, yeni referans
üretmeyin. Aynı işlemi tekrar deneyin veya `recoverPending()` çalıştırın. O oyuncu
için başka aktarım, belirsiz işlem çözülene kadar bekler. Kesin ret veya referans
çakışması da otomatik para iadesi değildir; operatör makbuzuyla mutabakat gerekir.
`snapshot(externalId)` `siteAvailableMinor`, `gamingAvailableMinor`, `gamingLockedMinor`,
`totalAvailableMinor`, `pendingFundingMinor`, `pendingCashoutMinor` ve `hasPendingTransfer`
döndürür. `syncEvents()` cursor'u kalıcı saklar; olayları tekrar para olarak eklemez.
DB/ledger dosyasını kalıcı diskte tutun ve düzenli yedekleyin; yeniden başlatmada
`recoverPending()` çalıştırın. Bir siteye ait cüzdan DB'sini başka siteye bağlamayın.

## 4. Tarayıcı entegrasyonu
Kendi backend `/integration/launch` endpoint'iniz POST + doğrulanmış site oturumu
+ Origin kontrolü + CSRF doğrulaması kullanmalı. API anahtarı tarayıcıya verilmez.

  const launch = await fetch('/integration/launch', {
    method: 'POST', credentials: 'same-origin',
    headers: {'Content-Type':'application/json','X-CSRF-Token':pageCsrf},
    body: '{}'
  }).then(async r => { if (!r.ok) throw Error('Launch failed'); return r.json(); });
  const game = mountSlotBattles({container, launch, providerOrigin,
    onIdentity: ({player}) => showPlayerCode(player.publicId),
    onWalletChange: () => refreshBalanceFromOurAuthenticatedBackend()
  });

Dosyalı modülde server-render edilmiş config:
  window.SlotBattlesConfig = {
    apiBase: 'https://PROVIDER_HOST/play/betcio/',
    launchEndpoint: '/integration/launch', launchMethod: 'POST',
    launchCsrfToken: pageCsrf, sessionNamespace: opaqueSessionNamespace
  };
Modül her açılışta kendi sitenin oturumunu yeniden doğrular. Başarısız doğrulamada
önceki kişinin cached hesabıyla devam etmez. Farklı hesapla giriş aynı tarayıcıda
eski hesabı kullanmaz. module olayları window üzerinde slot-battles:identity ve
slot-battles:wallet-changed olarak gelir. Bunlar sadece yenileme sinyalidir; hesap,
yetki veya bakiye kanıtı değildir. Balance endpoint kendi session'ından ID bulur.
Iframe yardımcı modülü kaynak pencere ve tam origin'i doğrular; wildcard kullanmaz.

Operatör logout/hesap değiştirme işleminde `client.revokeSessions(externalId)`
çağırın; iframe'i `destroy()` edin ve modül sayfasını temizleyin. Bu API aynı üyenin
o sitedeki tüm oyun token/read token/henüz kullanılmamış biletlerini iptal eder.
Devam eden savaşın sırası, parası veya sonucu logout ile iptal edilmez.

## 5. Ek API uçları

```http Aktarım makbuzu
GET /provider/v1/wallet/transfers/{encodedReference}
```

wallet kapsamı gerekir. Değişmeyen makbuz, `externalId` ve o anki kullanılabilir/kilitli oyun bakiyesi döner.

```http Oyuncu oturumlarını iptal et
POST /provider/v1/player/{encodedExternalId}/sessions/revoke
```

```json İstek gövdesi
{}
```

launch kapsamı gerekir. İptal edilen session/read token/launch ticket sayıları döner.

```http Oyuncu bakiyesi
GET /provider/v1/player/{encodedExternalId}/balance
```

read kapsamı gerekir. `player.publicId`, balance/`availableBalance` ve `lockedBalance` döner; tutarlar tam sayı kuruştur. Yoldaki yer tutucuları URL kodlanmış kimliklerle değiştirin.

```http Kalıcı site hesabı
POST /provider/v1/player
POST /provider/v1/launch
```

Aynı tenant + `externalId` kalıcı hesabı kullanılır. Admin ve operatör oyuncu listeleri `publicId`, dış üye ID ve site bağlamını gösterir.

## 6. Çalışan referans site
`examples/operator-authenticated.cjs`: HttpOnly/SameSite cookie, sunucu oturumu,
CSRF, iframe veya gerçek dosyalı modül, iki üye arasında hesap değiştirme,
canlı site/oyun/kilitli bakiye başlığı, logout revoke, kalıcı ilk aktarım referansı.
GET bakiye okuması para taşımaz. Yalnız POST launch ilk aktarımı başlatır.
Örnek ilk aktarımı bir defa yapar; kayıp/yeniden girişte otomatik yeniden doldurmaz.
`OPERATOR_ACCOUNTS_FILE` özel JSON dosyasıdır:
  [{"login":"member1","password":"YOUR_LOCAL_TEST_PASSWORD",
    "externalId":"member-1","openingBalanceMinor":5000000}]
Gerekli ortam değişkenleri: `PROVIDER_ORIGIN`, `OPERATOR_SLUG`, `OPERATOR_API_KEY`,
`OPERATOR_ACCOUNTS_FILE`, `OPERATOR_WALLET_FILE`; opsiyonel `OPERATOR_MODE`=iframe|module,
`OPERATOR_PORT`, `OPERATOR_INITIAL_TRANSFER_MINOR`. Sunucu localhost'ta açılır.
Operatör origin'ini ana admin'de izinli origin'e ekleyin.
Eski `examples/operator-site.cjs` yalnız profile seçmeli geliştirme örneğidir;
üyelik doğrulama örneği yerine `operator-authenticated.cjs` kullanın.

## 7. Doğrulama
R23 testleri gerçek yerel HTTP sunucusu, SQLite, iframe ve modül kullanır.
Tekrarlı giriş, aynı kimliğin farklı siteleri, reload/restart, eşzamanlı aktarım,
commit sonrası kayıp yanıt, rezerv/iade/ödül muhasebesi, CSRF/oturum ve mobil
görünüm denetlenir. Bu testler üçüncü taraf sitenin gerçek cüzdan bağlantısı veya
sağlayıcı oyun/ses/görüntü testinin yerine geçmez. Operatör kendi asıl hesap ve
cüzdan adaptörüyle kabul testlerini aynı referans kurallarıyla tekrar yapar.

Referans sitede süre dolumu da sağlayıcı oturumlarını iptal eder; başarısız iptal periyodik kuyrukta yeniden denenir. İşlem sırasında süresi dolan yeni bilet iptal edilir. Kit örneğini çalıştırırken client ZIP içeriğini kit kökündeki public/ klasörüne çıkarın. Gerçek operatörün dağıtımında mevcut üyelik ve cüzdan veritabanına uyarlanır.

## 8. Site kimliği ve oturum risk bilgileri
`/player` ve `/launch` isteklerinde isteğe bağlı `siteUsername` alanı kullanılır:
  {"externalId":"member-1545","siteUsername":"betcio_uye","origin":"https://casino.example"}
`externalId` değişmeyen site hesap anahtarıdır; kullanıcının giriş adını kimlik anahtarı
olarak kullanmak yerine sitenin değişmeyen üye ID'sini gönderin. `siteUsername` gerçek
site kullanıcı adını/hesap etiketini ayrı saklar. Bu alan yalnız yetkili sunucudan
güncellenir; `publicId`, görünen oyun adı, bakiye ve geçmiş değişmez. İlk kayıtta
`siteUsername` yoksa name yedek olarak alınır; sonraki girişlerde alan atlanırsa
kayıtlı değer korunur. 1–128 karakter, kontrol karakteri yok. Ana admin ve ilgili
site paneli iki kimliği de görebilir. Diğer oyuncular ve tarayıcı launch/state
yanıtları `siteUsername`/`externalId`/cihaz bilgilerini içermez.

Oyuncu istemcisi oturum açıldıktan sonra `/play/{slug}/api/telemetry` endpoint'ine
sınırlı ortam bilgisini gönderir: saat dilimi, dil, platform, ekran ölçüsü, piksel
oranı, dokunma sayısı ve rastgele origin kapsamlı tarayıcı kimliği. Raw kimlik
veritabanında saklanmaz; kurulumun gizli anahtarıyla HMAC olarak tutulur. Aynı
kurulumdaki siteler ortak odada aynı hash sinyalini karşılaştırabilir. Başka
kurulum aynı kimlikten farklı hash üretir. Tarayıcı depolaması silinebilir,
kısıtlanabilir veya bölümlenebilir; aynı hash fiziksel kişinin kesin kanıtı,
farklı hash farklı kişinin kesin kanıtı değildir. Parmak izi için canvas, ses,
font taraması veya hassas konum toplanmaz.

Varsayılan IP sunucunun gördüğü soket eşidir. `TRUSTED_PROXY_IPS` içinde açıkça tanımlı proxy, her istekte doğrulanan tek bir `X-Real-IP` gönderebilir. Diğer eşlerden gelen başlıklar yok sayılır; `X-Forwarded-For` ve `Forwarded` zincirleri ayrıştırılmaz. Proxy bu başlığı kendi bağlantı eşinden yeniden yazmalı, backend portu özel kalmalıdır. Yapılandırılmamış proxy IP'sini oyuncu IP'si sanarak otomatik ceza vermeyin. Ayrıntılar ACCESS-SECURITY.txt içindedir.
User-Agent/client hint ve tarayıcı alanları değiştirilebilir risk sinyalleridir.
Endpoint tam oyuncu oturumu + izinli Origin ister; okunur yayın biletiyle yazılmaz.
Oyuncu yalnız üyesi olduğu odanın bağlamını ekleyebilir; diğer oda ID'si genel
bağlama indirgenir. Dakikada oyuncu başına 30 istek sınırı vardır. Cihaz/ortam
kayıtları 90 günlük tutulur; eski temel IP bağlantı günlüğü mevcut yönetim
geçmişidir ve bu cihaz temizliğinden ayrı kalır. Hukuki saklama ve erişim
politikasını kendi dağıtımınız için belirleyin. Bu kayıtlar risk incelemesi içindir,
oyun sonucunu veya cüzdan işlemini değiştirmez.
