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ız | Panel sayfası |
|---|---|
| Vitrin ve ürün oluşturmak, fiyat, stok, görünürlük penceresi ve oyuncu limiti ayarlamak | Mağaza, Ürün |
| Fiyat için para birimi tanımlamak | Para 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_orderve 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 daiap(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 CELvisibility_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ındaIf-Matchbu sürümdür. - Ürün sürümü: Oyuncunun gördüğü ürünün
versionalanı; satın almadaexpected_versionolarak geri gönderilir. - Rezervasyon: Durumu
reserved,processing,completed,cancelled.reservedveprocessingö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) veOrigindenetimi 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 yol | Kimlik | Ne yapar |
|---|---|---|
GET /v1/store/pages[?page=<key>&at=now] | oyuncu oturumu | Oyuncunun görebildiği vitrinler; page tek vitrine daraltır |
POST /v1/player/store/listings/{listing_id}/buy | oyuncu oturumu | Oyuncu kendi adına satın alır |
GET /v1/store/catalog | sunucu 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/reorder | sunucu anahtarı | Vitrin yönetimi |
POST /v1/store/pages/{page_id}/listings, PUT/DELETE /v1/store/listings/{id}, POST …/listings/reorder | sunucu anahtarı | Ürün yönetimi |
POST /v1/store/listings/{listing_id}/reserve | sunucu anahtarı | Oyuncu için hak ayırır (ödeme değildir) |
POST /v1/store/reservations/{id}/checkout/currency | sunucu anahtarı | Oyun parasıyla ödemeyi tamamlar |
POST /v1/store/reservations/{id}/checkout/iap | sunucu anahtarı | Doğrulanmış IAP ile ödemeyi tamamlar |
POST /v1/store/reservations/{id}/cancel | sunucu anahtarı | reserved rezervasyonu iptal eder (iade değildir) |
POST /v1/iap/verify | API 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": "..."}:
code | HTTP | Anlamı |
|---|---|---|
not_visible | 404 | Ürün bu oyuncuya görünmüyor ya da yok |
listing_changed | 409 | Ürün siz bakarken değişti (expected_version bayat); vitrini yenileyin |
out_of_stock | 422 | Stok bitti |
limit_reached | 422 | Oyuncu başına limit doldu |
insufficient_funds | 422 | Bakiye yetersiz |
inventory_full | 422 | Envanter 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_storeya dagoogle_play.credential: App Store içintransactionId, Google Play içinpurchaseToken. Sunucu platformu kendisi sorgular; istemciye güvenilmez.- Aynı işlemi iki kez doğrulamak ikinci ödül vermez (
200,replayed: true). statusverified,granted,revokedya dafailedolabilir; mutlu yolgranted’dır.- Hatalar: sahte makbuz
403; ürün katalogda yok ya da işlem platformda tanınmıyor404;product_skuuyuşmazlığı, platform yapılandırılmamış ya da arz tavanı aşımı422; aynı anahtarla uçuşta başka istek409. - İ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 makbuzrevokedolur; kısmen geri alınabilen durumlarflaggedolarak işaretlenir (varsayılan politika, tüketilmiş kısma dokunmaz). İade dışı bildirimler ve bilinmeyen işlemler200alı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-Keyzorunlu,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/iapya daiap/verifyile 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/verifyvecheckout/iap422dö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
| Belirti | Neden | Çözüm |
|---|---|---|
409 listing_changed | Vitrinde okuduğunuz ürün sürümü eskidi | Vitrini 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_funds | Bakiye yetersiz | Bakiye gösterin; ödül ya da IAP ile para verin |
400 (bilinmeyen alan) | Satın alma gövdesine fazladan alan eklendi | Yalnız quantity ve expected_version gönderin |
400 (checkout) | Idempotency-Key/If-Match gönderildi | Checkout uçlarında bu başlıkları kaldırın |
403 (satın alma) | CSRF çifti ya da Origin reddi | CSRF belirtecini yenileyin; bkz. Oturumlar ve CSRF |
403 (iap/verify) | Sahte ya da imzası geçersiz makbuz | Makbuzu platformdan yeniden alın |
422 (iap) | Platform yapılandırılmamış ya da product_sku uyuşmuyor | Operatö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.