# Boutik Pam — Dokimantasyon API pou revandè yo

Dokiman sa a pèmèt yon sit ekstèn (WordPress, Shopify, sit ki fèt ak kòd, oswa yon bot) konekte ak Boutik Pam. Pa gen plugin obligatwa. Sèlman yon sèvè ki ka fè yon demann HTTPS.

Dokiman sa a dekri vrè konpòtman kòd API a (`reseller-api` v3.5.7). Kote yon bagay pa ko fèt nan kòd la, nou di l klèman.

---

## 0. Telechaje dokiman an

- **Li an liy:** [https://boutik-pam.com/?rsa_docs=1](https://boutik-pam.com/?rsa_docs=1)
- **Telechaje HTML:** [https://boutik-pam.com/?rsa_docs=html](https://boutik-pam.com/?rsa_docs=html)
- **Telechaje Markdown:** [https://boutik-pam.com/?rsa_docs=md](https://boutik-pam.com/?rsa_docs=md)

---

## 1. Enfòmasyon debaz

| Chan | Valè |
|---|---|
| Adrès baz | `https://boutik-pam.com/wp-json/reseller/v1` |
| Fòma done | JSON (`Content-Type: application/json`) |
| Otantifikasyon | Header `X-API-Key` (kle piblik) ak `X-API-Secret` (sekrè) |
| Limit pousantaj | **`POST /order`**: 60 kòmand pa minit. **`GET /catalog`**: 60 demann pa minit. **`POST /catalog/{id}/verify-player`**: 60 demann pa minit. Chak wout gen kont pwòp li, epi yo separe de `/order`. Depase limit an bay erè `429` `rsa_rate_limited`. `verify-player` gen tou yon kota 2 000 demann pa jou (erè `429` `rsa_daily_quota`). |
| Lockout | Apre 10 tantativ otantifikasyon **echwe** pou yon menm IP ak yon menm kle, API a bloke tantativ echwe yo pandan 15 minit (erè `429` `rsa_locked_out`). Yon demann ak sekrè ki kòrèk pa janm bloke pa lockout la. |

**Enpòtan:**
- Rele API a **sèlman sou sèvè ou** (backend). Pa janm mete `X-API-Secret` nan JavaScript ki kouri nan navigatè kliyan an.
- Tout apèl mande `https`.
- Admin Boutik Pam bay ou kle ak sekrè a. Ou ka wè sekrè a ankò nan paj « API mwen » nan portal la, men li sansib: pa janm pataje l, epi sove l sèlman sou sèvè ou.

---

## 2. Otantifikasyon

Chak demann mande de header:

```
X-API-Key: <kle ou a>
X-API-Secret: <sekrè ou a>
Content-Type: application/json
```

| HTTP | Kòd | Sa li vle di |
|---|---|---|
| 401 | `rsa_no_auth` | Header manke |
| 401 | `rsa_bad_auth` | Kle oswa sekrè a pa kòrèk |
| 403 | `rsa_suspended` | Kont la sispann |
| 403 | `rsa_not_approved` | Kont la poko apwouve |

---

## 3. Wout yo

| Metòd | Chemen | Sa li fè |
|---|---|---|
| GET | `/catalog` | Lis pwodwi ou ka vann |
| POST | `/catalog/{id}/verify-player` | Verifye yon ID jwè (ak sèvè a, si jwèt la bezwen l) |
| GET | `/balance` | Wè balans ou |
| POST | `/order` | Kreye yon kòmand |
| GET | `/order/{id}` | Wè estati yon kòmand ak kòd livrezon yo |

### 3.1 `GET /catalog`

Repons: `{ "success": true, "catalog": [...], "catalogs": [...] }`.

- `catalog` — lis plat tout pwodwi ki disponib pou ou.
- `catalogs` — menm pwodwi yo, gwoupe pa katalòg (`catalog_id`, `catalog_title`, `position`, `products`).

**Pwodwi ki nan yon katalòg sèlman parèt.** Yon pwodwi ki pa nan okenn katalòg aktif pa parèt, epi ou pa ka kòmande l.

Chak pwodwi gen:

| Chan | Sa li vle di |
|---|---|
| `product_id` | ID WooCommerce pwodwi a (oswa varyasyon an). **Se ID sa a ou itilize nan `{id}`** pou `verify-player`. |
| `sku` | SKU pwodwi a. Ou ka itilize l nan `POST /order`. |
| `name` | Non pwodwi a |
| `price` | Pri ou (si ou se machann, pri Machann; sinon pri founisè a). Se sa ki debite sou balans ou. |
| `currency` | Devis la |
| `categories` | Kategori WooCommerce yo |
| `catalog_id`, `catalog_title` | Katalòg pwodwi a ye nan |
| `parent_id` | ID pwodwi paran an si se yon varyasyon; `null` si se yon pwodwi senp |
| `requires_player_id` | Toujou `true` pou pwodwi jwèt. Pou kat kado, `false`. |
| `requires_server_id` | `true` si jwèt la mande yon `server_id` oswa `zone_id` |
| `customer_fields` | Lis chan ou dwe mande kliyan an (gade anba a) |
| `verify_player_endpoint` | URL konplè pou `verify-player` (`https://boutik-pam.com/wp-json/reseller/v1/catalog/{product_id}/verify-player`). **Sèlman pwodwi ki gen yon URL isit la sipòte `verify-player`.** Pou lòt pwodwi ki mande Player ID (pa egzanp kat kado, oswa yon lòt motè), se `null`: yo pa bezwen verifikasyon, epi `/order` pa egzije l pou yo.
| `in_stock` | Toujou `true` (stòk la verifye nan `/order`) |

Egzanp yon pwodwi jwèt nan `catalog` (valè yo se egzanp):

```json
{
  "product_id": 13858,
  "sku": "FF-100",
  "name": "100 + 10 bonis",
  "price": 139.99,
  "currency": "HTG",
  "categories": ["110"],
  "catalog_id": "free-fire-amerik",
  "catalog_title": "Free Fire Amerik",
  "requires_player_id": true,
  "requires_server_id": false,
  "customer_fields": [
    { "key": "player_id", "label": "Player ID", "required": true, "type": "text" }
  ],
  "verify_player_endpoint": "https://boutik-pam.com/wp-json/reseller/v1/catalog/13858/verify-player",
  "in_stock": true
}
```

**`customer_fields`**: chak chan gen `key`, `label`, `required` (default `true`), ak `type` (default `text`). Chan yo soti nan meta founisè pwodwi a. Nòmalizasyon an fèt otomatikman:

| Non nan meta a | Vin |
|---|---|
| `userid`, `user_id`, `playerid`, `roleid`, `role_id` | `player_id` |
| `serverid` | `server_id` |
| `zoneid` | `zone_id` |
| `character_name`, `player_name` | `charname` |

Fè fòm kliyan an ak `customer_fields`. Si yon chan gen `required: true`, mande l.

**Règ chan yo:**
- `player_id` toujou prezan pou yon pwodwi jwèt, menm si meta a pa bay li.
- `server_id` / `zone_id` obligatwa **sèlman** si chan an gen `required: true`. Si l obligatwa e w pa voye l: `400` `rsa_missing_server_id`.
- `charname` pa obligatwa, sof si chan an gen `required: true`. Si l obligatwa e vid: `400` `rsa_missing_charname`.
- Si yon pwodwi gen yon chan `required: true` ki pa nan lis ki sipòte pa API a (`player_id`, `server_id`, `zone_id`, `charname`), `/catalog/{id}/verify-player` ak `/order` retounen `422` `rsa_unsupported_required_field` (ak non chan an). Pa gen okenn kòmand kreye nanka sa a. Kontakte Boutik Pam pou korije konfigirasyon pwodwi a.
- Lòt chan ki pa obligatwa yo pa transmèt bay sistèm nan.

### 3.2 `POST /catalog/{id}/verify-player`

**`{id}` se `product_id`** (nan `/catalog`), **pa `catalog_id`**. Se yon nonb (egzanp `13858`).

Kò JSON:

```json
{
  "player_id": "123456789",
  "server_id": "",
  "charname": ""
}
```

| Chan | Obligatwa? | Sa li fè |
|---|---|---|
| `player_id` | **Wi** (vid = erè `400`) | ID jwè a. Sistèm verifikasyon an resevwa l kòm `user_id`. |
| `server_id` | Si `requires_server_id` = `true` | Obligatwa pou pwodwi sa yo. Si li manke, ou jwenn `400` `rsa_missing_server_id`. |
| `zone_id` | Alternatif pou `server_id` | Si w voye `zone_id` olye de `server_id`, li trete kòm `server_id`. |
| `charname` | Non | Non karaktè a. Si ou voye l, li itilize pou verifikasyon an. |

**Non jwè a (`name`) soti nan sistèm verifikasyon an, pa nan kliyan an.** Kliyan an pa bezwen tape non an.

**Ki jan verifikasyon an mache** (dapre kòd la):
1. Pwodwi a dwe aktif, nan yon katalòg, avèk yon pri, e disponib pou kont ou. Si non, `404`.
2. Pwodwi a dwe yon jwèt ki konfigire pou verifikasyon Player ID. Si non, `422` `rsa_not_game`.
3. Sistèm nan rele sèvis verifikasyon an ak `{ game, user_id, server_id?, charname? }`. Delè a se 20 segonn.
4. Entèpretasyon an **strik**: si youn nan chan `valid`, `success`, `exists` oswa `exists_in_game` di **false** (oswa `"false"`, `"0"`, `"invalid"`), ID an pa valid, menm si yon lòt chan di wi. Sinon, ID an konsidere kòm **valid** si youn nan chan sa yo di wi: `valid` = `"valid"` oswa `true`; `success`, `exists` oswa `exists_in_game` = `true`; oswa si li gen yon non (`name`, `player_name`, `username`).
5. Non an pran nan `name`, `player_name`, `username`, oswa `account_name` (premye ki egziste).

**Kota chak jou:** 2 000 verifikasyon pa jou pou chak revandè. Depase l → `429` `rsa_daily_quota`.

**Non jwè a se done pèsonèl:** sèvi ak li sèlman pou kliyan an wè konfimasyon ID a, epi pa kenbe l pi lontan pase sa.

**Repons pou yon ID valid** (egzak, sa kòd la retounen):

```json
{
  "success": true,
  "valid": true,
  "player_id": "123456789",
  "server_id": null,
  "name": "PlayerName"
}
```

Repons lan retounen tou `server_id` (sa ou te voye a), epi `region` ak `server` **sèlman si sèvis la bay yo**. Kòd la pa garanti yo.

**Repons pou erè yo** (fòma WordPress: `code`, `message`, `data.status`):

| Sitiyasyon | HTTP | `code` | `message` (egzanp) |
|---|---|---|---|
| ID pa valid | 422 | `rsa_invalid_player` | `Player ID sa a pa valid.` |
| Sèvis verifikasyon an retounen yon erè | 422 | `rsa_invalid_player` | Mesaj erè a, netwaye |
| Pwodwi pa disponib / pa nan katalòg | 404 | `rsa_product_unavailable` | `Pwodwi sa a pa disponib nan API a.` |
| Pwodwi a pa jwèt | 422 | `rsa_not_game` | `Pwodwi sa a pa bezwen verifikasyon Player ID.` |
| `player_id` vid | 400 | `rsa_missing_player_id` | `Player ID obligatwa.` |
| Konfigirasyon jwèt la pa konplè | 422 | `rsa_game_not_configured` | `Konfigirasyon jwèt la pa konplè pou pwodwi sa a.` |
| Sèvis livrezon an pa disponib | 503 | `rsa_delivery_service_unavailable` | `Sistèm livrezon jwèt la pa disponib pou kounye a.` |
| Sèvis verifikasyon an pa reponn (delè, erè rezo) | 503 | `rsa_delivery_network` | `Sistèm livrezon an pa reponn pou kounye a.` |
| Sèvis verifikasyon an retounen yon repons ki pa valid | 502 | `rsa_delivery_bad_response` | `Repons sistèm livrezon an pa valid.` |
| Sèvis verifikasyon an retounen 401, 403, 429 oswa nenpòt erè 5xx | 503 | `rsa_delivery_service_unavailable` | `Sistèm livrezon jwèt la pa disponib pou kounye a.` |

**Erè sèvis yo** (401, 403, 429, 5xx) parèt toujou kòm `503`, pa kòm « ID pa valid », kèlkeswa kò repons lan. Kliyan an pa dwe wè yo kòm erè ID.

### 3.3 `GET /balance`

Repons: `{ "success": true, "balance": 1250.00, "currency": "HTG", "site_name": "Non sit ou a" }`.

`currency` la montre devis `balance` la (menm devis ak `price` nan `/catalog` ak `debit` nan `/order`).

### 3.4 `POST /order`

Kò JSON:

```json
{
  "sku": "FF-100",
  "quantity": 1,
  "player_id": "123456789",
  "server_id": "",
  "charname": "",
  "external_order_id": "maCommande-000123",
  "webhook_url": "https://monsite.com/api/boutikpam-webhook"
}
```

| Chan | Obligatwa? | Règ |
|---|---|---|
| `sku` oswa `product_id` | Youn nan de | |
| `quantity` | Non (default 1) | Ant 1 ak 20 |
| `player_id` | Si `requires_player_id` = `true` | Maks 100 karaktè |
| `server_id` | Si `requires_server_id` = `true` | Maks 100 karaktè |
| `charname` | Non | Maks 100 karaktè |
| `external_order_id` | **Wi** (ka voye sèlman nan header `Idempotency-Key`) | Lèt, chif, pwen, tirè, underscore ak tilde sèlman |
| `webhook_url` | Non | Dwe HTTPS |

**Si motè livrezon pwodwi a pa disponib sou sèvè a, `/order` retounen `503` `rsa_delivery_service_unavailable` ANVAN okenn debit.** Eseye ankò pita.

**Pou pwodwi ki gen `verify_player_endpoint`, `/order` egzije yon verifikasyon ki resan pou menm `product_id` ak menm revandè a.** Si ou pa rele `verify-player` ak menm `player_id`, `server_id` ak `charname` nan 3 èdtan anvan, `/order` retounen `422` `rsa_player_not_verified`. Si kliyan an chanje ID, sèvè oswa non karaktè a, verifye ankò. Pou kat kado, pa gen verifikasyon.

**Idempotence:** si ou voye menm `external_order_id` ak menm done yo ankò, ou jwenn menm kòmand lan epi ou pa peye de fwa. Si done yo chanje, ou resevwa `409` `rsa_idempotency_conflict`.

Ou ka voye menm valè a nan header `Idempotency-Key`. Si ou voye tou de, yo dwe menm bagay.

Repons nòmal (201 oswa 200): gen `success`, `message`, `order_id`, `wc_order_id`, `external_order_id`, `product_id`, `sku`, `quantity`, `player_id`, `server_id`, `charname`, `status`, `payment_status`, `total_price`, `currency`, `new_balance`, `refunded`, `refund_status`, `response_note`, `history`, `codes`, `updated_at`.

Erè espesifik: `402` `rsa_insufficient_balance`, `422` `rsa_product_unavailable` (pa nan stòk, pa nan katalòg, oswa pa gen pri), `422` `rsa_not_linked`, `400` `rsa_missing_idempotency`, `400` `rsa_missing_player_id` / `rsa_missing_server_id`, `400` `rsa_invalid_quantity`, `400` `rsa_identifier_too_long`, `400` `rsa_missing_product`, `409` `rsa_idempotency_conflict`, `409` `rsa_previous_attempt_failed`, `429` `rsa_rate_limited`, `503` `rsa_rate_busy`, `503` `rsa_storage_unsafe`. Erè anplis: `400` `rsa_idempotency_mismatch`, `404` `rsa_invalid_product`, `409` `rsa_product_mismatch`, `409` `rsa_unexpected_order_status`, `500` `rsa_order_failed`, `500` `rsa_payment_failed`, `500` `rsa_delivery_start_failed`, `503` `rsa_woocommerce_missing`, `503` `rsa_no_wallet_plugin`.

### 3.5 `GET /order/{id}`

`{id}` ka `external_order_id` ou a oswa `order_id` Boutik Pam lan.

| Chan | Sa li vle di |
|---|---|
| `status` | `creating`, `pending`, `processing`, `completed`, `failed`, `cancelled`, `refunded`, `payment_failed`, `creation_failed` |
| `payment_status` | Estati peman an |
| `total_price`, `currency`, `quantity` | Detay kòmand lan |
| `codes` | Lis kòd livrezon yo (PIN, kat kado, elatriye). Yo parèt sèlman lè `status` = `completed`. |
| `message` | Mesaj ki eksplike estati a |
| `refunded`, `refund_status` | Si lajan an retounen sou balans ou |
| `history` | Istorik estati yo (pou debogaj) |

Erè: `404` `rsa_order_not_found`.

---

## 4. Webhook

Si ou bay `webhook_url`, Boutik Pam voye yon `POST` chak fwa estati a chanje.

**Kò a gen:** `event_id`, `order_id`, `external_order_id`, `wc_order_id`, `product_id`, `sku`, `quantity`, `status`, `payment_status`, `refunded`, `refund_status`, `message`, `history`, `codes`, `updated_at`.

**Header yo:**
- `X-Reseller-Timestamp` — lè a (Unix, segonn)
- `X-Reseller-Signature` — `sha256=` + HMAC-SHA256 de `timestamp + "." + kò_brit_la`, ak API Secret ou a kòm kle. Ansyen header a rete pou kompatibilite, men li mande pou ou gen API Secret la sou sèvè webhook la.
- `X-Reseller-Signature-V2` — menm fòmilasyon, men kle a se **kle verifikasyon webhook** (derive ak API Secret la, pa API Secret la menm). **Nouvo entegrasyon dwe sèvi ak V2.** Ansyen header a pral retire nan yon vèsyon pita.

### Verifye siyati a

Pou header `X-Reseller-Signature-V2` (rekòmande), pase **kle verifikasyon webhook** ou a kòm `secret` (ou jwenn li nan paj « API mwen »). Pou `X-Reseller-Signature` (ansyen), pase API Secret ou a.

Toujou verifye siyati a anvan ou kwè mesaj la. Fè konparezon an ak `hash_equals` oswa nan tan konstan.

**Node.js:**

```js
const crypto = require('crypto');
function verify(rawBody, timestamp, signature, secret) {
  if (typeof rawBody !== 'string' || typeof timestamp !== 'string' || typeof signature !== 'string' || !secret) return false;
  const ts = Number(timestamp);
  if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) >= 300) return false;
  const expected = Buffer.from('sha256=' + crypto.createHmac('sha256', secret)
    .update(timestamp + '.' + rawBody).digest('hex'));
  const received = Buffer.from(signature);
  // timingSafeEqual lanse yon erè si longè yo pa egal: verifye longè a anvan.
  if (expected.length !== received.length) return false;
  return crypto.timingSafeEqual(expected, received);
}
```

**Python:**

```python
import hmac, hashlib, time
def verify(raw_body: str, timestamp: str, signature: str, secret: str) -> bool:
    if not isinstance(raw_body, str) or not isinstance(timestamp, str) or not isinstance(signature, str) or not secret:
        return False
    try:
        ts = int(timestamp)
    except ValueError:
        return False
    if abs(time.time() - ts) >= 300:
        return False
    expected = 'sha256=' + hmac.new(secret.encode(), (timestamp + '.' + raw_body).encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
```

**PHP:**

```php
function rsa_verify_webhook($raw_body, $ts, $signature, $secret) {
    if (!is_string($raw_body) || !is_string($ts) || !is_string($signature) || $secret === '') return false;
    if (!ctype_digit($ts) || abs(time() - (int) $ts) >= 300) return false;
    $expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $raw_body, $secret);
    return hash_equals($expected, $signature);
}
```

Pran `raw_body` tèks brit la (pa re-kodaj JSON an).

Sispann si `event_id` deja trete, pou evite doub livrezon. Kò mesaj la ak `event_id` la rete MENM nan tout relansman yo (sèlman `X-Reseller-Timestamp` ak `X-Reseller-Signature` chanje). Si ou resevwa menm `event_id` yon dezyèm fwa, konfime l epi pa trete l ankò.

**Konpòtman livrezon:**
- Ou dwe reponn ak yon kòd HTTP `2xx` nan 5 segonn. Si ou pa reponn, oswa ou reponn ak lòt kòd, Boutik Pam konsidere l kòm echwe.
- Si l echwe, Boutik Pam relanse l jiska 5 fwa: 1 minit, 5 minit, 15 minit, 1 è, apre 3 èdtan. Apre sa, li abandone.
- Voye webhook yo ak relansman yo fèt pa WP-Cron, sou sit la. Si sit la gen ti trafik oswa WP-Cron dezaktive, yo ka reta. Pwosesis kreyasyon kòmand lan pa tann yo.
- Evènman yo sere nan yon tablo pèmanan, avèk kò mesaj ak `event_id` ki pa chanje ant tantativ yo. Si planifikasyon yon travay echwe, yon travay rekiperasyon chak 5 minit replanifye evènman ki pare yo. Yon travay ki bloke plis pase 2 minit kapab rekipere. Yon reseptè dwe toujou dedouble ak `event_id`, paske yon timeout ka rive apre li fin resevwa mesaj la. Mesaj ka rive nan yon lòt lòd; verifye estati aktyèl la atravè `GET /order/{id}` anvan yon desizyon final.
- Si ou rate yon webhook (oswa ou pa sèten ou resevwa l), rele **`GET /order/{id}`** pou jwenn estati aktyèl kòmand lan. Sa toujou mache.


---

## 5. Modèl jesyon erè ak repetisyon

- **Timeout oswa erè rezo** sou `POST /order`: ou pa konnen si kòmand lan kreye. Rele `POST /order` ANKÒ ak **menm `external_order_id` ak menm done**. Si li te kreye deja, ou jwenn menm repons lan (`200` oswa `202`), san nouvo debit.
- **`202`**: kòmand lan ap kreye. Tann, epi rele `GET /order/{id}`.
- **`409` `rsa_idempotency_conflict`**: menm `external_order_id` ak done diferan. Pa repete l. Itilize yon nouvo `external_order_id`.
- **`409` `rsa_previous_attempt_failed`**: lòd sa a gen yon debit oswa yon ranbousman deja (oswa yon rezilta final). Kreye yon nouvo `external_order_id`.
- **`402` `rsa_insufficient_balance`**: pa gen debit. Mande kliyan an rechaje kont la. Apre sa, ou ka rele `POST /order` ANKÒ ak **menm `external_order_id`**: lòd la re-ouvri otomatikman (3.5.7).
- **`500` `rsa_order_failed` / `rsa_payment_failed`**: si lòd la pa gen `payment_reference` (pa gen debit), menm `external_order_id` la mache ankò. Si ou pa sèten, verifye ak `GET /order/{id}`.
- **`503` `rsa_delivery_service_unavailable`** sou `/order`: motè livrezon an pa disponib. Pa gen okenn debit. Retry ak yon ti reta, ak menm `external_order_id`.
- **Ranbousman**: ranbousman fèt yon sèl fwa. Si estati a `refunded`, lajan an retounen sou balans ou. Verifye ak `GET /order/{id}` olye de kreye yon nouvo acha.

```js
// Modèl sou sèvè ou (pseudo-kòd)
const r = await callApi('POST', '/order', body, { timeoutMs: 15000 })
  .catch(() => ({ status: 0 }));           // timeout / rezo
if (r.status === 0 || r.status === 503) {
  // menm external_order_id, retry ak reta (pa plis pase kèk tantativ)
}
```

### 5.1 Egzanp rapid: verifye ID epi kreye kòmand

**1. Jwenn pwodwi a ak chan kliyan an bezwen** (`GET /catalog`).

**2. Verifye ID a** (sèvè a, avan kliyan an peye):

```bash
curl -X POST https://boutik-pam.com/wp-json/reseller/v1/catalog/13858/verify-player \
  -H "X-API-Key: $KEY" -H "X-API-Secret: $SECRET" \
  -H "Content-Type: application/json" \
  -d '{"player_id":"123456789","server_id":"","charname":""}'
```

Si repons lan gen `"valid": true`, sove `player_id`, `server_id`, ak `name` pou kliyan an wè l. **Si `valid` pa vrè, bloke acha a.**

**3. Kreye kòmand lan** (pandan kliyan an peye):

```bash
curl -X POST https://boutik-pam.com/wp-json/reseller/v1/order \
  -H "X-API-Key: $KEY" -H "X-API-Secret: $SECRET" \
  -H "Content-Type: application/json" \
  -d '{"sku":"FF-100","quantity":1,"player_id":"123456789","external_order_id":"cmd-000123"}'
```

**Node.js:**

```js
const base = 'https://boutik-pam.com/wp-json/reseller/v1';
const headers = { 'X-API-Key': KEY, 'X-API-Secret': SECRET, 'Content-Type': 'application/json' };

const v = await fetch(`${base}/catalog/13858/verify-player`, {
  method: 'POST', headers,
  body: JSON.stringify({ player_id: '123456789', server_id: '', charname: '' }),
});
const verified = await v.json();
if (!v.ok || !verified.valid) throw new Error('ID pa valid');
```

**Python:**

```python
import requests
base = 'https://boutik-pam.com/wp-json/reseller/v1'
h = {'X-API-Key': KEY, 'X-API-Secret': SECRET}
r = requests.post(f'{base}/catalog/13858/verify-player', headers=h,
                  json={'player_id': '123456789', 'server_id': '', 'charname': ''}, timeout=25)
data = r.json()
if not r.ok or not data.get('valid'):
    raise RuntimeError('ID pa valid')
```

---

## 6. Kòd erè (rezime)

| HTTP | Kòd | Kote |
|---|---|---|
| 400 | `rsa_missing_idempotency` | `/order` |
| 400 | `rsa_missing_player_id` / `rsa_missing_server_id` / `rsa_missing_charname` | `/order`, `/verify-player` |
| 422 | `rsa_unsupported_required_field` | `/order`, `/verify-player` |
| 400 | `rsa_invalid_quantity` | `/order` |
| 400 | `rsa_identifier_too_long` | `/order` |
| 400 | `rsa_missing_product` | `/order` |
| 400 | `rsa_idempotency_mismatch` | `/order` |
| 409 | `rsa_product_mismatch` | `/order` |
| 409 | `rsa_unexpected_order_status` | `/order` |
| 500 | `rsa_order_failed`, `rsa_payment_failed`, `rsa_delivery_start_failed` | `/order` |
| 503 | `rsa_woocommerce_missing`, `rsa_no_wallet_plugin` | `/order` |
| 401 | `rsa_no_auth` / `rsa_bad_auth` | Tout |
| 402 | `rsa_insufficient_balance` | `/order` |
| 403 | `rsa_suspended` / `rsa_not_approved` | Tout |
| 404 | `rsa_order_not_found` | `/order/{id}` |
| 404 | `rsa_product_unavailable` | `/verify-player` |
| 404 | `rsa_invalid_product` | `/order` |
| 422 | `rsa_product_unavailable` | `/order` |
| 409 | `rsa_idempotency_conflict` | `/order` |
| 409 | `rsa_previous_attempt_failed` | `/order` |
| 422 | `rsa_invalid_player` | `/verify-player` |
| 422 | `rsa_not_game` / `rsa_game_not_configured` | `/verify-player` |
| 422 | `rsa_not_linked` | `/order` |
| 422 | `rsa_player_not_verified` | `/order` (pou jwèt, verifye ID an anvan, 3 èdtan valab) |
| 429 | `rsa_rate_limited` | `/order` (60/minit), `/catalog` ak `/verify-player` (60/minit chak) |
| 429 | `rsa_locked_out` | Tout (apre 10 tantativ echwe) |
| 429 | `rsa_daily_quota` | `/verify-player` (2 000/jou) |
| 503 | `rsa_wallet_debit_failed`, `rsa_wallet_user_missing`, `rsa_wallet_storage_unsafe`, `rsa_transaction_failed` | `/order` |
| 500 | `rsa_reservation_failed` | `/order` |
| 422 | `rsa_sku_too_long` | `/order` |
| 502 | `rsa_delivery_bad_response` | `/verify-player` |
| 503 | `rsa_delivery_network` / `rsa_delivery_service_unavailable` | `/verify-player`; `/order` (motè livrezon pa disponib, pa gen debit) |
| 503 | `rsa_rate_busy` / `rsa_storage_unsafe` | `/order` |

---

## 7. Lis kontwòl anvan lansman

- [ ] Kle ak sekrè a nan yon kote sekirize sou sèvè a sèlman
- [ ] Ou rele `verify-player` **sou sèvè ou** (pa nan navigatè), epi ou sèvi ak `product_id` nan `{id}`
- [ ] Ou bloke acha a si `valid` pa vrè
- [ ] Ou verifye ankò si kliyan an chanje `player_id`, `server_id`, oswa `charname`
- [ ] Chak kòmand gen yon `external_order_id` inik
- [ ] Ou sove repons lan anvan ou montre anyen bay kliyan an
- [ ] Ou verifye siyati webhook la (V2 pref) epi ou sispann `event_id` ki double
- [ ] Ou pa montre `history`, `new_balance` oswa non Boutik Pam bay kliyan final ou yo
- [ ] Ou respekte limit 60 `/order` pa minit
- [ ] Ou teste ak yon kòmand reyèl ki ba ou yon kòd, epi ou konfime `codes` parèt

---

## 8. Chanjman API

**Vèsyon 3.5.7:**
1. **Lockout** konte sèlman tantativ echwe. Yon sekrè ki kòrèk pa janm bloke pa lockout la (yon atakè ki konnen kle piblik la pa ka bloke revandè a).
2. **Re-ouvri lòd**: yon lòd ki echwe san debit (402, erè kreyasyon) ka re-eseye ak menm `external_order_id` ak menm done yo.
3. **Limit** pou `GET /balance` (60/minit) ak `GET /order/{id}` (120/minit).
4. **Webhook**: signature legacy `X-Reseller-Signature` ka dezaktive pa filtre `rsa_send_legacy_webhook_signature` pou entegratè ki fin migre sou V2.
5. Mesaj piblik yo kache non BPFF/wp2p tou.

**Vèsyon 3.5.6:**
1. **Kle verifikasyon webhook (V2)**: nouvo header `X-Reseller-Signature-V2`, siyen ak yon kle derive (pa API Secret la). Kle a parèt nan paj « API mwen ». Ansyen `X-Reseller-Signature` rete pou kompatibilite.
2. **Revokasyon dijans**: nan « Rekreye API Key/Secret », koche « revoke imedyatman » pou kontoune limit 24 èdtan lè yon kle konpwomèt. Aksyon an anrejistre nan Log API.
3. **Kota chak jou** pou `verify-player`: 2 000 demann pa jou pou chak revandè (`rsa_daily_quota`).
4. **Sekrè pa efase ankò**: yon kont ki gen sekrè ki pa ka dechifre pa pèdi done li. Admin wè yon avètisman nan paj Revandè a. Rekreye kle yo pou kont sa yo.
5. **Rekonsiliyasyon**: lòd ki bloke yo pa anpeche lòt yo verifye ankò (chwa o aza).

**Vèsyon 3.5.5:**
1. Repons founisè vid (`[]`) pou `verify-player` retounen `502` `rsa_delivery_bad_response`, pa `422`.

**Vèsyon 3.5.4:**
1. Repons verifikasyon ki pa yon array (tèks, nonb, boolean, objè) retounen `502` kontwole, san erè PHP. `null`, `WP_Error` ak eksepsyon sèvis yo retounen `503` san ekspoze detay entèn.
2. Chan repons ki mal fòme yo rejte anvan konvèsyon; 401/403/429/5xx toujou rete erè sèvis `503`.
3. Pwoteksyon cache portal la aplike sou hooks `wp` ak `template_redirect` anvan rann template la. Li rekonèt paj ki konfigire a ak twa shortcode portal yo.
4. Nòt `delivery_stalled` la baze sou 60 minit depi dènye mizajou kòmand lan, menm si yon tranzaksyon livrezon egziste. Li pa pwouve livrezon an echwe.

**Vèsyon 3.5.3:**
1. Tablo kòd erè ak estati HTTP korije: `/order` pwodwi pa disponib retounen `422`; erè 5xx sou verify-player retounen `rsa_delivery_service_unavailable` (503).
2. Katalòg konstwi yon sèl fwa pou portal la ak pou `GET /catalog`.
3. Ajisteman balans admin: montan an dwe diferan de 0 epi li pa ka mennen balans lan nan negatif; chak ajisteman anrejistre nan Log API.
4. Yon pwodwi ke admin dezaktive pa re-aktive pa otomatizasyon MACHANN.
5. Yon lòd ki rete `processing` plis pase 60 minit depi dènye mizajou li make `delivery_stalled` nan istwa a (pa gen ranbousman otomatik; admin verifye).
6. Paj portal la pa sere nan cache (done sekrè yo).

**Vèsyon 3.5.2:**
1. Ranbousman fini rete pwoteje kont tout nouvo estati WooCommerce, menm `failed`, `cancelled` ak `refunded`.
2. Webhook yo sere nan baz done a; yon purge cache pa retire evènman ki poko livre yo. Premye tantativ la ak relansman yo fèt an fon.
3. Planifikasyon verifye; travay rekiperasyon an repran travay ki pa t planifye oswa ki te bloke.
4. Player ID toujou make obligatwa pou pwodwi jwèt.
5. Disponibilite motè a verifye apati callbacks livrezon ki chaje yo. Adaptè espesyal bezwen yon verifikasyon entegrasyon sou sit la.

**Vèsyon 3.5.1:**
1. **Non blokaj MySQL** kout (64 karaktè) pou pa bloke API a sou sèvè ki aplike limit la.
2. **Webhook**: kò ak `event_id` fiks ant tout relansman yo. Yo voye yo nan cron, pa nan demann acha a.
3. **Chan obligatwa**: `player_id` toujou prezan pou jwèt; yon chan obligatwa ki pa sipòte refize kòmand lan (`rsa_unsupported_required_field`).
4. **Estati WooCommerce**: yon kòmand ki deja ranbouse pa chanje estati a lè WooCommerce voye yon nouvo estati.
5. **Motè bpff**: plugin an dwe aktif, pa sèlman tablo a.

**Vèsyon 3.5.0:**
1. **Repetisyon kòmand**: yon demann ki repete ak menm `external_order_id` ak menm done retounen menm kòmand lan, menm lè `charname` la gen valè.
2. **Balans**: `GET /balance` retounen `currency`.
3. **Verifye jwè**: repons yo entèprete strikteman. Yon chan ki di `false` pa janm vin `valid`. `server_id` obligatwa pou pwodwi ki mande l.
4. **Erè sèvis**: 5xx, 401, 403 ak 429 nan sèvis verifikasyon an parèt kòm `503`, pa kòm « ID pa valid ».
5. **Katalòg**: varyasyon yo parèt ak bon paran yo, san doublon.
6. **Webhook**: relansman otomatik (5 fwa) ak log rezilta chak tantativ.
7. **Limit**: `/catalog` ak `/verify-player` gen chak kont pwòp yo.
8. **Motè livrezon**: si motè a pa disponib, `/order` retounen `503` anvan okenn debit.

**Fèt (vèsyon anvan):**
1. **`/order` egzije verifikasyon ki resan** pou pwodwi jwèt (`rsa_player_not_verified`, 3 èdtan).
2. **Limit pousantaj separe** sou `/catalog` ak `/verify-player` (60/minit chak), separe de `/order`.
3. **Erè klarifye:** erè konfigirasyon sèvis la (401, 403, 429) parèt kòm `503`; repons ki pa valid parèt kòm `502`. Yo pa parèt kòm « ID pa valid ».
4. **`zone_id`** aksepte kòm alternatif pou `server_id`.
5. **Rejyon ak sèvè** retounen sèlman si sèvis verifikasyon an bay yo.
