# API پلتفرم، قدم‌به‌قدم

> همه‌ی endpointهای پلتفرم به ترتیبی که استفاده‌شان می‌کنید: چه کاری می‌کند، کِی صدایش بزنید، چه بفرستید و چه برمی‌گردد. بدون کد؛ کد آماده در بخش «نمونه کد» است.

## پیش از شروع
> [!IMPORTANT]
> توکن‌های کاربر در حال تغییر به **توکن دسترسی ۱۵ دقیقه‌ای، توکن تمدید روی سرور شما و کد اتصال یک‌بارمصرف** هستند. قدم‌های زیر در دوره‌ی مهاجرت همچنان کار می‌کنند. چه چیزی را تا کی عوض کنید: [مهاجرت توکن](token-migration.md).

| | |
| --- | --- |
| **آدرس پایه** `{api}` | توسعه: `https://platform-dev.karizmastudios.org/api`<br>اصلی: `https://platform.karizmastudio.com/api` |
| **قالب** | بدنه‌ی درخواست و پاسخ JSON است (`Content-Type: application/json`) و نام فیلدها camelCase |
| **چه کسی صدا می‌زند** | **سرور شما**، به جز قدم ۶ (`user/get-coin`) که کلاینت بازیکن صدا می‌زند |
| **احراز هویت** | قدم‌های ۲ تا ۵ هدر `Authorization: Bearer <توکن کلاینت>` از قدم ۱ را می‌خواهند؛ قدم ۶ **توکن کاربر** بازیکن را |

کل ترتیب برای یک بازیکن جدید:

```mermaid
sequenceDiagram
  autonumber
  participant S as Your server
  participant K as Platform API
  participant C as Player's client
  S->>K: client/auth (client id + secret)
  K-->>S: client token
  S->>K: client/create-user (device id)
  K-->>S: userId + user token
  S-->>C: user token
  C->>K: user/get-coin (user token)
  K-->>C: balance
  Note over C: the player signs in on a Baziness page → the page gives the client a token
  C->>S: that token
  S->>K: client/validate-user-token
  K-->>S: userId (who signed in)
  S->>K: client/add-coin (when the player wins)
  K-->>S: new balance
```

## قدم ۱ — گرفتن توکن کلاینت
**چه کار می‌کند:** **شناسه‌ی کلاینت** و **کلید محرمانه** را با یک **توکن کلاینت** عوض می‌کند. همه‌ی درخواست‌های دیگر سرور (قدم‌های ۲ تا ۵) این توکن را در هدر `Authorization` می‌فرستند.

**کِی صدایش بزنید:** با اولین درخواست به پلتفرم بعد از بالا آمدن سرور، و دوباره هر وقت درخواستی پاسخ **401** داد، چون توکن منقضی شده است.

```http
POST {api}/client/auth
Content-Type: application/json

{ "clientId": 12, "clientSecret": "••••••••••••" }

→ 200 OK
{ "clientId": 12, "token": "eyJhbGciOiJIUzI1NiIs…" }
```

**قانون‌ها**

- توکن را **در حافظه‌ی سرور** نگه دارید و برای همه‌ی درخواست‌ها از همان استفاده کنید؛ برای هر درخواست توکن تازه نگیرید.
- اگر درخواستی پاسخ `401 Unauthorized` داد، دوباره `client/auth` را صدا بزنید و **همان درخواست را یک بار دیگر** بفرستید.
- توکن کلاینت به اندازه‌ی کلید محرمانه حساس است: هرگز آن را به کلاینت نفرستید.

## قدم ۲ — ساختن کاربر بازینس برای بازیکن
**چه کار می‌کند:** برای یکی از بازیکنان شما یک کاربر بازینس می‌سازد و این‌ها را برمی‌گرداند:

- **`userId`** بازینس: آن را روی رکورد بازیکن ذخیره کنید؛ برای همه‌ی درخواست‌های بعدی لازم است
- یک **توکن کاربر** برای این بازیکن: آن را به کلاینت بفرستید
- **`isLinkedToAccount`**: آیا این کاربر با شماره موبایل وارد شده (`true`) یا هنوز ناشناس است (`false`)

