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

Документация API: [https://docs.bet-flow.com](https://docs.bet-flow.com)  
**Полный справочник всех методов (один MD):** [MERCHANT_API.md](./MERCHANT_API.md)  
Личный кабинет: [https://app.bet-flow.com/integration](https://app.bet-flow.com/integration)

---

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

1. Получите доступ в **личный кабинет мерчанта** (Bet-Flow выдаёт учётную запись).
2. Откройте раздел **Интеграция** → **Сгенерировать ключи**.
3. Сохраните **публичный** и **секретный** ключ. Секрет показывается **один раз**.
4. Укажите **Callback URL** — HTTPS endpoint для webhook-уведомлений.
5. Добавьте **IP whitelist** — публичные IP серверов, с которых идут запросы к API.
6. Реализуйте подпись запросов и обработку webhook по этому документу.
7. Протестируйте через кнопку **«Тест»** в ЛК и боевой PayIn на минимальную сумму.

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

---

## 2. Безопасность

### 2.1. Общие требования

| Правило | Зачем |
|---------|-------|
| Только HTTPS | Защита ключей и данных в transit |
| Секрет только на backend | Никогда не кладите secret в frontend/mobile |
| Ротация ключей при утечке | В ЛК → «Ротировать ключи» |
| Уникальный `externalID` | Идемпотентность и защита от дублей |
| Проверка webhook-подписи | Защита от подделки статусов |
| IP whitelist в ЛK | Если список не пуст — API принимает запросы **только** с указанных IP/CIDR |

### 2.2. Подпись исходящих запросов (HMAC-SHA256)

**Заголовки (все обязательны):**

```
Content-Type: application/json
X-API-Key: <public_key>
Expires: <unix_timestamp_seconds>
X-API-Sign: <hmac_hex>
X-Nonce: <unique_uuid>
```

**Алгоритм:**

```
message = Expires + body_json     # для POST
message = Expires + query_string  # для GET (как url.Values.Encode())

X-API-Sign = hex(HMAC-SHA256(secret_key, message))
```

**Expires:** рекомендуется `now + 300` секунд (5 минут). Запросы с прошедшим Expires отклоняются (`408`).

**X-Nonce:** UUID v4 на каждый запрос. Повтор nonce в течение 5 минут → `403 forbidden`.

**Rate limit:** 120 запросов/мин на один API-ключ → `429`.

### 2.3. Проверка входящих 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))`

**Чеклист обработки webhook:**

1. Прочитать **сырое** тело запроса (до JSON-парсинга).
2. Вычислить HMAC-SHA256 и сравнить с `X-Signature` (constant-time).
3. Проверить, что `timestamp` не старше разумного окна (например, 10 мин).
4. Найти заказ по `merchant_order_id` или `order_id`.
5. Обновить статус только если переход допустим (не откатывать `success` → `pending`).
6. Ответить **HTTP 200** с любым телом. Non-2xx → повторная доставка.

### 2.4. Тестовый webhook из ЛК

Кнопка **«Тест»** в разделе интеграции отправляет:

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

Подпись: `X-Signature = hex(HMAC-SHA256(secret, raw_body))` — тот же алгоритм, что и для боевых webhook.

---

## 3. API Reference (кратко)

Полная спецификация: [OpenAPI](/openapi.yaml) · [Redoc](/)

### PayIn — создать платёж

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

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

Ответ содержит `id`, `status`, `requisite` (карта/телефон/QR и т.д.).

### PayIn — статус

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

### 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"
}
```

### Справочники

| Метод | Путь |
|-------|------|
| GET | `/v1/balances` |
| GET | `/v1/dictionaries/banks` |
| GET | `/v1/dictionaries/currencies` |
| GET | `/v1/dictionaries/commissions` |

### Статусы (нижний регистр)

| status | Значение |
|--------|----------|
| `created` | Создана |
| `pending` | В обработке |
| `waiting_payment` | Ожидает оплаты |
| `success` | Успешно |
| `success_recalc` | Успех с пересчётом суммы |
| `error` | Ошибка / отмена |
| `expired` | Истёк срок оплаты |
| `dispute` | Диспут |

---

## 4. Примеры кода

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

### 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, path, body=None, query=""):
    expires = str(int(time.time()) + 300)
    nonce = str(uuid.uuid4())
    payload = ""
    if method == "GET":
        payload = query
    elif body is not None:
        payload = json.dumps(body, separators=(",", ":"), ensure_ascii=False)
    message = expires + payload
    sig = hmac.new(SECRET.encode(), message.encode(), hashlib.sha256).hexdigest()
    headers = {
        "Content-Type": "application/json",
        "X-API-Key": API_KEY,
        "Expires": expires,
        "X-API-Sign": sig,
        "X-Nonce": nonce,
    }
    return headers

body = {"externalID": "order-42", "currency": "RUB", "amount": "1000", "type": "card"}
headers = sign_request("POST", "/payin", body)
r = requests.post(f"{BASE}/payin", json=body, headers=headers, 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();
  let payload = method === "GET" ? query : JSON.stringify(body);
  const message = expires + payload;
  const sig = crypto.createHmac("sha256", SECRET).update(message).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)
```

---

## 5. Legacy API (v1 account)

Для существующих интеграций доступны legacy-эндпоинты на том же хосте:

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

Legacy использует те же заголовки подписи. Дополнительно для `POST /v1/payment` поддерживается заголовок **`Idempotency-Key`** (кеш ответа 24ч при повторе с тем же телом).

Legacy-поля: `clientID` вместо `externalID`, статусы в **ВЕРХНЕМ** регистре (`SUCCESS`, `PENDING`).

Новые интеграции — только **v2 API** (`/v1/payin`, `/v1/payout`, `/v1/dictionaries/*`).

---

## 6. Поддержка

- Документация: [docs.bet-flow.com](https://docs.bet-flow.com)
- Настройки ключей и webhook: [app.bet-flow.com/integration](https://app.bet-flow.com/integration)
- При ошибках интеграции приложите: `externalID`, `order_id`, timestamp запроса, HTTP status и `error.cause` из ответа (без secret key).
