# پرداخت بازینس، قدم‌به‌قدم

> فروش آیتم با پول واقعی، به ترتیب: صفحه‌ی پرداخت را باز کنید، پرداخت را پیدا و تأیید کنید، آیتم را دقیقاً یک بار بدهید و خرید را مصرف کنید تا دوباره قابل خرید باشد.

## پیش از شروع
| | |
| --- | --- |
| **آدرس پایه** `{pay}` | توسعه: `https://pay-dev.karizmastudios.org`<br>اصلی: `https://pay.baziness.com` |
| **چه کسی صدا می‌زند** | **سرور شما**. کلاینت فقط صفحه‌ی پرداخت را باز می‌کند و بقیه‌ی کارها را با سرور خود شما انجام می‌دهد |
| **`pname`** | **نام بسته‌ی پرداخت** شما؛ ممکن است با نام بسته‌ی پلتفرم فرق کند |
| **`sku`** | شناسه‌ی محصولی که در داشبورد بازینس ثبت شده (مثلاً `example_pack`) |
| **`uid`** | شناسه‌ی ثابت بازیکن **در بازی شما**، و باید **عددی** باشد (به شکل رشته بفرستید، مثلاً `"1042"`). uid غیرعددی با `no_required_params` رد می‌شود |

```mermaid
sequenceDiagram
  autonumber
  participant P as Player
  participant C as Your client
  participant S as Your server
  participant Y as Baziness Pay
  P->>C: taps "Buy"
  C->>S: start a purchase
  S-->>C: payment id + payment page URL
  C->>Y: opens the payment page (new tab)
  P->>Y: pays
  P->>C: returns to the game (or taps «پرداخت کردم»)
  C->>S: verify my purchase
  S->>Y: get_payment
  Y-->>S: payment token
  S->>Y: verify_payment
  Y-->>S: success
  S->>S: give the item — exactly once
  S-->>C: done + new balances
  S->>Y: consume
```

## قدم ۱ — باز کردن صفحه‌ی پرداخت
**چه کار می‌کند:** صفحه‌ی پرداخت بازینس را برای یک محصول به بازیکن نشان می‌دهد.

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

```http
GET {pay}/buy?sku=example_pack&uid=1042&pname=com.karizmastudio.example

→ the hosted payment page (opened in a new tab / the system browser)
```

