Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Mağaza ve uygulama içi satın alma

Bu sayfa oyuncunun gördüğü mağaza vitrinini, oyun parasıyla satın alma akışını ve gerçek parayla yapılan uygulama içi satın almanın (IAP, in-app purchase) doğrulanmasını anlatır. Vitrin oluşturmak, ürün satmak ve ödemeyi sunucuda güvenle tamamlamak istiyorsanız okuyun.

Ne işe yarar

Mağaza modülü (store) üç şeyi birbirine bağlar:

  • Vitrin (page) ve ürün (listing): Operatörün panelden kurduğu, oyuncuya gösterilen katalog.
  • Ödeme: Ürün ya oyun parasıyla (currency: bir tüccar üzerinden, eşya karşılığı para) ya da gerçek parayla (iap: App Store ya da Google Play makbuzu) satılır.
  • Rezervasyon: Satın alma önce bir hak ayırır (stok ve oyuncu limiti), sonra ödemeyi tek işlemde tamamlar. Ödeme ve teslimat birlikte ya olur ya hiç olmaz.

Modül kapalıysa uçlar 404 ve {"code":"module_disabled","module":"store"} döner. Modülü Oyun ayarları › Modüller sayfasından açın.

Panelde nerede

Ne yapacaksınızPanel sayfası
Vitrin ve ürün oluşturmak, fiyat, stok, görünürlük penceresi ve oyuncu limiti ayarlamakMağaza, Ürün
Fiyat için para birimi tanımlamakPara birimleri
Satılacak eşyayı tanımlamakİçerik

Panelde IAP ürünleri ve makbuzları yalnız listelenir. Mağazaya IAP ürünü bağlamak ve platform kimlik bilgileri (App Store / Google Play) için bkz. Sınırlar.

Veri modeli (düz dille)

  • Vitrin (page): key (küçük harf, rakam, _, -; en çok 64), title, sort_order ve görünürlük. Açıklama, afiş görseli, ürün başlığı, rozet ve görsel gibi vitrin alanları panelden girilir; sunucu anahtarlı REST taslağı bunları taşımaz.
  • Ürün (listing): Bir vitrine bağlıdır. Ödeme biçimi currency (tüccar + eşya tanımı + para birimi + price_amount) ya da iap (IAP ürün kimliği) olur. limit_per_player (en az 1) ömür boyu oyuncu başına adet sınırıdır.
  • Görünürlük: active, tarih penceresi (starts_at/ends_at, Unix ms) ve isteğe bağlı bir CEL visibility_rule (oyuncuya özgü koşul; kayıtta derlenir, en çok 8192 bayt). Oyuncu vitrinini her okumada yeniden hesaplanır: koşul, saat ve kalan limit o anki duruma göre değerlendirilir.
  • Katalog sürümü (version): Vitrinde yapılan her değişiklik katalog sürümünü artırır; yönetim komutlarında If-Match bu sürümdür.
  • Ürün sürümü: Oyuncunun gördüğü ürünün version alanı; satın almada expected_version olarak geri gönderilir.
  • Rezervasyon: Durumu reserved, processing, completed, cancelled. reserved ve processing ödeme sayılmaz.
  • Fiyat: En küçük birimde tam sayı; vitrinde display_decimals (0–4) ile birlikte gelir. Gösterimi tamsayı/dize işlemiyle kurun, ondalık sayıya çevirmeyin.

Oyunun yaptığı çağrılar

İki kimlik türü vardır:

  • Oyuncu oturumu (çerez ya da oturum belirteci + x-crx-env: <ortam uuid>): vitrini okur ve kendi adına satın alır. Çerezli oturumda mutasyonlar CSRF çift gönderimi (x-csrf-token) ve Origin denetimi ister; bkz. Oturumlar ve CSRF.
  • Sunucu anahtarı (Authorization: Bearer crx_live_<önek>_<gizli>): katalog yönetimi, oyuncu önizlemesi, rezervasyon ve ödeme tamamlama.
Yöntem ve yolKimlikNe yapar
GET /v1/store/pages[?page=<key>&at=now]oyuncu oturumuOyuncunun görebildiği vitrinler; page tek vitrine daraltır
POST /v1/player/store/listings/{listing_id}/buyoyuncu oturumuOyuncu kendi adına satın alır
GET /v1/store/catalogsunucu anahtarıHam yönetim kataloğu (görünürlük süzülmez)
GET /v1/store/preview/{player_id}[?at=now]sunucu anahtarıBir oyuncunun göreceği vitrini önizler
POST /v1/store/pages, PUT/DELETE /v1/store/pages/{id}, POST /v1/store/pages/reordersunucu anahtarıVitrin yönetimi
POST /v1/store/pages/{page_id}/listings, PUT/DELETE /v1/store/listings/{id}, POST …/listings/reordersunucu anahtarıÜrün yönetimi
POST /v1/store/listings/{listing_id}/reservesunucu anahtarıOyuncu için hak ayırır (ödeme değildir)
POST /v1/store/reservations/{id}/checkout/currencysunucu anahtarıOyun parasıyla ödemeyi tamamlar
POST /v1/store/reservations/{id}/checkout/iapsunucu anahtarıDoğrulanmış IAP ile ödemeyi tamamlar
POST /v1/store/reservations/{id}/cancelsunucu anahtarıreserved rezervasyonu iptal eder (iade değildir)
POST /v1/iap/verifyAPI anahtarıIAP makbuzunu doğrular ve ödülü verir
POST /v1/webhooks/iap/{platform}/{environment_id}anahtarsız (imza)Platformun iade bildirimini alır

