REST API — v1

Bayiler için Oyun Yükleme ve Kupon API'si

Oyun yüklemeleri, oyun bakiyesi ve kupon kodları için tek bir REST API: kataloğu okuyun, oyuncuyu doğrulayın, siparişi oluşturun ve sonucu webhook ile ya da durum sorgusuyla alın.

Bu, SHOP2TOPUP'ın arkasındaki host-to-host (H2H) yükleme API'sidir. HTTPS üzerinden çalışan sade bir JSON API'dir; Bearer kimlik doğrulaması, UUID idempotency anahtarları, yayımlanmış hız sınırları ve kararlı, makine tarafından okunabilir hata kodları içerir. Böylece bir mağaza, bir vitrin ya da bir bot, istemci kütüphanesi olmadan programatik biçimde dijital oyun kredisi satabilir. Aynı arayüz, 5+ yıllık kesintisiz işleyiş boyunca 3.000.000+ teslim edilen siparişi taşıdı.

Fiyatlandırma, ödeme yöntemleri ve iş ortağı hesabının kendisinin nasıl çalıştığı mı ilginizi çekiyor? Şunu okuyun: SHOP2TOPUP bayi programına genel bakış.

Hiçbir şey imzalamadan önce ilk çağrınız

Her uç nokta, tek bir başlık taşıyan sıradan bir HTTPS isteğidir. Kurulacak bir SDK, projenize dahil edilecek bir istemci kütüphanesi veya eklenecek bir derleme adımı yoktur. Aşağıdaki çağrıyı yapıştırın, anahtarınızı yerleştirin; terminalinizde canlı bir katalog yanıtı belirsin.

İstek — cURL
curl -s "https://shop2topup.com/api/endpoints/v1/catalog/categories?bigCategoryId=1" \
  -H "Authorization: Bearer YOUR_KEY_ID.YOUR_KEY_SECRET"
Yanıt — 200 OK
{
  "success": true,
  "categories": [
    {
      "id": 12,
      "name": "Free Fire",
      "description": "Garena Free Fire diamonds",
      "big_category_id": 1,
      "big_category_name": "Mobile Games"
    }
  ]
}

Temel URL

https://shop2topup.com/api/endpoints/v1

Belgelenen her yol bu temel adrese görelidir. Yanıtlar JSON'dur ve her zaman boolean bir success alanı taşır; böylece istemcinizdeki tek bir dal, mutlu yolu diğer her şeyden ayırır.

Hata zarfı

Hatalar her durum kodunda aynı biçimi korur. Sözleşmenin parçası olan error.code üzerinden dallanın; insanlar için yazılan ve her an yeniden ifade edilebilecek error.message üzerinden asla dallanmayın.

Hata yanıtı
{
  "success": false,
  "error": {
    "code": "PRICE_INCREASED",
    "message": "Price has increased beyond expected",
    "details": {
      "expected_unit_price": "0.950000",
      "current_unit_price": "0.980000"
    }
  }
}

API yüzeyinin tamamı tek ekranda

On bir uç nokta entegrasyonun tamamını kapsar: kataloğu okuyun, bir kalemi fiyatlandırın, oyuncuyu kontrol edin, siparişi verin ve sonrasında mutabık kılın. Her satır; tüm parametreleri, örnek isteği ve örnek yanıtı içeren tam referansa doğrudan bağlanır.

YöntemYolNe yaparReferans
GET/accountGet Account InfoReferansı okuyun
GET/catalog/big-categoriesList Big CategoriesReferansı okuyun
GET/catalog/categoriesList CategoriesReferansı okuyun
GET/catalog/subcategoriesList Subcategories (Products)Referansı okuyun
GET/catalog/subcategory/:itemId/priceGet Item PriceReferansı okuyun
GET/catalog/category/:categoryId/requirementsGet Category RequirementsReferansı okuyun
POST/player/validateValidate PlayerReferansı okuyun
POST/orders/createCreate OrderReferansı okuyun
GET/orders/:orderIdGet Order StatusReferansı okuyun
POST/orders/batchBatch Get OrdersReferansı okuyun
GET/ordersList OrdersReferansı okuyun