> [!TIP]
> مرورگرها تبی را که **مستقیم** با ضربه‌ی بازیکن باز نشده باشد مسدود می‌کنند. اول در همان رویداد ضربه یک تب خالی باز کنید، بعد وقتی سرور جواب داد آن را به آدرس ببرید ([کد کلاینت](samples-client.md#purchase-popup)).

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

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

```http
POST {pay}/get_payment
Content-Type: application/json

{ "uid": "1042", "sku": "example_pack", "pname": "com.karizmastudio.example" }

→ 200 OK   (paid, not consumed yet)
{ "uid": "1042", "sku": "example_pack", "token": "TEST-633…", "time": 1791366000000, "success": true }

→ 200 OK   (nothing to verify yet — the player hasn't paid)
{ "error": "payment_is_not_valid", "success": false }
```

`payment_is_not_valid` یعنی «هنوز پرداختی نیست»: بی‌صدا منتظر بمانید.

## قدم ۳ — تأیید پرداخت
**چه کار می‌کند:** از بازینس تأیید می‌گیرد که توکن پرداخت واقعی است. **این همان درخواستی است که از شما محافظت می‌کند:** هیچ‌وقت بر اساس حرف کلاینت چیزی ندهید.

```http
POST {pay}/verify_payment
Content-Type: application/x-www-form-urlencoded

uid=1042&token=TEST-633…&sku=example_pack&pname=com.karizmastudio.example

→ 200 OK
{ "success": true }
```

> [!WARNING]
> `verify_payment` بدنه را به شکل **فرم** (`application/x-www-form-urlencoded`) می‌گیرد؛ بقیه‌ی درخواست‌ها JSON. فرستادن JSON در این‌جا رایج‌ترین دلیل شکست تأیید است.

## قدم ۴ — دادن آیتم، دقیقاً یک بار
بازینس به شما می‌گوید پرداخت واقعی است؛ اینکه بازیکن آیتم را **یک بار** بگیرد کار سرور شماست. یک **جدول پرداخت‌ها** در دیتابیس نگه دارید:

| ستون | معنی |
| --- | --- |
| `id` | شناسه‌ی سفارش شما (در قدم ۱ به کلاینت برمی‌گردد) |
| `user_id`، `product_id`، `sku`، `price` | چه کسی چه چیزی می‌خرد |
| `status` | `REDIRECTED` (صفحه‌ی پرداخت باز شد) ← `FULFILLED` (آیتم داده شد)، یا `FAILED` / `CANCELLED` |
| `reward_snapshot` | دقیقاً همان چیزی که هنگام زدن «خرید» به بازیکن وعده داده شد |
| `provider_token` | توکن پرداخت از قدم ۲، با محدودیت **یکتا** |

بعد از قدم ۳ موفق، در **یک تراکنش دیتابیس**:

1. ردیف سفارش را قفل کنید و ببینید از قبل `FULFILLED` نباشد. اگر هست، بدون دادن دوباره‌ی آیتم «انجام شد» را برگردانید.
2. اگر همین توکن پرداخت قبلاً سفارش **دیگری** را تحویل داده، این یکی را `FAILED` کنید و متوقف شوید.
3. آیتم‌های `reward_snapshot` را بدهید، نه تعریف فعلی محصول را. مدیری که وسط پرداخت بازیکن یک بسته را ویرایش می‌کند نباید سفارش او را عوض کند.
4. توکن را ذخیره کنید و سفارش را `FULFILLED` کنید.

## قدم ۵ — مصرف کردن خرید
**چه کار می‌کند:** خرید را مصرف‌شده علامت می‌زند تا همان محصول دوباره قابل خرید باشد. تا وقتی مصرف نشده، قدم ۲ همچنان آن را برمی‌گرداند و خرید دوم آن SKU مسدود است.

**کِی:** درست **بعد از** ثبت نهایی قدم ۴. اگر شکست خورد چند بار دوباره تلاش کنید؛ آیتم قبلاً داده شده، پس شکست این قدم هیچ‌وقت بازیکن را معطل نمی‌کند.

```http
POST {pay}/consume
Content-Type: application/json

{ "uid": "1042", "sku": "example_pack", "pname": "com.karizmastudio.example" }

→ 200 OK
{ "success": true }
```

## چیزی که بازیکن می‌بیند
| لحظه | کلاینت شما نشان می‌دهد |
| --- | --- |
| بعد از زدن «خرید» | یک پنجره‌ی کوچک: «صفحه‌ی پرداخت بازینس باز شد، بعد از پرداخت به بازی برگرد»، با دکمه‌ی «پرداخت کردم» |
| برگشت به بازی، هنوز پرداختی پیدا نشده | چیز تازه‌ای نشان نمی‌دهد (بررسی بی‌صدا)؛ فقط زدن «پرداخت کردم» می‌گوید «پرداختی پیدا نشد، چند لحظه بعد دوباره بررسی کن» |
| پرداخت تأیید شد | «خرید موفق» و موجودی‌های به‌روز |
| پنجره پیش از پرداخت بسته شد | چیزی لغو نمی‌شود؛ دفعه‌ی بعد که فروشگاه باز شود دوباره بررسی می‌شود |

## تست
- روی **pay-dev** صفحه‌ی پرداخت گزینه‌ی **«خرید آزمایشی»** دارد: کل جریان، از قدم ۱ تا ۵، بدون پول واقعی اجرا می‌شود.
- اگر به هیچ درگاهی دسترسی ندارید، از **درگاه آزمایشی محلی** بازی نمونه استفاده کنید: همان پروتکل را دارد، با دکمه‌ی «پرداخت موفق» ([تنظیمات](samples-server.md#local-fake-gateway)).

## بیلدهای فروشگاهی (بازار / مایکت)
بیلدی که در کافه‌بازار یا مایکت منتشر می‌شود باید از **پرداخت درون‌برنامه‌ای خود آن فروشگاه** استفاده کند، نه پرداخت بازینس. نام SKUها را در هر دو یکی نگه دارید تا یک فهرست محصول برای همه‌ی بیلدها کافی باشد، و در آن بیلدها `buildStore` را روی لینک صفحه‌های بازینس بفرستید ([پارامترها](pages.md#parameters)).
