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ız | Panel sayfası |
|---|---|
| Para birimi oluşturmak, ikon, kuruş hanesi ve arz tavanı ayarlamak | Para birimleri, Para birimi profili |
| Günlük özet, bakiye sıralaması, takaslar ve cüzdan sorgusu görmek | Ekonomi raporları |
| Oyuncunun cüzdanını ve hareketlerini görmek | Oyuncu profili |
| Ödül tablosu kurmak | Ödül tabloları |
| Ürünü paraya bağlayıp satmak | Mağ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…) vereference(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 kendinpc_buy_price/npc_sell_pricedeğeri varsayılandır. - Takas (trade): İki oyuncu arasında durum makinesi:
draft→offered→locked→completedya dacancelled/expired.
Oyuncuya para nasıl verilir
Doğrudan “cüzdana şu kadar ekle” REST ucu yoktur. Para şu yollarla oyuncuya girer:
| Yol | Ne 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 hazinesi | Sosyal |
| Panelden elle verme | Operatö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 yol | Anahtar | Ne yapar |
|---|---|---|
POST /v1/currencies | sunucu | Para 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/transfers | API 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/merchants | sunucu | Tü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}/buy | API anahtarı | Oyuncu tüccardan eşya alır |
POST /v1/merchants/{key}/sell | API anahtarı | Oyuncu tüccara eşya satar |
POST /v1/trades | API 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}/offer | API anahtarı | Bacakları kilitler |
POST /v1/trades/{id}/accept | API anahtarı | Son onay; atomik takas |
POST /v1/trades/{id}/cancel | API anahtarı | İptal; kilitler açılır, para iade edilir |
GET /v1/economy/faucet-sink, /flags, /merchant-gap, /top-holders | sunucu | Günlük ekonomi raporları |
GET /v1/auth/admin/player-wallet-movements?player_id= | sunucu | Oyuncunun 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ı
POST /v1/trades—{ "initiator_player_id", "counterparty_player_id", "ttl_ms" }ile taslak açın (ttl_ms> 0; süre dolunca takasexpiredolur).- İki taraf da
POST /v1/trades/{id}/offerile 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 (kilitliyse409); para escrow’ya (emanet hesabına) alınır. - Her iki taraf kilitleyince takas
lockedolur.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 yoksa422döner ve takas kilitli kalır. POST /v1/trades/{id}/canceliki 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,tradeskomutlarının hepsindeIdempotency-Keyzorunludur (yoksa400). Aynı anahtar ve aynı gövde işlemi tekrarlamaz,replayed: truedöner; farklı gövde422. 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 (
limit1–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
i64en küçük birimdir; tutar 0’dan büyük olmalıdır. - Para birimi
max_supplytavanını aşan üretim (ödül, IAP, satış)422döner. - Raporlarda en çok 366 gün.
Sık görülen hatalar
| Belirti | Neden | Çözüm |
|---|---|---|
400 (Idempotency-Key) | Başlık eksik | Her yazıda anahtar gönderin |
403 | İstemci anahtarıyla para birimi/tüccar kaydı | Sunucu anahtarı kullanın |
404 cüzdan | Transferde verilen cüzdan kimliği yok | Kimliğ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 bakiye | Bakiye tutardan az | Bakiyeyi GET /v1/wallets ile okuyun |
422 transferable=false | Para birimi transfer edilemez | Para biriminde transferi açın ya da başka yol kullanın |
409 stok | Tüccar stoğu yetmiyor | Adedi 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;
}
}
}