# Bet-Flow — полная документация методов для мерчантов

Один файл со всеми методами: Merchant API, legacy, личный кабинет, webhook.

| Ресурс | URL |
|--------|-----|
| **Merchant API (prod)** | `https://api.bet-flow.com/v1` |
| **Личный кабинет** | https://app.bet-flow.com |
| **API ЛК (backend)** | `https://app.api.bet-flow.com` |
| **Документация** | https://docs.bet-flow.com |
| **Ключи / callback / IP** | https://app.bet-flow.com/integration |

---

## Содержание

1. [Быстрый старт](#1-быстрый-старт)
2. [Аутентификация Merchant API](#2-аутентификация-merchant-api)
3. [Сводная таблица методов](#3-сводная-таблица-методов)
4. [Merchant API v2](#4-merchant-api-v2)
5. [Legacy Merchant API](#5-legacy-merchant-api)
6. [Методы личного кабинета (ЛК)](#6-методы-личного-кабинета-лк)
7. [Отмена заявки](#7-отмена-заявки)
8. [Webhook](#8-webhook)
9. [Статусы](#9-статусы)
10. [Ошибки](#10-ошибки)
11. [Примеры подписи](#11-примеры-подписи)

---

## 1. Быстрый старт

1. Получите доступ в ЛК мерчанта.
2. Раздел **Интеграция** → сгенерируйте ключи (секрет показывается один раз).
3. Укажите **Callback URL** (HTTPS).
4. Добавьте **IP whitelist** (egress IP ваших серверов). Пустой список = все IP.
5. Реализуйте HMAC-подпись и обработку webhook.
6. Проверьте webhook кнопкой **«Тест»** в ЛК.

---

## 2. Аутентификация Merchant API

Все запросы к `https://api.bet-flow.com/v1` требуют заголовков:

| Header | Обязателен | Описание |
|--------|------------|----------|
| `Content-Type` | POST | `application/json` |
| `X-API-Key` | да | Публичный ключ |
| `Expires` | да | Unix timestamp (сек), рекомендуется `now + 300` |
| `X-API-Sign` | да | `hex(HMAC-SHA256(secret, Expires + payload))` |
| `X-Nonce` | да | UUID v4 на каждый запрос (anti-replay, 5 мин) |

**Подпись:**

```
# POST
message = Expires + body_json

# GET
message = Expires + query_string   # как Go url.Values.Encode()
```

**Rate limit:** 120 req/min на ключ → `429`.

**Аутентификация ЛК:** cookie / `Access-Token` (сессия пользователя в app.bet-flow.com). Это не HMAC.

---

## 3. Сводная таблица методов

### Merchant API (`https://api.bet-flow.com`)

| # | Метод | Путь | Назначение |
|---|-------|------|------------|
| 1 | `POST` | `/v1/payin` | Создать PayIn |
| 2 | `GET` | `/v1/payin?id=` | Статус PayIn |
| 3 | `POST` | `/v1/payout` | Создать PayOut (фиат) |
| 4 | `GET` | `/v1/payout?id=` | Статус PayOut |
| 5 | `GET` | `/v1/balances` | Баланс |
| 6 | `GET` | `/v1/dictionaries/banks` | Справочник банков |
| 7 | `GET` | `/v1/dictionaries/currencies` | Справочник валют |
| 8 | `GET` | `/v1/dictionaries/commissions` | Справочник комиссий |
| 9 | `POST` | `/upload-receipt` | Загрузить чек по заявке |
| 10 | `POST` | `/v1/payment` | Legacy: создать PayIn |
| 11 | `GET` | `/v1/account/transaction?trackerID=` | Legacy: статус |
| 12 | `GET` | `/v1/account/balances` | Legacy: баланс |

### Личный кабинет (`https://app.api.bet-flow.com`)

| # | Метод | Путь | Назначение |
|---|-------|------|------------|
| 13 | `GET` | `/account/balances` | Баланс в ЛК |
| 14 | `POST` | `/account/withdrawal` | Вывод на кошелёк (сумма + address) |
| 15 | `POST` | `/account/payout/create` | PayOut из ЛК (карта / СБП) |
| 16 | `POST` | `/account/transactions/{txId}/upload-receipt` | Чек по заявке из ЛК |
| 17 | `POST` | `/account/transactions/{txId}/comment` | Комментарий к заявке |
| 18 | `GET` | `/account/transactions/deal` | Карточка заявки |
| 19 | `POST` | `/account/payment/create` | Создать платёж из ЛК |
| 20 | `POST` | `/account/payin/manual` | Ручной PayIn из ЛК |

### Отмена

| # | Где | Метод | Путь |
|---|-----|-------|------|
| 21 | Админка | `POST` | `/admin/transactions/{id}/cancel` |
| — | Система | авто | Истечение TTL PayIn → `expired` / отмена без реквизитов |

Публичного merchant-endpoint отмены в `/v1` **нет**. Отмена оператором — через админку; клиентская заявка без оплаты закрывается по TTL.

---

## 4. Merchant API v2

Base: `https://api.bet-flow.com/v1`

### 4.1. Создать PayIn

```http
POST /v1/payin
```

**Body:**

```json
{
  "externalID": "order-42",
  "currency": "RUB",
  "amount": "1000.00",
  "bank": "any",
  "type": "card",
  "callbackURL": "https://merchant.example/webhook",
  "merchantUserID": "user-123"
}
```

| Поле | Тип | Обяз. | Описание |
|------|-----|-------|----------|
| `externalID` | string | да | Ваш уникальный ID заказа |
| `currency` | string | да | `RUB`, `UZS`, `KGS`, `KZT`, `USD`, `EUR`, `INR`, `EGP`, `TJS`, `TRY`, `AZN` |
| `amount` | string | да | Сумма |
| `bank` | string | нет | Код банка или `any` |
| `type` | string | нет | `card`, `sbp`, `nspk`, `account`, `payform` |
| `callbackURL` | string | нет | Webhook URL (приоритет над глобальным в ЛК) |
| `merchantUserID` | string | нет | ID пользователя у мерчанта |

**Ответ (успех):** `success: true`, в `data` — `id`, `status`, `requisite` (карта / телефон / QR / paymentURL), `expires_at`.

---

### 4.2. Статус PayIn

```http
GET /v1/payin?id=<order_id>
```

`id` — внутренний ID Bet-Flow (`order_id` из ответа / webhook).

---

### 4.3. Создать PayOut (фиатная выплата)

```http
POST /v1/payout
```

```json
{
  "externalID": "payout-99",
  "bank": "SBER",
  "type": "card",
  "currency": "RUB",
  "amount": "5000.00",
  "recipient": "4000000000000000",
  "holder": "IVAN IVANOV",
  "callbackURL": "https://merchant.example/webhook"
}
```

| Поле | Тип | Обяз. | Описание |
|------|-----|-------|----------|
| `externalID` | string | да | Ваш ID |
| `type` | string | да | `card`, `sbp`, … |
| `currency` | string | да | Валюта |
| `amount` | string | да | Сумма |
| `recipient` | string | да | Карта / телефон СБП / реквизит |
| `bank` | string | нет | Код банка |
| `holder` | string | нет | ФИО |
| `callbackURL` | string | нет | Webhook |
| `iban` / `ifscCode` / `tcID` / `card` | — | нет | Доп. поля под метод |

> Для **вывода USDT на кошелёк** используйте ЛК: `POST /account/withdrawal` (см. §6.2).

---

### 4.4. Статус PayOut

```http
GET /v1/payout?id=<order_id>
```

---

### 4.5. Баланс

```http
GET /v1/balances
```

**Ответ (пример структуры):**

```json
{
  "success": true,
  "data": {
    "balances": [
      {
        "currency": "RUB",
        "items": [
          { "type": "in", "available": "12457.89", "frozen": "0.00" },
          { "type": "out", "available": "500.00", "frozen": "0.00" }
        ]
      }
    ]
  }
}
```

`in` — приёмный баланс, `out` — выплатной.

---

### 4.6. Справочник банков

```http
GET /v1/dictionaries/banks
```

---

### 4.7. Справочник валют

```http
GET /v1/dictionaries/currencies
```

---

### 4.8. Справочник комиссий

```http
GET /v1/dictionaries/commissions
```

Элементы содержат `bank`, `currency`, `type` (`in`/`out`), `percent`, `minAmount`, `maxAmount`.

---

### 4.9. Загрузка чека (Merchant API)

Когда метод / провайдер требует чек, мерчант или интеграция может отправить файл:

```http
POST https://api.bet-flow.com/upload-receipt
```

| Параметр | Где | Описание |
|----------|-----|----------|
| `trackerID` | Header | ID заявки (tracker / public id) |
| `receipt` | multipart form | Файл чека (до 20 MB) |
| HMAC-заголовки | Header | Как у остальных API-запросов |

Также клиент плательщика может загрузить чек сам, если метод это требует (UI оплаты / редирект).

---

## 5. Legacy Merchant API

Для старых интеграций на том же хосте:

| v2 (рекомендуется) | Legacy |
|--------------------|--------|
| `POST /v1/payin` | `POST /v1/payment` |
| `GET /v1/payin?id=` | `GET /v1/account/transaction?trackerID=` |
| `GET /v1/balances` | `GET /v1/account/balances` |

- Те же HMAC-заголовки.
- Для `POST /v1/payment` поддерживается `Idempotency-Key` (кеш ответа 24ч).
- Legacy-поля: `clientID` вместо `externalID`, статусы в **ВЕРХНЕМ** регистре (`SUCCESS`, `PENDING`).

Новые интеграции — только **v2**.

---

## 6. Методы личного кабинета (ЛК)

Base: `https://app.api.bet-flow.com`  
Авторизация: сессия ЛК (`Access-Token`).

### 6.1. Баланс

```http
GET /account/balances
```

Отображается на дашборде ЛК.

---

### 6.2. Заявка на вывод на кошелёк

```http
POST /account/withdrawal
```

```json
{
  "accountID": 1,
  "currency": "USDT",
  "address": "TXxx...",
  "amount": "100.00",
  "description": "optional",
  "totpCode": "123456"
}
```

| Поле | Описание |
|------|----------|
| `accountID` | `1` — приёмный счёт, иначе выплатной |
| `currency` | Валюта (обычно `USDT`) |
| `address` | Адрес кошелька (должен быть в whitelist) |
| `amount` | Сумма |
| `description` | Комментарий (опционально) |
| `totpCode` | 2FA, если включена |

Whitelist кошельков настраивается в разделе интеграции ЛК.

---

### 6.3. PayOut из ЛК (карта / СБП)

```http
POST /account/payout/create
```

```json
{
  "amount": "5000.00",
  "type": "CARD",
  "bank": "SBER",
  "currency": "RUB",
  "receiver": "4000000000000000",
  "callbackURL": "https://merchant.example/webhook"
}
```

---

### 6.4. Чек по заявке (ЛК)

```http
POST /account/transactions/{txId}/upload-receipt
```

`multipart/form-data`, поле **`file`**.

Доступно в карточке заявки ЛК, пока статус не терминальный.

---

### 6.5. Комментарий к заявке

```http
POST /account/transactions/{txId}/comment
```

```json
{ "body": "текст комментария" }
```

---

### 6.6. Карточка заявки

```http
GET /account/transactions/deal?lookup=<internal_id|tracker_id|external_id>
```

---

### 6.7. Прочее в ЛК

| Метод | Путь | Назначение |
|-------|------|------------|
| `POST` | `/account/payment/create` | Создать платёж |
| `POST` | `/account/payin/manual` | Ручной PayIn |
| `GET` | `/account/transactions` | Список заявок |
| `GET` | `/account/transactions/crypto` | Крипто-выводы |
| `GET` | `/account/keys` | Генерация API-ключей |
| `GET` | `/account/integration` | Настройки интеграции |
| `PUT` | `/account/integration/callback` | Callback URL |
| `POST` | `/account/integration/callbacks/test` | Тестовый webhook |
| `POST` / `DELETE` | `/account/integration/ip` | IP whitelist |

---

## 7. Отмена заявки

| Сценарий | Как |
|----------|-----|
| Оператор / саппорт | Админка: `POST /admin/transactions/{id}/cancel` |
| Не оплатили вовремя | Авто-истечение → статус `expired` |
| Нет реквизитов | Система закрывает / отменяет заявку |
| Merchant API `/v1/.../cancel` | **Не предоставляется** |

**Админ-отмена (внутренний метод):**

```http
POST /admin/transactions/{id}/cancel
```

```json
{
  "version": 1,
  "reason": "merchant_request",
  "note": "optional"
}
```

---

## 8. Webhook

Bet-Flow шлёт `POST` на `callbackURL` при смене статуса.

```json
{
  "order_id": "0199ee07-dffa-754c-ba2e-c28c2b2ea15a",
  "merchant_order_id": "order-42",
  "status": "success",
  "amount": "1000.00",
  "currency": "RUB",
  "timestamp": 1760635122
}
```

**Подпись:**

```
X-Signature = hex(HMAC-SHA256(secret_key, raw_body))
```

Ответьте **HTTP 200**. Non-2xx → повторная доставка.

**Тест из ЛК:**

```json
{"event":"test","timestamp":"2026-08-22T12:00:00Z","merchantId":1}
```

Та же проверка `X-Signature`.

---

## 9. Статусы

| status (v2, lower) | Значение |
|--------------------|----------|
| `created` | Создана |
| `pending` | В обработке |
| `waiting_payment` | Ожидает оплаты |
| `success` | Успешно |
| `success_recalc` | Успех с пересчётом суммы |
| `error` | Ошибка / отмена |
| `expired` | Истёк срок |
| `dispute` | Диспут |
| `wrong_amount` | Неверная сумма |
| `wrong_bank` | Неверный банк |
| `manual_check` | Ручная проверка |
| `refunded` | Возврат |

Legacy: те же смыслы, **UPPERCASE** (`SUCCESS`, `PENDING`, …).

---

## 10. Ошибки

```json
{
  "success": false,
  "error": { "cause": "invalid X-API-SIGN" }
}
```

| HTTP | cause | Описание |
|------|-------|----------|
| 400 | missing X-API-KEY / SIGN / EXPIRES | Нет заголовка |
| 401 | invalid X-API-KEY / SIGN | Неверный ключ или подпись |
| 403 | forbidden | Нет nonce / replay |
| 403 | client IP is not whitelisted | IP не в whitelist |
| 408 | request has expired | Истёк `Expires` |
| 409 | external id already exists | Дубликат `externalID` |
| 422 | cannot parse request body | Невалидный JSON |
| 429 | too many requests | Rate limit |
| 500 | internal server error | Ошибка сервера |

---

## 11. Примеры подписи

### Python

```python
import hashlib, hmac, json, time, uuid, requests

API_KEY = "your_public_key"
SECRET = "your_secret_key"
BASE = "https://api.bet-flow.com/v1"

def sign_request(method, body=None, query=""):
    expires = str(int(time.time()) + 300)
    nonce = str(uuid.uuid4())
    if method == "GET":
        payload = query
    else:
        payload = json.dumps(body, separators=(",", ":"), ensure_ascii=False)
    sig = hmac.new(SECRET.encode(), (expires + payload).encode(), hashlib.sha256).hexdigest()
    return {
        "Content-Type": "application/json",
        "X-API-Key": API_KEY,
        "Expires": expires,
        "X-API-Sign": sig,
        "X-Nonce": nonce,
    }

body = {"externalID": "order-42", "currency": "RUB", "amount": "1000", "type": "card"}
r = requests.post(f"{BASE}/payin", json=body, headers=sign_request("POST", body), timeout=30)
print(r.status_code, r.json())
```

### Node.js

```javascript
const crypto = require("crypto");

function signRequest(method, body, query = "") {
  const expires = String(Math.floor(Date.now() / 1000) + 300);
  const nonce = crypto.randomUUID();
  const payload = method === "GET" ? query : JSON.stringify(body);
  const sig = crypto.createHmac("sha256", SECRET).update(expires + payload).digest("hex");
  return {
    "Content-Type": "application/json",
    "X-API-Key": API_KEY,
    Expires: expires,
    "X-API-Sign": sig,
    "X-Nonce": nonce,
  };
}
```

### Проверка webhook

```python
def verify_webhook(secret: str, raw_body: bytes, signature: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
```

Готовые скрипты: [examples/sign.py](./examples/sign.py) · [examples/sign.js](./examples/sign.js)

---

## Поддержка

Вопросы по подключению — через менеджера Bet-Flow или тикет в ЛК.  
Интерактивный OpenAPI (Redoc): https://docs.bet-flow.com/
