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

Ekonomi

Bu sayfa oyun içi paranın Open Play’de nasıl tutulduğunu ve hareket ettiğini anlatır: para birimi tanımı, cüzdan, defter (ledger), oyuncular arası transfer, NPC tüccar ve oyuncu takası. Oyuncuya para vermenin, harcatmanın ve ekonomiyi izlemenin güvenli yolunu arıyorsanız okuyun.

Ne işe yarar

Ekonomi iki modüle yayılır:

  • economy: para birimleri, cüzdanlar, defter, transfer ve raporlar.
  • trading: NPC tüccar (merchants) ve oyuncular arası takas (trades).

Her para hareketi çift kayıtlıdır (double-entry): bir cüzdandan çıkan tutar başka bir cüzdana ya da bir sistem hesabına girer ve ikisi aynı işlem kimliğiyle (tx_id) defterde durur. Defter yalnızca eklenir; hiçbir satır güncellenmez ya da silinmez. Hata düzeltmek için ters kayıt yazılır.

Modül kapalıysa uçlar 404 ve {"code":"module_disabled","module":"economy"} (ya da trading) döner.

Panelde nerede

Ne yapacaksınızPanel sayfası
Para birimi oluşturmak, ikon, kuruş hanesi ve arz tavanı ayarlamakPara birimleri, Para birimi profili
Günlük özet, bakiye sıralaması, takaslar ve cüzdan sorgusu görmekEkonomi raporları
Oyuncunun cüzdanını ve hareketlerini görmekOyuncu profili
Ödül tablosu kurmakÖdül tabloları
Ürünü paraya bağlayıp satmakMağaza

Veri modeli (düz dille)

  • Para birimi (currency): Anahtarı (gold), adı, transferable (oyuncular arası transfer serbest mi), max_supply (toplam arz tavanı; yoksa sınırsız) ve ikonu vardır. Aynı anahtarla yeniden kayıt yeni sürüm üretir; eski sürümler değişmez. Kuruş hanesi (display_decimals) ve ikon görseli panelden ayarlanır; REST kaydı onları 0/boş bırakır.
  • Cüzdan (wallet): Bir sahibin bir para birimindeki bakiyesi. Sahip <tür>:<uuid> biçimindedir (player, character, clan, system). Cüzdan yazma ucu yoktur; cüzdan ilk hareketle kendiliğinden doğar ve doğduğu para birimi sürümüne bağlanır.
  • Bakiye: En küçük birimde tam sayı (i64). Ondalık sayı yoktur: “12,50 altın” gösterimi için panelde ayarlanan kuruş hanesini istemcide uygulayın.
  • Defter satırı: direction (giriş/çıkış), amount, reason (transfer, grant, burn, merchant_purchase, merchant_sale, iap, iap_revoke, party_reward, clan_treasury, quest, login, npc_reward, reward_table, trade_lock, trade_refund, trade_settle …) ve reference (bağlam).
  • Tüccar (merchant): Anahtarı, adı ve eşya başına alış/satış fiyat geçersiz kılmaları (price_overrides) olan NPC. Eşyanın kendi npc_buy_price/npc_sell_price değeri varsayılandır.
  • Takas (trade): İki oyuncu arasında durum makinesi: draft → offered → locked → completed ya da cancelled/expired.

Oyuncuya para nasıl verilir

Doğrudan “cüzdana şu kadar ekle” REST ucu yoktur. Para şu yollarla oyuncuya girer:

YolNe zaman
Ödül tablosu verme (POST /v1/reward-tables/{key}/grant)Görev, turnuva, giriş ödülü gibi oyunun verdiği paralar — bkz. Ödüller
Günlük giriş ödülü, görev, NPC ödülüÖdüller
Uygulama içi satın alma (IAP)Gerçek parayla alınan oyun parası — bkz. Mağaza
Tüccara eşya satmak (POST /v1/merchants/{key}/sell)Oyuncu eşyayı NPC’ye satar
Parti ödül dağıtımı, klan hazinesiSosyal
Panelden elle vermeOperatör; işlem denetim izine yazılır

Her biri defterde kendi reason değeriyle görünür.

Oyunun yaptığı çağrılar

Uçlar Authorization: Bearer crx_live_<önek>_<gizli> ile çağrılır. Para birimi ve tüccar yönetimi sunucu anahtarı ister (403); okuma ve oyuncu işlemleri herhangi bir API anahtarıyla yapılabilir.