**کِی صدایش بزنید:** اولین باری که بازیکن شما به بازینس نیاز دارد؛ معمولاً اولین اجرای بازی وقتی پلتفرم روشن است. اگر از قبل `userId` بازیکن را دارید، به جای این قدم ۳ را صدا بزنید.

```http
POST {api}/client/create-user
Authorization: Bearer <client token>
Content-Type: application/json

{ "deviceId": "8f0c2b7e-…", "platform": "Web", "store": "Direct", "linkedToken": null }

→ 200 OK
{ "userId": 239236, "token": "eyJhbGciOiJIUzI1NiIs…", "isLinkedToAccount": false }
```

| فیلد | مقدار |
| --- | --- |
| `deviceId` | شناسه‌ی ثابت بازیکن شما، مثلاً شناسه‌ی دستگاهی که بازی در اولین اجرا ساخته |
| `platform` | `Web`، `Android`، `Ios` یا `Editor` |
| `store` | `Direct` (وب)، `GooglePlay`، `AppStore`، `CafeBazaar` یا `Myket` |
| `linkedToken` | توکن کاربری که بازیکن از قبل دارد، یا `null`. اپ بازینس بازی‌ها را با `?linkedToken=…` باز می‌کند؛ فرستادن آن در این‌جا بازیکن را به **همان** حساب بازینس وصل می‌کند |