Ayrıca sipariş durumu webhook'ları — bu sayfanın ilerisinde ayrıntılı olarak belgelenmiştir.

Kimlik doğrulama ve erişim denetimi

Kimlik doğrulama tek bir HTTP başlığından ibarettir. Her istek, bayi panelinizin API Access sayfasından üretilen bir anahtar kimliği ile gizli anahtardan oluşan Bearer kimlik bilgisini taşıyan bir Authorization başlığı içerir. Anahtar kimliği hesabı tanımlar; gizli anahtar sahipliğinizi kanıtlar ve sunucu tarafında bir HMAC kontrolüyle doğrulanır.

Gizli anahtar yalnızca oluşturma anında bir kez gösterilir ve sonrasında bir daha alınamaz. Onu yığınınızda zaten kullandığınız gizli yöneticisine koyun, ortam değişkeni olarak enjekte edin ve sürüm kontrolünün dışında tutun. Kaybederseniz kurtarmazsınız, yenilersiniz.

OAuth yönlendirmesi, oturum çerezi, yenileme token'ı ve zamanlanacak hiçbir şey yoktur. Bu, otomatik entegrasyonlar için önemlidir: bir cron işi veya kuyruk işçisi aynı kimlik bilgisini yıllarca yenileme yolu olmadan taşıyabilir ve gecenin üçünde uyanmanıza neden olacak bir sona erme saati yoktur.

Bir anahtara IP izin listesi eklenebilir. Liste boş olmadığı anda, başka adreslerden gelen istekler herhangi bir iş mantığı çalışmadan önce IP_NOT_ALLOWED ve HTTP 403 ile reddedilir; böylece sızmış bir kimlik bilgisi kendi sunucularınızın dışında işe yaramaz. Yenileme bir göç değil, bir dağıtımdır: yeni anahtarı oluşturun, yayına alın, eskisini silin.

Anahtarlar tek bir bayi hesabına aittir ve o hesabın cüzdanından harcar; bu yüzden gizli anahtarı asla bir tarayıcıya, bir mobil pakete veya herkese açık bir depoya göndermeyin. API'yi kendi arka ucunuzdan çağırın, kendi ön yüzünüz de sizin arka ucunuzla konuşsun. Burada belgelenen her şey, sunucudan sunucuya bir çağıran varsayar.

İstek başlığı
Authorization: Bearer <keyId>.<secret>

Bir anahtarı tek çağrıda doğrulayın

GET /account bir kimlik bilgisinin sağlık kontrolüdür. Üç soruyu aynı anda yanıtlar: anahtar kimlik doğrulamasından geçiyor mu, hesap etkin mi ve cüzdan sipariş etmek üzere olduğunuz tutarı karşılıyor mu. Açılışta ve her toplu çalıştırmadan önce çağırın.

Kimlik doğrulama ve hesap referansını okuyun

Baştan sona eksiksiz bir entegrasyon

Çalışan bir entegrasyon dört adımdır. Aşağıdaki örnekler bu adımların hepsini canlı API üzerinde cURL, Node ve PHP ile çalıştırır. Kendi kimlik bilgilerinizi yerleştirin; oldukları gibi çalışırlar.

  1. 1

    Kataloğu okuyun

    Büyük kategorilerden kategorilere, oradan alt kategorilere inin; ardından satmak üzere olduğunuz kalemin güncel fiyatını okuyun. Ağacı önbelleğe alın, fiyatı taze çekin.

  2. 2

    Oyuncuyu doğrulayın

    Para hareket etmeden önce kimliği oyun içi bir ada ve bölgeye çözümleyin. Yazım hataları ve bölgeler arası yanlışlıklar burada yakalanır.

  3. 3

    Siparişi oluşturun

    Bir UUID üretin, saklayın, sonra order_id olarak gönderin. Bu sizin idempotency anahtarınızdır ve bir oluşturma isteğini yeniden denemenin tek güvenli yoludur.

  4. 4

    Teslimatı doğrulayın

    Sipariş durumu webhook'unu bekleyin; kendi bekleme pencerenizden sonra hâlâ pending kalan her şey için bir durum okumasına başvurun.