Hata zarfları: yönetim ve rezervasyon uçlarının hataları text/plain’dir; oyuncu satın alma ucu tipli code taşıyan JSON döner (aşağıda). Genel liste: Hatalar.

Vitrini okumak

GET /v1/store/pages?page=garaj
x-crx-env: 0192f000-0000-7000-8000-00000000e001
{
  "version": 12,
  "pages": [
    {
      "id": "…", "key": "garaj", "title": "Garaj",
      "listings": [
        {
          "id": "0192f111-…", "version": 3,
          "title": "Hızlı Araba", "badge": "sale",
          "price": { "currency_key": "gold", "currency_name": "Altın", "amount": 1250, "display_decimals": 0, "icon_url": null },
          "item": { "key": "fast_car", "name": "Hızlı Araba" },
          "remaining": 40
        }
      ]
    }
  ]
}

Yanıt Cache-Control: private, no-store taşır. Görünmeyen ya da bilinmeyen page anahtarı 404 döner. Oyuncu kimliği, saat ve CEL sonucu istemciden alınmaz; sorguya player_id ya da başka bir alan eklemek 400 döner.

Oyuncunun satın alması

POST /v1/player/store/listings/0192f111-…/buy
x-crx-env: 0192f000-0000-7000-8000-00000000e001
x-csrf-token: <csrf>
Idempotency-Key: buy-oyuncu-1234-0001
Content-Type: application/json

{ "quantity": 1, "expected_version": 3 }
{
  "reservation_id": "…",
  "payment_reference": "…",
  "item_instance_ids": ["…"],
  "balance_after": 750,
  "replayed": false
}

Gövdede yalnız quantity (pozitif) ve expected_version bulunur; oyuncu kimliği, fiyat ya da ödeme iddiası kabul edilmez ve bilinmeyen alan 400 döner. Kimlik oturumdan gelir. Rezervasyon ve tahsilat tek işlemdedir. Idempotency-Key zorunludur: aynı anahtar ve aynı gövde kalıcı sonucu replayed: true ile tekrar eder ve ikinci tahsilat yapmaz; aynı anahtarla farklı gövde 422’dir. IAP ürünü bu uçta alınamaz (422).

Reddedilen satın almalar tipli JSON kod taşır, {"code": "...", "message": "..."}:

codeHTTPAnlamı
not_visible404Ürün bu oyuncuya görünmüyor ya da yok
listing_changed409Ürün siz bakarken değişti (expected_version bayat); vitrini yenileyin
out_of_stock422Stok bitti
limit_reached422Oyuncu başına limit doldu
insufficient_funds422Bakiye yetersiz
inventory_full422Envanter dolu; işlem tamamen geri alınır

Diğer hatalar (401, 403 CSRF/Origin, 400, 429 bütçe, 500) genel kurallara tabidir.

Sunucu tarafı akış: rezervasyon ve ödeme

Ödemeyi oyun sunucusunun yönettiği durumlarda (örneğin gerçek para makbuzunu sizin sunucunuz alıyorsa) iki adım kullanın:

POST /v1/store/listings/{listing_id}/reserve
Authorization: Bearer crx_live_<önek>_<gizli>
Idempotency-Key: reserve-oyuncu-1234-0001
If-Match: 12
Content-Type: application/json

{ "player_id": "0192f0f2-…", "quantity": 1 }

Yanıt { catalog_version, entity_id, event_id, event, reservation, replayed } biçimindedir; reservation.status reserved’dır. Ardından:

POST /v1/store/reservations/{id}/checkout/currency
Authorization: Bearer crx_live_<önek>_<gizli>
Content-Type: application/json

{ "player_id": "0192f0f2-…" }
{ "reservation_id": "…", "payment_reference": "…", "kind": "currency", "replayed": false }

checkout uçlarında Idempotency-Key ve If-Match gönderilmez (gönderilirse 400); kalıcı rezervasyon kimliği tekrar güvenliğinin çapasıdır. Para birimiyle ödemede hata olursa işlem geri alınır ve rezervasyon reserved kalır; yeniden deneyebilir ya da iptal edebilirsiniz. IAP ödemesinde sonuç belirsizse rezervasyon processing olur ve oyuncu limiti korunur; aynı credential ile yeniden deneyin. IAP ödemesinde quantity 1’dir.

reserve ve katalog yönetim komutlarında Idempotency-Key zorunludur; If-Match katalog sürümüdür ve isteğe bağlıdır (uyuşmazlık 409). Başarısız komut anahtarı yakmaz.

IAP doğrulaması