> [!IMPORTANT]
> اگر `userId` برگشتی **از قبل روی بازیکن دیگری از بازیکنان شما ذخیره شده**، این همان شخص است، مثلاً روی گوشی جدید. آن را دوباره ذخیره نکنید؛ به جایش این دستگاه را به آن بازیکن جابه‌جا کنید ([ورود ← جابه‌جایی حساب](login.md#account-switching-step-by-step)). روی ستونی که `userId` را نگه می‌دارد محدودیت **یکتا** بگذارید.

## قدم ۳ — گرفتن توکن تازه برای کاربر
**چه کار می‌کند:** برای کاربر بازینسی که از قبل می‌شناسید یک توکن تازه و مقدار فعلی `isLinkedToAccount` را برمی‌گرداند.

**کِی صدایش بزنید:** در هر جلسه‌ی بعدی بازیکن، معمولاً هر بار که کلاینت بالا می‌آید. به این ترتیب کلاینت همیشه توکن معتبر دارد، و شما می‌فهمید کِی یک کاربر ناشناس وارد حسابش شده (`isLinkedToAccount` شده `true`).

```http
GET {api}/client/get-user-token/239236
Authorization: Bearer <client token>

→ 200 OK
{ "userId": 239236, "token": "eyJhbGciOiJIUzI1NiIs…", "isLinkedToAccount": true }
```

## قدم ۴ — بررسی توکنی که کلاینت فرستاده
**چه کار می‌کند:** می‌گوید یک توکن کاربر واقعی است یا نه، و **مال کدام کاربر بازینس است**.

**کِی صدایش بزنید:** هر وقت توکنی از کلاینت رسید و می‌خواهید بر اساسش کاری بکنید. مورد اصلی: بازیکن در صفحه‌ی ورود بازینس وارد می‌شود، صفحه یک توکن به کلاینت می‌دهد و کلاینت آن را به سرور می‌فرستد. **هیچ‌وقت بر اساس توکنی که بررسی نکرده‌اید حسابی وصل نکنید یا چیزی ندهید.**

```http
GET {api}/client/validate-user-token/eyJhbGciOiJIUzI1NiIs…
Authorization: Bearer <client token>

→ 200 OK
{ "userId": 239236, "isValid": true }

→ 200 OK   (an invalid or expired token is NOT an HTTP error)
{ "userId": 0, "isValid": false }
```

> [!WARNING]
> توکن نامعتبر خطای HTTP **نیست**: پاسخ `200` با `"isValid": false` و `"userId": 0` است. همیشه `isValid` را بررسی کنید.

## قدم ۵ — دادن گیتا کوین
**چه کار می‌کند:** به موجودی بازینس بازیکن گیتا کوین اضافه می‌کند و موجودی جدید را برمی‌گرداند.

**کِی صدایش بزنید:** وقتی بازیکن در بازی شما سکه به دست می‌آورد؛ مثلاً با بردن یک مسابقه، رتبه‌ی تورنمنت یا باز کردن هدیه.

```http
POST {api}/client/add-coin
Authorization: Bearer <client token>
Content-Type: application/json

{
  "userId": 239236,
  "amount": 50,
  "transactionId": "239236_50_9f2c1a7d0b4e8c3f5a6d2e1b",
  "metaData": { "source": "match_win", "matchId": 17 }
}

→ 200 OK
{ "resourceId": 1, "amount": 2450 }          ← the player's new balance

→ 400 Bad Request   (this transactionId was already credited)
{ "success": false, "error": "add resource failed" }
```

| فیلد | مقدار |
| --- | --- |
| `userId` | شناسه‌ی کاربر بازینس از قدم ۲ |
| `amount` | تعداد سکه (مثبت) |
| `transactionId` | یک **شناسه‌ی یکتا برای همین پرداخت** (پایین را ببینید). پلتفرم هر شناسه را فقط یک بار واریز می‌کند |
| `metaData` | هر JSON که می‌خواهید کنار تراکنش ذخیره شود (منبع، شناسه‌ی مسابقه و …)، برای گزارش‌ها |

**امن کردن `transactionId`:** آن را از **ماهیت پرداخت** بسازید، نه از زمان:

```text
transactionId = <userId>_<amount>_<first 24 hex chars of sha1(grantKey)>
grantKey      = "match:17" | "reward_claim:42" | "payment:88:GITA_COIN" | "tutorial_gift:<userId>"
```

- یک پرداخت همیشه یک شناسه‌ی ثابت می‌سازد. تلاش دوباره، دو بار زدن دکمه یا درخواست تکراری با **`400`** رد می‌شود و هیچ‌وقت دو بار پرداخت نمی‌شود.
- **این `400` را به معنی «قبلاً پرداخت شده» بگیرید** و حساب‌داری خودتان را تمام کنید، مثلاً جایزه را دریافت‌شده علامت بزنید. وگرنه کرشی بین واریز و ذخیره‌ی شما بازیکن را پشت یک خطا گیر می‌اندازد.
- شناسه‌ای که از ساعت ساخته شود (`<userId>_<amount>_<time>`) با هر بار صدا زدن دوباره واریز می‌کند؛ فقط برای پرداخت‌هایی که واقعاً تکرارشونده‌اند.

## قدم ۶ — خواندن موجودی (کلاینت)
**چه کار می‌کند:** موجودی گیتا کوین بازیکن را برمی‌گرداند. تنها درخواست پلتفرم است که **کلاینت** با **توکن کاربر** بازیکن می‌زند. درخواست از دامنه‌های دیگر مجاز است، پس بازی مرورگری می‌تواند مستقیم صدایش بزند.

**کِی صدایش بزنید:** بعد از اینکه کلاینت توکن گرفت، وقتی یک صفحه‌ی بازینس بسته می‌شود (شاید بازیکن سکه خرج کرده باشد)، وقتی بازی دوباره به جلو می‌آید، و بعد از اینکه سرور سکه داد.

```http
GET {api}/user/get-coin
Authorization: Bearer <user token>

→ 200 OK
{ "resourceId": 1, "amount": 2450 }
```

## خطاها در یک نگاه
| پاسخ | کجا | معنی | کار شما |
| --- | --- | --- | --- |
| `401 Unauthorized` | قدم‌های ۲ تا ۵ | توکن کلاینت نیست یا منقضی شده | قدم ۱ را دوباره صدا بزنید و یک بار دیگر بفرستید |
| `200` با `"isValid": false` | قدم ۴ | توکن واقعی نیست یا منقضی شده | درخواست را رد کنید؛ کلاینت باید دوباره وارد شود |
| `400` با `"add resource failed"` | قدم ۵ | این `transactionId` قبلاً واریز شده | آن را پرداخت‌شده حساب کنید |
| خطای شبکه / `5xx` | هر جا | پلتفرم در دسترس نیست | بازی را بدون بخش‌های بازینس ادامه دهید و بعداً دوباره تلاش کنید |