cURL — akışın tamamıKodu göster
export S2T_KEY="YOUR_KEY_ID.YOUR_KEY_SECRET"
export S2T_BASE="https://shop2topup.com/api/endpoints/v1"

# 1. Find the item you want to sell.
curl -s "$S2T_BASE/catalog/subcategories?categoryId=12" \
  -H "Authorization: Bearer $S2T_KEY"

# 2. Read the exact price you will be charged for it.
curl -s "$S2T_BASE/catalog/subcategory/999/price" \
  -H "Authorization: Bearer $S2T_KEY"

# 3. Confirm the player exists BEFORE any money moves.
curl -s -X POST "$S2T_BASE/player/validate" \
  -H "Authorization: Bearer $S2T_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sub_category_id": 999, "player_id": "123456789", "server": "Asia"}'

# 4. Create the order. order_id is YOUR uuid and YOUR idempotency key.
curl -s -X POST "$S2T_BASE/orders/create" \
  -H "Authorization: Bearer $S2T_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "order_id": "01912345-6789-7abc-8def-0123456789ab",
    "sub_category_id": 999,
    "quantity": 1,
    "requirements": { "player_id": "123456789", "server": "Asia" },
    "expected_unit_price": "0.950000"
  }'

# 5. Read the order back until it leaves "pending".
curl -s "$S2T_BASE/orders/01912345-6789-7abc-8def-0123456789ab" \
  -H "Authorization: Bearer $S2T_KEY"
Node.js — akışın tamamıKodu göster
// Node 18+ — no dependencies, global fetch and global crypto.
const BASE = 'https://shop2topup.com/api/endpoints/v1';
const KEY = process.env.S2T_KEY; // "<keyId>.<secret>", server-side only.

async function call(path, init = {}) {
  const res = await fetch(BASE + path, {
    ...init,
    headers: {
      Authorization: `Bearer ${KEY}`,
      'Content-Type': 'application/json',
      ...(init.headers || {}),
    },
  });

  if (res.status === 429) {
    const wait = Number(res.headers.get('Retry-After') || 5);
    await new Promise((r) => setTimeout(r, wait * 1000));
    return call(path, init);
  }

  const body = await res.json();
  if (!res.ok || body.success === false) {
    throw Object.assign(new Error(body?.error?.code || 'HTTP_' + res.status), {
      code: body?.error?.code,
      status: res.status,
    });
  }
  return body;
}

async function sell({ itemId, categoryId, playerId, server }) {
  // 1 + 2. Catalog and current price.
  const { subcategories } = await call(
    `/catalog/subcategories?categoryId=${categoryId}`,
  );
  const item = subcategories.find((s) => s.item_id === itemId);
  const { price } = await call(`/catalog/subcategory/${itemId}/price`);

  // 3. Player check before charging.
  const { player } = await call('/player/validate', {
    method: 'POST',
    body: JSON.stringify({
      sub_category_id: itemId,
      player_id: playerId,
      server,
    }),
  });

  // 4. Create the order. Persist orderId BEFORE the call so a crash mid-flight
  //    can be replayed with the same uuid instead of double-charging.
  const orderId = crypto.randomUUID();
  await saveIntent(orderId, itemId, playerId);

  const { order } = await call('/orders/create', {
    method: 'POST',
    body: JSON.stringify({
      order_id: orderId,
      sub_category_id: itemId,
      quantity: 1,
      requirements: { player_id: playerId, server },
      expected_unit_price: price.unit_price,
    }),
  });

  // 5. Confirm. A webhook usually beats the poll; the poll is the safety net.
  let status = order.status;
  while (status === 'pending') {
    await new Promise((r) => setTimeout(r, 20000));
    status = (await call(`/orders/${orderId}`)).order.status;
  }

  return { orderId, status, itemName: item?.name, playerName: player.player_name };
}
PHP — akışın tamamıKodu göster
<?php
// PHP 8 — plain cURL, no SDK required.
const S2T_BASE = 'https://shop2topup.com/api/endpoints/v1';