Yöntem ve yolAnahtarNe yapar
POST /v1/currenciessunucuPara birimini yeni sürüm olarak kaydeder
GET /v1/wallets?owner=player:<uuid>API anahtarıSahibin cüzdanları (para birimi başına bir satır)
POST /v1/transfersAPI anahtarıCüzdandan cüzdana transfer (Idempotency-Key zorunlu)
GET /v1/ledger?wallet_id=<uuid>API anahtarıCüzdanın defter akışı (cursor, limit)
POST /v1/merchantssunucuTüccarı yeni sürüm olarak kaydeder
GET /v1/merchants/{key}API anahtarıTüccarın güncel içeriği
POST /v1/merchants/{key}/buyAPI anahtarıOyuncu tüccardan eşya alır
POST /v1/merchants/{key}/sellAPI anahtarıOyuncu tüccara eşya satar
POST /v1/tradesAPI anahtarıTakas taslağı açar
GET /v1/trades/{id}?actor_id=API anahtarıTakası görüntüler (yalnız iki taraf)
POST /v1/trades/{id}/offerAPI anahtarıBacakları kilitler
POST /v1/trades/{id}/acceptAPI anahtarıSon onay; atomik takas
POST /v1/trades/{id}/cancelAPI anahtarıİptal; kilitler açılır, para iade edilir
GET /v1/economy/faucet-sink, /flags, /merchant-gap, /top-holderssunucuGünlük ekonomi raporları
GET /v1/auth/admin/player-wallet-movements?player_id=sunucuOyuncunun son cüzdan hareketleri (limit 1–100, varsayılan 20)

Not

TypeScript SDK’sında bu modüller için tipli istemci yoktur; ham REST ile çağrılır. Bkz. TypeScript SDK.

Uyarı

Bilinen kısıt: Transfer, tüccar alım/satımı ve takas uçları anahtar türüne bakmaz; geçerli bir istemci anahtarı da çağırabilir ve cüzdan/oyuncu kimliği istekte serbestçe verilir. İstemci anahtarı ortam kapsamlıdır, oyuncu kapsamlı değildir. Para hareketlerini yalnız kendi oyun sunucunuzdan yapın ve oyuncudan gelen kimliği orada doğrulayın.

Para birimi kaydı

POST /v1/currencies
Authorization: Bearer crx_live_<önek>_<gizli>
Content-Type: application/json

{ "key": "gold", "name": "Altın", "transferable": true, "max_supply": 1000000000 }
{ "id": "0192f0aa-1000-7000-8000-000000000001", "key": "gold", "version": 1, "name": "Altın", "transferable": true, "max_supply": 1000000000, "icon": null }

key ve name boş olamaz; max_supply verilirse 0’dan büyük olmalıdır (400). transferable: false olan para birimi oyuncular arası transferde 422 döner (oyun içi harcamalar yine çalışır).

Transfer

POST /v1/transfers
Authorization: Bearer crx_live_<önek>_<gizli>
Content-Type: application/json
Idempotency-Key: transfer-2026-11-03-0001

{ "from_wallet": "0192f1…", "to_wallet": "0192f2…", "amount": 500, "reference": { "neden": "hediye" } }
{ "tx_id": "0192f3…", "from": { "id": "0192f1…", "balance": 1500, "version": 7 }, "to": { "id": "0192f2…", "balance": 500, "version": 3 }, "replayed": false }

(Yanıttaki from ve to cüzdan kayıtlarının tam alanları yer alır; yukarıda kısaltılmıştır.) Reddedilen durumlar: tutar 0 ya da eksi 400; para birimleri farklı 400; cüzdan yok 404; yetersiz bakiye, transferable: false ya da aynı anahtar farklı gövde 422; aynı anahtar uçuşta 409.

Tüccar alım ve satımı

POST /v1/merchants/blacksmith/buy
Authorization: Bearer crx_live_<önek>_<gizli>
Content-Type: application/json
Idempotency-Key: buy-oyuncu-1234-17

{ "player_id": "0192f0f2-…", "item_key": "iron_sword", "currency_key": "gold", "quantity": 1 }

Yanıt: { "event": {…}, "wallet_balance": 900, "wallet_version": 8, "inventory_id": "…", "inventory_version": 5, "replayed": false }.

Alım tek işlemde cüzdandan düşer, eşyayı yeni örnek olarak üretir, ilk boş yuvaya yerleştirir ve stoğu azaltır. Fiyat tanımsızsa, bakiye yetersizse ya da envanter doluysa 422; stok yetersizse 409. Satışta (sell) gövde { "player_id", "item_instance_id", "currency_key" } taşır ve yığının tamamı satılır; kısmi satış için önce split uygulayın. Her ikisinde Idempotency-Key zorunludur.

Oyuncu takası

  1. POST /v1/trades — { "initiator_player_id", "counterparty_player_id", "ttl_ms" } ile taslak açın (ttl_ms > 0; süre dolunca takas expired olur).
  2. İki taraf da POST /v1/trades/{id}/offer ile bacaklarını kilitler: { "actor_id", "legs": [ {"type":"item","item_instance_id":"…"}, {"type":"currency","currency_id":"…","amount":100} ] }. Eşya yerleşik, takas edilebilir ve başka bir takasta kilitli olmamalıdır (kilitliyse 409); para escrow’ya (emanet hesabına) alınır.
  3. Her iki taraf kilitleyince takas locked olur. POST /v1/trades/{id}/accept ({ "actor_id" }) yalnız başlatan oyuncuyla çalışır ve tek işlemde bütün bacakları takas eder. Alıcının envanterinde yer yoksa 422 döner ve takas kilitli kalır.
  4. POST /v1/trades/{id}/cancel iki taraftan biriyle çağrılabilir; kilitler açılır ve para iade edilir.