POST /v1/iap/verify
Authorization: Bearer crx_live_<önek>_<gizli>
Idempotency-Key: iap-oyuncu-1234-tx-abc
Content-Type: application/json

{ "platform": "app_store", "product_sku": "gems_500", "credential": "<transactionId>", "player_id": "0192f0f2-…" }
{ "receipt_id": "…", "status": "granted", "product_sku": "gems_500", "transaction_id": "…", "grants": [ … ], "replayed": false }
  • platform: app_store ya da google_play. credential: App Store için transactionId, Google Play için purchaseToken. Sunucu platformu kendisi sorgular; istemciye güvenilmez.
  • Aynı işlemi iki kez doğrulamak ikinci ödül vermez (200, replayed: true).
  • status verified, granted, revoked ya da failed olabilir; mutlu yol granted’dır.
  • Hatalar: sahte makbuz 403; ürün katalogda yok ya da işlem platformda tanınmıyor 404; product_sku uyuşmazlığı, platform yapılandırılmamış ya da arz tavanı aşımı 422; aynı anahtarla uçuşta başka istek 409.
  • İade bildirimi: App Store (ASN V2) ve Google Play (RTDN) bildirimleri POST /v1/webhooks/iap/{platform}/{environment_id} adresine gelir; uç anahtarsızdır, kimlik doğrulaması imzadır. Bilinen bir makbuzun iadesinde ödül ters kayıtlarla geri alınır ve makbuz revoked olur; kısmen geri alınabilen durumlar flagged olarak işaretlenir (varsayılan politika, tüketilmiş kısma dokunmaz). İade dışı bildirimler ve bilinmeyen işlemler 200 alır, yok sayılır.

Sürümleme ve tekrar güvenliği

  • Oyuncu satın alma: expected_version (ürün sürümü) + Idempotency-Key.
  • Katalog yönetimi ve rezervasyon: Idempotency-Key zorunlu, If-Match = katalog sürümü.
  • Ödeme tamamlama: kalıcı rezervasyon kimliği; başlık yok.
  • Genel kurallar: Idempotency ve eşzamanlılık.

Olaylar ve webhook’lar

Mağaza olayları webhook olarak gönderilebilir: store_page_created, store_page_updated, store_page_deleted, store_pages_reordered, store_listing_created, store_listing_updated, store_listing_deleted, store_listings_reordered, store_purchase_initiated, store_payment_started, store_purchase_completed, store_reservation_cancelled. Zarfta event_id, environment_id, type, seq, now_ms, actor_api_key_id ve data bulunur. Kayıt ve doğrulama: Webhook’lar.

Sınırlar ve bilinen kısıtlar

  • Vitrin anahtarı ^[a-z0-9_-]{1,64}$; sıralama isteğinde en çok 2000 giriş.
  • Oyuncu satın alma ucunda IAP ürünü desteklenmez; IAP ürünleri checkout/iap ya da iap/verify ile alınır.
  • IAP ürün kaydı: Panelde IAP ürünleri ve makbuzları salt okunurdur ve ürün kaydetmek için herkese açık bir HTTP ucu henüz yoktur. IAP ürünü eklenmesi şimdilik Open Play operatörü üzerinden yapılır.
  • Platform kimlik bilgileri: App Store ve Google Play doğrulama kimlik bilgileri dağıtımda operatör tarafından tanımlanır. Tanımlı değilse iap/verify ve checkout/iap 422 döner.
  • Vitrini oyuncu açtığında ya da yenilediğinde okuyun; değişiklikler okuma anında yeniden hesaplanır.

Sık görülen hatalar

BelirtiNedenÇözüm
409 listing_changedVitrinde okuduğunuz ürün sürümü eskidiVitrini yeniden okuyun, yeni version ile deneyin
404 not_visibleÜrün oyuncuya görünmüyor (pencere, koşul, pasif)Vitrini yenileyin; koşulu panelde kontrol edin
422 insufficient_fundsBakiye yetersizBakiye gösterin; ödül ya da IAP ile para verin
400 (bilinmeyen alan)Satın alma gövdesine fazladan alan eklendiYalnız quantity ve expected_version gönderin
400 (checkout)Idempotency-Key/If-Match gönderildiCheckout uçlarında bu başlıkları kaldırın
403 (satın alma)CSRF çifti ya da Origin reddiCSRF belirtecini yenileyin; bkz. Oturumlar ve CSRF
403 (iap/verify)Sahte ya da imzası geçersiz makbuzMakbuzu platformdan yeniden alın
422 (iap)Platform yapılandırılmamış ya da product_sku uyuşmuyorOperatörle iletişime geçin; sku’yu kontrol edin

Çalışan örnek

Vitrini okuyup ilk ürünü satın alma örneği Ekonomi sayfasında; hata kodlarına göre ayrı mesaj gösterir (storeBuyErrorCode). TypeScript SDK’sı player.store.catalog() ve player.store.buy() yardımcılarını sağlar; Idempotency-Key verilmezse çağrı başına üretir, ağ kesintisinden sonra aynı anahtarı kendiniz vermelisiniz.

İlgili sayfalar