function s2t(string $path, ?array $body = null): array
{
    $ch = curl_init(S2T_BASE . $path);
    $headers = [
        'Authorization: Bearer ' . getenv('S2T_KEY'),
        'Content-Type: application/json',
    ];

    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => $headers,
        CURLOPT_TIMEOUT        => 30,
    ]);

    if ($body !== null) {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
    }

    $raw    = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $data = json_decode($raw, true) ?: [];

    if ($status >= 400 || ($data['success'] ?? false) === false) {
        throw new RuntimeException($data['error']['code'] ?? 'HTTP_' . $status);
    }

    return $data;
}

// 1 + 2. Catalog, then the exact price for the item.
$catalog = s2t('/catalog/subcategories?categoryId=12');
$price   = s2t('/catalog/subcategory/999/price');

// 3. Player check before charging.
$player = s2t('/player/validate', [
    'sub_category_id' => 999,
    'player_id'       => '123456789',
    'server'          => 'Asia',
]);

// 4. Create the order with your own uuid as the idempotency key.
$orderId = sprintf(
    '%04x%04x-%04x-7%03x-%04x-%04x%04x%04x',
    random_int(0, 0xffff), random_int(0, 0xffff),
    random_int(0, 0xffff), random_int(0, 0x0fff),
    random_int(0x8000, 0xbfff),
    random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0xffff)
);

$order = s2t('/orders/create', [
    'order_id'            => $orderId,
    'sub_category_id'     => 999,
    'quantity'            => 1,
    'requirements'        => ['player_id' => '123456789', 'server' => 'Asia'],
    'expected_unit_price' => $price['price']['unit_price'],
]);

// 5. Read it back until it settles.
do {
    sleep(20);
    $latest = s2t('/orders/' . $orderId);
} while ($latest['order']['status'] === 'pending');

echo $latest['order']['status'];

İki ayrıntıyı birebir kopyalamaya değer: sipariş UUID'sini oluşturma çağrısından sonra değil önce saklayın ve önbellekteki bir sayıya güvenmek yerine fiyatı yeniden okuyun. Bu iki alışkanlık, neredeyse her çifte tahsilatı ve her PRICE_INCREASED reddini ortadan kaldırır.

Webhook'lar: sipariş durumu geri çağrıları

Tek bir HTTPS uç noktası kaydedin; platform sipariş sonuçlarını gerçekleştikçe oraya gönderir, böylece mağazanız sorgulamayı bırakıp tepki vermeye başlar. Geri çağrı imzalıdır; yani üzerinde işlem yapmadan önce yükün bizden geldiğini kanıtlayabilirsiniz.

Olaylar

OlayNe zaman tetiklenir
order.completedThe order finished and everything it owed the buyer was delivered.
order.failedThe order failed and no sub-transaction is left pending or processing.
order.refundedThe order was fully refunded back to your wallet.
webhook.testYou triggered a signed test delivery from the panel to check your handler.

Teslimat başlıkları

BaşlıkDeğer
Content-Typeapplication/json
X-Shop2Topup-Signaturesha256=<hex> — HMAC-SHA256 of the raw request body
X-Shop2Topup-EventEvent name, e.g. order.completed
User-Agentshop2topup-webhook/1.0

Geri çağrı yükü

Her geri çağrının aynı üç üst düzey alanı vardır — event, timestamp ve data — ve data, durum uç noktasının döndürdüğü sipariş nesnesini yansıtır. Kupon kodları yalnızca tamamlanmış bir siparişte bulunur, player_name null olabilir ve sipariş adetlere bölündüğünde sub_transaction_summary görünür.