Taraf olmayan biri GET /v1/trades/{id} çağırırsa 404 alır (takasın varlığı gizlenir); actor_id sorgu parametresi zorunludur.

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

  • transfers, merchants/{key}/buy|sell, trades komutlarının hepsinde Idempotency-Key zorunludur (yoksa 400). Aynı anahtar ve aynı gövde işlemi tekrarlamaz, replayed: true döner; farklı gövde 422. Anahtar yalnız başarıda yanar.
  • Para birimi ve tüccar kaydı her seferinde yeni sürüm yazar; eski sürümler dokunulmazdır.
  • Defter okuması imleçlidir (limit 1–200, varsayılan 50).

Raporlar

Rapor uçları (faucet-sink, flags, merchant-gap, top-holders) UTC’de kapanmış günler üzerinden hesaplanır: from ve to dahildir ve en çok 366 gün aralık verilebilir. Eksik günler yanıtta missing_dates olarak listelenir; sıfır sayılmaz. iap_net gerçek para değil oyun parasıdır. flags bir inceleme sinyalidir; otomatik yaptırım uygulanmaz.

Olaylar

Para hareketleri outbox’a olay yazar. Webhook filtresi olarak seçilebilen türler arasında ekonomiye özel bir tür yoktur; filtresiz kayıtlı uç noktalar bütün olayları aldığı için para olaylarını da alır, ancak bu olayların zarf biçimi belgelenmiş bir sözleşme değildir. Mağaza ve ödül olayları seçilebilir türlerdir: Webhook’lar.

Sınırlar

  • Bakiye ve tutarlar i64 en küçük birimdir; tutar 0’dan büyük olmalıdır.
  • Para birimi max_supply tavanını aşan üretim (ödül, IAP, satış) 422 döner.
  • Raporlarda en çok 366 gün.

Sık görülen hatalar

BelirtiNedenÇözüm
400 (Idempotency-Key)Başlık eksikHer yazıda anahtar gönderin
403İstemci anahtarıyla para birimi/tüccar kaydıSunucu anahtarı kullanın
404 cüzdanTransferde verilen cüzdan kimliği yokKimliği GET /v1/wallets ile okuyun; sahibin o para biriminde hiç hareketi yoksa cüzdan henüz doğmamıştır ve liste boş döner
422 yetersiz bakiyeBakiye tutardan azBakiyeyi GET /v1/wallets ile okuyun
422 transferable=falsePara birimi transfer edilemezPara biriminde transferi açın ya da başka yol kullanın
409 stokTüccar stoğu yetmiyorAdedi düşürün

Çalışan örnek: para ver, sonra ürün satın al

Sunucu oyuncuya ödül tablosundan para verir; oyuncu kendi oturumuyla mağazadan ürün alır. İlk adım sunucuda, ikinci adım oyun istemcisinde çalışır. welcome_pack tablosu panelden (Ödül tabloları) ya da POST /v1/reward-tables ile önceden kurulmuş olmalıdır.

/** Ekonomi akışı: sunucu ödül tablosundan para verir, oyuncu kendi oturumuyla mağazadan ürün alır. */
import { CrxClient, StoreBuyErrorCode, storeBuyErrorCode, type PlayerClient } from "@ferdiunal/open-play-ts";

/** 1. adım (güvenilir sunucu): oyuncuya tablodan ödül verir. Aynı `requestKey` ile tekrar ödeme yapmaz. */
export async function grantWelcomeReward(baseUrl: string, serverApiKey: string, playerId: string, requestKey: string): Promise<void> {
  const crx = new CrxClient({ baseUrl, apiKey: serverApiKey });
  const grant = await crx.grantRewardTable("welcome_pack", {
    playerId,
    source: { type: "manual", operatorId: "game-server" },
    idempotencyKey: requestKey,
  });
  // `rewards` verilen birleşik ödüllerdir; `replayed: true` ikinci çağrının aynı sonucu geri verdiği anlamına gelir.
  console.log(grant.rewards, grant.replayed);
}

/** 2. adım (oyun istemcisi): oyuncunun gördüğü ilk ürünü satın alır. */
export async function buyFirstListing(player: PlayerClient, pageKey: string, idempotencyKey: string): Promise<string> {
  const catalog = await player.store.catalog({ page: pageKey });
  const listing = catalog.pages[0]?.listings[0];
  if (listing === undefined || listing.version === null) return "Vitrinde ürün yok.";
  try {
    // Gördüğünüz sürümü geri gönderin: fiyat arada değiştiyse sunucu satmaz (409 listing_changed).
    const result = await player.store.buy(listing.id, { expectedVersion: listing.version, quantity: 1, idempotencyKey });
    return `Satın alındı. Kalan bakiye: ${result.balanceAfter}`;
  } catch (error: unknown) {
    switch (storeBuyErrorCode(error)) {
      case StoreBuyErrorCode.InsufficientFunds: return "Bakiye yetmiyor.";
      case StoreBuyErrorCode.OutOfStock: return "Stok tükendi.";
      case StoreBuyErrorCode.ListingChanged: return "Ürün değişti; mağazayı yenileyin.";
      default: throw error;
    }
  }
}

İlgili sayfalar