# Baziness Pay, step by step

> Selling items for real money, in order — open the payment page, check and verify the payment, give the item exactly once, and consume the purchase so it can be bought again.

## Before you start
| | |
| --- | --- |
| **Base URL** `{pay}` | development: `https://pay-dev.karizmastudios.org`<br>production: `https://pay.baziness.com` |
| **Who calls it** | **your server**. The client only opens the payment page, and otherwise calls your own server |
| **`pname`** | your **Pay package name**. It may differ from the platform package name |
| **`sku`** | the product id registered in the Baziness dashboard (for example `example_pack`) |
| **`uid`** | **your** stable id for the player, and it must be **numeric** (send it as a string, e.g. `"1042"`). A non-numeric uid is rejected with `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
```

## Step 1 — Open the payment page
**What it does:** shows the player Baziness's payment page for one product.

**How:** when the player taps "Buy", your client asks **your server** to start a purchase. Your server saves an order (see step 4), builds the page URL and returns it. The client opens it in a **new tab** on the web, or in the system browser on mobile.

```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]
> Browsers block new tabs that aren't opened **directly** by a tap. Open an empty tab in the tap handler first, then send it to the URL once your server answers ([client code](samples-client.md#purchase-popup)).

## Step 2 — Check whether the player paid
**What it does:** asks Baziness whether this player has a completed, not yet consumed payment for this product, and if so returns its **payment token**.

**When to call it:** when the player comes back to the game (the window or app regains focus), when they tap «پرداخت کردم» ("I've paid"), and when your shop opens, to catch payments finished while the game was closed.

```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` simply means "nothing paid yet": keep waiting quietly.

## Step 3 — Verify the payment
**What it does:** confirms with Baziness that the payment token is genuine. **This is the call that protects you:** never give anything based on what the client says.

```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` takes a **form** body (`application/x-www-form-urlencoded`). The other calls take JSON. Sending JSON here is the most common reason verification fails.

## Step 4 — Give the item exactly once
Baziness tells you the payment is real. Making sure the player receives the item **once** is your server's job. Keep a **payments table** in your database:

| Column | Meaning |
| --- | --- |
| `id` | your order id (returned to the client in step 1) |
| `user_id`, `product_id`, `sku`, `price` | who buys what |
| `status` | `REDIRECTED` (payment page opened) → `FULFILLED` (item given), or `FAILED` / `CANCELLED` |
| `reward_snapshot` | exactly what the player was promised when they tapped "Buy" |
| `provider_token` | the payment token from step 2, with a **unique** constraint |

Then, after a successful step 3, in **one database transaction**:

1. Lock the order row and check it isn't already `FULFILLED`. If it is, answer "done" without giving anything again.
2. If the same payment token already fulfilled **another** order, mark this one `FAILED` and stop.
3. Give the items in `reward_snapshot`, not the product's current definition. An admin who edits a pack while the player pays must not change the order.
4. Save the token and set the order to `FULFILLED`.

## Step 5 — Consume the purchase
**What it does:** marks the purchase as used, so the same product can be bought again. Until it is consumed, step 2 keeps returning it and a second purchase of the SKU is blocked.

**When:** right **after** step 4 is committed. Try a few times if it fails. The item is already given, so a failure here never blocks the player.

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

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

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

## What the player sees
| Moment | Your client shows |
| --- | --- |
| after tapping "Buy" | a small window: "the Baziness payment page is open, come back after paying", with a «پرداخت کردم» button |
| back in the game, payment not found yet | nothing new (quiet re-check); only a tap on «پرداخت کردم» says "payment not found yet, try again in a moment" |
| payment verified | "purchase successful", and the updated balances |
| window closed before paying | nothing is cancelled; the payment is checked again the next time the shop opens |

## Testing
- On **pay-dev**, the payment page offers **«خرید آزمایشی»** (test purchase): the whole flow, from step 1 to step 5, runs without real money.
- Without any gateway access, use the **local fake gateway** from the example game: it speaks the same protocol, with a «پرداخت موفق» button ([settings](samples-server.md#local-fake-gateway)).

## Store builds (Cafe Bazaar / Myket)
A build published in Cafe Bazaar or Myket must use **that store's own in-app billing** instead of Baziness Pay. Keep the same SKU names in both, so one product list serves every build, and send `buildStore` on Baziness page links in those builds ([parameters](pages.md#parameters)).