POST gövdesi — order.completed
{
  "event": "order.completed",
  "timestamp": "2026-03-11T14:30:00.000Z",
  "data": {
    "order_id": "01912345-6789-7abc-8def-0123456789ab",
    "status": "completed",
    "player_id": "123456789",
    "player_name": "ProGamer99",
    "subcategory_name": "FF 100 Diamonds",
    "quantity": 1,
    "charged_amount": "0.950000",
    "currency": "USD",
    "created_at": "2026-03-11T14:28:00.000Z",
    "completed_at": "2026-03-11T14:30:00.000Z",
    "vouchers": [
      {
        "code": "XXXX-YYYY-ZZZZ",
        "serial_number": "SN-9928371",
        "expiry_date": "2027-03-11"
      }
    ],
    "sub_transaction_summary": {
      "total": 1,
      "completed": 1,
      "failed": 0,
      "pending": 0,
      "processing": 0,
      "retrying": 0,
      "refunded": 0
    }
  }
}

İmza doğrulama

Her teslimat, sha256= ardından webhook gizli anahtarınızla ham istek gövdesinin hex HMAC-SHA256 değeri biçiminde bir X-Shop2Topup-Signature taşır. Herhangi bir JSON ayrıştırmasından önce ham baytlar üzerinden doğrulayın ve sabit zamanda karşılaştırın. Önce ayrıştırıp sonra doğrulayan bir işleyici, yeniden serileştirilmiş bir gövdeyle kandırılabilir.

Node.js — doğrula ve onayla
// Express — verify the signature against the RAW body, not the parsed object.
const crypto = require('crypto');

app.post(
  '/shop2topup/webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.get('X-Shop2Topup-Signature') || '';
    const expected =
      'sha256=' +
      crypto
        .createHmac('sha256', process.env.S2T_WEBHOOK_SECRET)
        .update(req.body)
        .digest('hex');

    const a = Buffer.from(expected);
    const b = Buffer.from(signature);
    if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
      return res.status(401).end();
    }

    const payload = JSON.parse(req.body.toString('utf8'));

    // Answer fast, then do the work. The delivery times out after 10 seconds.
    res.status(200).end();
    enqueueSettlement(payload.data.order_id, payload.event);
  },
);

Teslimat ve yeniden denemeler

Bir geri çağrı yalnızca bir kez denenir ve uç noktanızın yanıt vermek için on saniyesi vardır. Arkasında otomatik yeniden deneme veya üstel geri çekilme yoktur; bu bilinçlidir: yetkili kayıt durum uç noktasıdır, dolayısıyla mutabakat, göremediğiniz bir yeniden deneme takvimine değil sizin kontrol ettiğiniz bir sorgulamaya aittir. Hemen 200 döndürün ve işi kendi kuyruğunuzda yapın.

Idempotent işleyiciler

İşleyicinizi order_id üzerine kurun ve bir geri çağrının yinelenmesini etkisiz hale getirin. Tek teslimat denemesinde bile kendi altyapınız zaman zaman aynı mesajı iki işçiye verecektir; bir müşteriye iki kez kredi yükleyen sipariş, geç yükleyenden çok daha kötü bir hatadır.

Kayıt ve gizli anahtarlar

Geri çağrı URL'sini kaydetmek, imzalı bir test teslimatı göndermek ve imzalama gizli anahtarını yenilemek; herkese açık API üzerinden değil, oturum açtığınız bayi panelinin içinde yapılır. HTTPS zorunludur ve özel adresler kabul edilmez.

Bayi panelinde API access'i açın

Hız sınırları ve hata yönetimi

Sınırlar yayımlanmıştır, hesap bazındadır ve kayan pencereyle uygulanır. En geniş pay, okuma ağırlıklı katalog işlerine ayrılmıştır; durum uç noktaları bilinçli olarak dardır, çünkü bir sorgulama döngüsü değil, mutabakat yedeği olarak vardır.

RotaİstekPencereSayım birimi
GET /catalog/* (all five)8060sper account
POST /orders/create12060sper account
GET /orders/:orderId360sper account + order id
POST /orders/batch260sper account
GET /orders260sper account
GET /account6060sper account
POST /player/validatededicated limiterper account

Her yanıt X-RateLimit-Limit, X-RateLimit-Remaining ve X-RateLimit-Reset taşır; böylece düzgün davranan bir istemci nerede durduğunu tahmin etmek zorunda kalmaz. remaining sayacını izleyin ve kısıtlandıktan sonra değil, öncesinde yavaşlayın.

Kısıtlanmış yanıt — 429
HTTP/1.1 429 Too Many Requests
Retry-After: 45
X-RateLimit-Limit: 80
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1765000000

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Try again in 45 seconds.",
    "details": {
      "limit": 80,
      "remaining": 0,
      "retry_after": 45,
      "window_seconds": 60
    }
  }
}

İlk karşılaşacağınız hatalar ve çözümleri

Hatalar her zaman HTTP durumunun yanında kararlı bir kod döndürür. Bunlar, yeni bir entegrasyonun ilk haftasında karşılaştıklarıdır; her biri, sorunu çözen değişiklikle birlikte verilmiştir.

KodHTTPNe anlama gelirNasıl düzeltilir
INVALID_API_KEY401Invalid API keyThe provided API key does not match any active key in the system.Verify your key ID and secret are correct. Regenerate the key from the API Access panel if needed.
IP_NOT_ALLOWED403IP address not in allowlistThe request originated from an IP address not in your API key allowlist.Add your server IP to the allowlist in the API Access panel, or remove IP restrictions.
MISSING_REQUIRED_FIELD400Missing required fieldA required field is missing from the request body.Check the endpoint documentation for required fields.
INVALID_UUID_FORMAT400Invalid UUID formatThe order_id is not a valid UUID format.Use any valid UUID format (v1, v4, v7, etc). Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
PLAYER_NOT_FOUND400Player not foundThe player ID could not be found in the game system.Verify the player ID is correct. Check if a zone_id is required for the game.
REGION_MISMATCH400Region mismatchThe player belongs to a different region than expected.Use the correct zone_id or genshin_zone for the player region.
INSUFFICIENT_BALANCE400Insufficient wallet balanceYour wallet does not have enough funds to complete this order.Top up your wallet from the Reload page before placing the order.
OUT_OF_STOCK400Product is out of stockThe requested product is currently unavailable.Try again later or choose a different product.
DUPLICATE_ORDER409Duplicate order IDAn order with this UUID already exists. This is an idempotency protection.Generate a new UUID for a new order. If retrying, use the same UUID to get the existing order.
PRICE_INCREASED400Price has increased beyond expectedThe current unit price is higher than the expected_unit_price you provided.Fetch the latest price with GET /catalog/subcategory/:id/price and update expected_unit_price.
RATE_LIMIT_EXCEEDED429Rate limit exceededYou have exceeded the rate limit for this endpoint. Every response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers. When exceeded, the 429 response body contains: limit, remaining (0), retry_after (seconds to wait), and window_seconds. A Retry-After header is also set.Wait for the number of seconds indicated by retry_after or the Retry-After header before retrying. Monitor X-RateLimit-Remaining headers to avoid hitting limits.
SERVICE_UNAVAILABLE503Service temporarily unavailableThe service is temporarily down for maintenance or overloaded.Wait and retry after a few minutes.
Tam hata kodu referansını görün — 32 hata kodunun tamamı, çözümleriyle

Entegrasyon soruları, yanıtlarıyla

Geliştiricilerin bunu kurarken gerçekten sorduğu sorular: kimlik doğrulama, yeniden denemeler, idempotency, kısıtlama, geri çağrılar ve hata yönetimi.

Bayi API'si istekleri nasıl doğruluyor?

Tek bir HTTP başlığıyla: Authorization: Bearer <keyId>.<secret>. Anahtar çifti bayi panelinizin API Access sayfasından üretilir, gizli anahtar yalnızca oluşturma anında bir kez gösterilir ve sunucu her çağrıda bunu bir HMAC kontrolüyle doğrular. OAuth yönlendirmesi, oturum çerezi veya yenilenecek bir token yoktur; aynı başlık hem ilk istekte hem de sonraki her istekte çalışır.

İstemcim hangi temel URL'yi ve sürümü hedeflemeli?

Tek bir temel URL var ve o da canlı olan: önce üzerinde geliştirip sonra değiştireceğiniz ayrı bir sandbox sunucusu yok. İstemciyi yukarıdaki v1 temel URL'sine yönlendirin, her istekte Authorization: Bearer <keyId>.<secret> gönderin ve her şeyden önce GET /account ile anahtarın ve başlık biçiminin doğruluğunu kanıtlayın. Para hareketi yaratan tek çağrı POST /orders/create olduğundan onu en sona bırakın, en küçük birim üzerinde deneyin ve istemci daha oturmamışken yanında expected_unit_price gönderin.

POST /orders/create üzerinde idempotency anahtarını hangi alan taşır?

order_id alanı — istemci tarafında ürettiğiniz ve oluşturma gövdesinde gönderdiğiniz bir UUID. Zaten var olan bir order_id ile oluşturmayı yinelerseniz API ikinci bir sipariş açmak yerine HTTP 409 ve DUPLICATE_ORDER döner; böylece yeniden deneme asla ikinci bir tahsilata dönüşmez, orijinal siparişi GET /orders/:orderId ile okursunuz. UUID'yi ilk denemeden önce üretip saklayın: zaman aşımından sonra üretilen UUID yeni bir anahtardır, yeniden deneme anahtarı değil, ve sunucu aynı şeyi kasteden iki order_id değerini sizin için birleştirmez.

İsteğim zaman aşımına uğradı. Sipariş oluştu mu?

Tahmin etmek yerine durum okuyarak öğrenin. Gönderdiğiniz UUID ile GET /orders/:orderId çağırın: sipariş geri geliyorsa zaman aşımı, sipariş kabul edildikten sonra yaşanmıştır ve yeniden göndermemelisiniz. ORDER_NOT_FOUND alıyorsanız hiçbir tahsilat yapılmamıştır ve oluşturma isteğini aynı UUID ile yinelemek güvenlidir.

Bir webhook çağrısının imzasını nasıl doğrularım?

HMAC'i ham istek gövdesi üzerinden yeniden hesaplayıp X-Shop2Topup-Signature başlığıyla karşılaştırın. Başlık sha256=<hex> taşır: aldığınız baytların tam olarak kendisini webhook gizli anahtarınızla HMAC-SHA256'dan geçirin, başına sha256= ekleyin ve sabit zamanlı karşılaştırma yapın — asla yeniden serileştirilmiş bir JSON nesnesiyle değil, çünkü ayrıştırıp yeniden yazmak baytları değiştirir ve karşılaştırma hiçbir zaman tutmaz. Tutmayan isteği 401 ile reddedin, geçerli teslimata on saniye içinde 200 dönün ve mutabakat işini sonrasında kendi kuyruğunuzda yapın.

Hız sınırına takılırsam ne olur?

RATE_LIMIT_EXCEEDED kodu ve bir Retry-After başlığıyla HTTP 429 alırsınız. Yanıt gövdesi aynı değeri retry_after alanında yineler ve limit, remaining ile window_seconds ekler. Tam olarak o kadar saniye bekleyip bir kez yeniden deneyin; hemen yeniden denemeyin ve aynı çağrıyı birkaç anahtara yaymayın, çünkü sayaç bağlantıya değil hesaba göre tutulur.

POST /player/validate hangi alanları döner?

player_name alanını, oyun sunuyorsa zone_id ve region alanlarıyla birlikte, yanıtın data nesnesi içinde döner. Sipariş oluşturma oyuncuyu kendisi çözer ve bölge kontrolünü kendisi yapar; bölge dışı bir deneme, cüzdandan herhangi bir tahsilat olmadan önce REGION_MISMATCH (HTTP 400) ile reddedilir, yani bakiyenizi koruyan şey bu çağrı değildir. Yine de çağırın: player_name değerini geri okumak, alıcıya yükleme yapacağı hesabı göstermenin ve o onaylamadan önce yanlış yazılmış bir kimliği yakalamanın tek pratik yoludur — ad her zaman oyundan gelir, alıcının yazdığından değil.

requirements yanıtını sipariş gövdesine nasıl dönüştürürüm?

field_name üzerinden eşleyin: GET /catalog/category/:categoryId/requirements yanıtındaki her kayıt, POST /orders/create isteğinde gönderdiğiniz requirements nesnesinin bir alanı olur. Her kayıt ayrıca data_type taşır; formunuz neyi çizeceğini, sunucunuz neye dönüştüreceğini böyle bilir. single_select ve multi_select kayıtları izin verilen değerleri de getirir — serbest metin kutusu yerine bunları sunun. Listeyi her sipariş yerine kategori başına önbelleğe alın ve bir oluşturma çağrısı MISSING_REQUIRED_FIELD ile dönerse yeniden okuyun.

İstemcim PRICE_INCREASED yanıtını nasıl ele almalı?

Fiyatı yeniden okuyun, karar verin, sonra yeniden gönderin — aynı gövdeyi asla tekrarlamayın. PRICE_INCREASED (HTTP 400), güncel birim fiyatın gönderdiğiniz expected_unit_price değerinin üzerinde olduğu anlamına gelir; oluşturma reddedilmiştir ve hiçbir tahsilat yapılmamıştır. Hata ayrıntıları hem expected_unit_price hem current_unit_price taşır ve eski rakamla körlemesine yeniden denemek her seferinde reddedilir. GET /catalog/subcategory/:itemId/price okuyun, yeni rakamın size hâlâ uyup uymadığına karar verin ve oluşturmayı güncellenmiş expected_unit_price ile ve aynı order_id ile yeniden gönderin — reddedilen deneme onu tüketmemiştir. expected_unit_price göndermemek korumayı tümüyle kapatır; otomatik bir entegrasyonun isteyeceği şey neredeyse hiç bu değildir.

Kısmen teslim edilen bir sipariş yanıtta nasıl görünür?

status partial olarak, ayrıntısı da sub_transaction_summary içinde görünür. Bu nesne birimleri durumlarına göre sayar — completed, refunded, failed, pending, processing, retrying — ve her sayacın arkasındaki satırı sub_transactions taşır; böylece mutabakat mantığınız tek bir kelimeden çıkarım yapmak yerine sayı okur. pending ve processing durumlarını nihai saymayın ve beklemeye devam edin; completed, partial, failed ve refunded nihai durumlardır. order.completed, order.failed ve order.refunded çağrıları da aynı özeti taşır, dolayısıyla imzası doğrulanmış bir yük ikinci bir okuma olmadan kapatılabilir.

İzin listesinde olmayan bir adresten gelen istek ne alır?

İş mantığı çalışmadan önce dönen HTTP 403 ve IP_NOT_ALLOWED kodunu alır. Kontrol, anahtarın IP izin listesi boş olmadığı sürece geçerlidir; bu da sızmış bir anahtarı kendi çıkış adresleriniz dışında işe yaramaz hale getirir. Mesaj metnine değil error.code değerine göre dallanın ve üretimde aniden gelen bir IP_NOT_ALLOWED'ı bozuk anahtar değil, değişmiş bir çıkış adresi olarak okuyun — yeni bir NAT geçidi ya da yeni bir işçi alt ağı. İzin listesini yalnızca çıkış adresiniz gerçekten sabit değilse boş bırakın.

Bir siparişin tamamlanması ne kadar sürer?

Tamamlanmayı anında değil, asenkron kabul edin. POST /orders/create, sipariş kabul edilip cüzdandan tahsilat yapılır yapılmaz genellikle pending durumuyla yanıt döner; karşılama ardından ayrı işçilerde yürür ve nihai durum size webhook ile veya bir sonraki durum okumanızda ulaşır. Müşteriye dönük bir HTTP isteğini nihai durumu bekleyerek bloklamayın — siparişi kabul edin, müşterinize yanıt verin ve mutabakatı arka planda tamamlayın.

Hesap oluşturun, anahtar üretin, yayına geçin

Kaydolmakla API'yi çağırmak arasında ne onay kuyruğu ne de bekleme süresi var. Bayi hesabını oluşturun, API Access sayfasından bir anahtar üretin, cüzdanı doldurun; ilk siparişiniz aynı gün çıkabilir.

Bu projedeki geliştirici siz değil misiniz? Ticari taraf şurada anlatılıyor: SHOP2TOPUP bayi programı sayfası.

Bayiler için Oyun Yükleme ve Kupon API'si — REST API