> ## Documentation Index
> Fetch the complete documentation index at: https://oblodai.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript / Node.js SDK

Официальная библиотека для Node.js и TypeScript. Пакет `@oblodai-npm/sdk`. Типы «из коробки», работает и
в JS, и в TS.

**Требования:** Node.js 18+.

***

## Установка

```bash theme={null}
npm install @oblodai-npm/sdk
# или: pnpm add @oblodai-npm/sdk / yarn add @oblodai-npm/sdk
```

<Info>
  Пакет выходит в npm под scope **`@oblodai-npm`** — ставьте именно `@oblodai-npm/sdk`.

  **Новое в v1.1.0** (текущая версия в npm): группы `batches` / `links` / `splits` / `payoutLinks`,
  методы `createBatch` / `refundBatch` / `sendEmail` / `resolve` — и **ломающее изменение**
  идемпотентности: вместо авто-`order_id` теперь заголовок `Idempotency-Key` (см. «Ошибки и повторы»).
</Info>

***

## Аутентификация (ключи из окружения)

Ключи берутся в кабинете [my.oblodai.com](https://my.oblodai.com) — см.
[Регистрация и ключи](/guides/get-keys).

```bash theme={null}
export OBLODAI_PUBLIC_ID=oblodai_ваш_public_id
export OBLODAI_SECRET=oblodai_ваш_secret
# необязательно: export OBLODAI_BASE_URL=https://api.oblodai.com
```

```ts theme={null}
import { OblodaiClient } from '@oblodai-npm/sdk';

const client = OblodaiClient.fromEnv(); // OBLODAI_PUBLIC_ID / OBLODAI_SECRET / OBLODAI_BASE_URL
```

Или явно:

```ts theme={null}
const client = new OblodaiClient({
  publicId: process.env.OBLODAI_PUBLIC_ID!,
  secret: process.env.OBLODAI_SECRET!,
  baseUrl: 'https://api.oblodai.com', // необязательно
});
```

***

## Быстрый старт: принять платёж

```ts theme={null}
const payment = await client.payments.create({
  amount: '10',
  currency: 'USD',
  order_id: 'order-1',
  to_currency: 'USDT',
  network: 'tron',
  url_callback: 'https://ваш-сайт.ру/oblodai/webhook',
  url_success: 'https://ваш-сайт.ру/thanks',
});

res.redirect(payment.url); // отправляем покупателя оплачивать
```

<Tip>
  Не хотите указывать валюту/сеть за покупателя? Не передавайте `to_currency` и `network` —
  получится «валюто-агностичная» ссылка, где покупатель сам выберет монету на странице оплаты.
</Tip>

***

## Ресурсы и методы

| Группа                            | Методы                                                                                                                                                                                                                                                                |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client.payments`                 | `create`, `info`, `history`, `services`, `qr`, `resend`, `refund`, `listAccepted`, `setAccepted`, `getAccuracy`, `setAccuracy`, `getAutorefund`, `setAutorefund`, `setDiscount`, `listDiscounts` · **с v1.1.0:** `createBatch`, `refundBatch`, `sendEmail`, `resolve` |
| `client.payouts`                  | `create`, `createMass`, `info`, `history`, `services`, `calculate`, `approve`, `getFeeConfig`, `setFeeConfig`, `getRefundFeeConfig`, `setRefundFeeConfig`, `refund` · **с v1.1.0:** `createBatch`                                                                     |
| `client.batches` **(v1.1.0)**     | `info`                                                                                                                                                                                                                                                                |
| `client.links` **(v1.1.0)**       | `create`, `list`, `info`, `toggle`, `publicGet`, `checkout` · `client.links` = синоним `client.paymentLinks`                                                                                                                                                          |
| `client.payoutLinks` **(v1.1.0)** | `create`, `createBatch` (до 500), `list`, `info`, `cancel` + публичные без подписи: `claimInfo(token)`, `claim(token, {address, memo?})`                                                                                                                              |
| `client.splits` **(v1.1.0)**      | `splitToAddress`, `splitToMerchant`, `listRules`, `deleteRule`, `getConfig`, `setConfig`                                                                                                                                                                              |
| `client.wallets`                  | `create`, `block`, `blockedAddressRefund`, `qr`                                                                                                                                                                                                                       |
| `client.account`                  | `balance`, `referral`, `transferToPersonal`, `vrcs`                                                                                                                                                                                                                   |
| `client.webhooks`                 | `register`, `deliveries`, `testPayment`, `testWallet`, `testPayout`                                                                                                                                                                                                   |
| `client.settings`                 | `listAutoWithdraw`, `setAutoWithdraw`, `deleteAutoWithdraw`, `listAllowlist`, `addAllowlist`, `removeAllowlist`, `enableAllowlist`                                                                                                                                    |
| `client.rates`                    | `list` (курсы), `currencies` (публичный каталог)                                                                                                                                                                                                                      |

Все методы возвращают `Promise` с уже развёрнутым `result`. Точные поля — в [Справочнике](/reference/overview).

<Info>
  Всё, что помечено «с v1.1.0», доступно начиная с версии **1.1.0** — если у вас стоит v1.0.x,
  обновите пакет (`npm update @oblodai-npm/sdk`). Свой ключ идемпотентности в методах создания —
  параметр `idempotency_key` (уйдёт в заголовок).
</Info>

```ts theme={null}
// Массовые операции: до 5000 элементов одним подписанным запросом (одна отметка rate-limit).
const sub = await client.payments.createBatch(
  [
    { amount: '10', currency: 'USD', order_id: 'a-1', to_currency: 'USDT', network: 'tron' },
    { amount: '20', currency: 'EUR', order_id: 'a-2', to_currency: 'USDT', network: 'tron' },
  ],
  { onError: 'continue' }, // 'continue' (по умолчанию) или 'stop'
);

const info = await client.batches.info(sub.batch_id, { limit: 100 }); // прогресс и результат по элементам

// Платёжная ссылка: платят многие, каждый платёж — свой инвойс. Работает без вашего бэкенда.
const link = await client.links.create({ amount_mode: 'open', currency: 'USD', title: 'Донат' });

// Сплит: доля каждого входящего платежа автоматически уходит партнёру.
await client.splits.splitToAddress('T...', 'tron', 10, 'партнёр А');

// Счёт на e-mail (письмо с кнопкой «Оплатить»).
await client.payments.sendEmail({ uuid: payment.uuid, email: 'buyer@example.com' });

// Резолв недоплаты: принять частичную оплату (глушит авто-возврат) или вернуть плательщику.
await client.payments.resolve({ uuid: payment.uuid, action: 'accept' }); // или action: 'refund'
```

**Крипто-чек за 4 строки** — выплата без адреса получателя (заберёт сам по ссылке):

```ts theme={null}
const check = await client.payoutLinks.create({
  amount: '25', currency: 'USDT', network: 'tron',
  reference: 'bonus-42', expires_in_hours: 72, email: 'winner@example.com',
});
console.log(check.claim_url); // отдайте получателю — он введёт свой адрес сам
```

* Задавайте `expires_in_hours` **явно**: без него чек живёт всего **1 час**.
* `claim_url` / `claim_token` возвращаются **только из `create`, один раз** — сохраните сразу.
* Дедупликация — поле `reference` (заголовок `Idempotency-Key` на этих эндпоинтах не действует).

Подробности — [Массовые операции](/guides/batch-operations),
[Платёжные ссылки](/guides/payment-links), [Сплит-платежи](/guides/split-payments),
[Счета на e-mail](/guides/email-invoices), [Крипто-чеки](/guides/payout-links),
[Резолв платежа](/reference/payment-resolve).

***

## Валюта цены и валюта расчёта

В примере выше `currency: 'USD'` — **валюта цены**, а `to_currency: 'USDT'` — **валюта расчёта**.
Это разные вещи:

* **`currency` (цена)** — одна из **23 фиатных валют**: USD, EUR, GBP, RUB, UAH, PLN, CZK, TRY, CNY,
  INR, BRL, CAD, AUD, CHF, AED, ZAR, MXN, IDR, THB, VND, NGN, JPY, KRW — **или любая монета**
  (USDT, BTC, TRX, …).
* **`to_currency` (расчёт)** — **только крипта.** Фиата здесь не бывает: баланс, выплаты и возвраты
  всегда в монете, шлюз не хранит фиат.
* У **JPY и KRW ноль знаков** после запятой (`amount: '10000'`, не `'10000.00'`); у остальных — 2.
* **KZT, KGS, UZS пока не поддерживаются** → `payment.unknown_currency`.
* Фиатная цена + **только** `network` без `to_currency` → `400 payment.to_currency_required`.
  Либо задайте `to_currency`, либо не задавайте **ни то, ни другое** (тогда монету выберет покупатель).

Полный список валют цены — `client.rates.currencies()`, поле `pricing_currencies`.

***

## Проверьте, что заработало

1. Запустите код создания платежа из «Быстрого старта». В ответе должен прийти `url` — это ссылка на
   hosted-страницу оплаты. Откройте её в браузере: если страница открылась, приём платежей настроен.
2. Чтобы реально **поймать вебхук** об оплате на локальной машине, нужен публичный HTTPS-адрес —
   поднимите туннель (ngrok / cloudflared) и укажите его URL в `url_callback`. Подробнее — в
   [Тестировании](/guides/testing) и [Настройке вебхуков](/guides/webhooks-setup).

***

## Вебхуки

```ts Шаг 1. Регистрация (один раз) и сохранение секрета theme={null}
const endpoint = await client.webhooks.register('https://ваш-сайт.ру/oblodai/webhook');
// сохраните endpoint.secret — им проверяются все вебхуки (это НЕ ваш API-секрет)
```

```ts Шаг 2. Приём (Express, сырое тело) theme={null}
import express from 'express';
import { constructWebhookEvent, OblodaiSignatureError, type WebhookEvent } from '@oblodai-npm/sdk';

app.post('/oblodai/webhook', express.raw({ type: '*/*' }), async (req, res) => {
  const raw = req.body as Buffer;
  const maybe = JSON.parse(raw.toString('utf8'));
  if (maybe.is_test) return res.send('ok'); // тестовые вебхуки не подписаны

  let event: WebhookEvent;
  try {
    event = constructWebhookEvent<WebhookEvent>(WEBHOOK_SECRET, raw, {
      timestamp: req.get('X-Webhook-Timestamp')!,
      signature: req.get('X-Webhook-Signature')!,
    });
  } catch (e) {
    if (e instanceof OblodaiSignatureError) return res.status(403).send('bad signature');
    throw e;
  }

  const info = await client.payments.info({ uuid: event.uuid });
  if (info.payment_status === 'paid' || info.payment_status === 'paid_over') {
    // пометить заказ info.order_id оплаченным (идемпотентно)
  }
  res.send('ok');
});
```

Оплаченными считайте только `paid` и `paid_over`.

***

## Ошибки и повторы

```ts theme={null}
import { OblodaiApiError, OblodaiConnectionError } from '@oblodai-npm/sdk';

try {
  await client.payouts.create({ /* ... */ });
} catch (e) {
  if (e instanceof OblodaiApiError) {
    e.code;        // "payout.insufficient_funds" — ветвитесь по коду
    e.status;      // HTTP-статус
    e.isRetriable; // временная ли
  }
}
```

Клиент **сам** повторяет 5xx/429/сетевые сбои с backoff и учётом `Retry-After` — **повторы включены по
умолчанию**. Отключить: `new OblodaiClient({ ..., retry: false })`.

**Повтор безопасен, но механизм зависит от версии:**

|                  | **v1.0.x** (предыдущая)                             | **v1.1.0** (текущая, в npm)                                         |
| ---------------- | --------------------------------------------------- | ------------------------------------------------------------------- |
| Защита от дублей | `order_id`: не задали — **SDK подставит свой**      | заголовок `Idempotency-Key`, одинаковый во всех внутренних повторах |
| Ваш `order_id`   | если не задан — в платеже будет сгенерированный SDK | уходит **как есть**, SDK не подставляет и не переписывает           |
| Свой ключ        | `idempotency_key` **не существует**                 | можно передать `idempotency_key` — уйдёт в заголовок                |

В обеих версиях таймаут/обрыв сети не создаст дубль счёта или перевода. Для **выплат** `order_id`
обязателен всегда — его задаёте вы (иначе `payout.order_id_required`).

***

## Логи и отладка

Включите логи одной переменной окружения — увидите каждый запрос/ответ/ретрай:

```bash theme={null}
export OBLODAI_LOG=debug   # уровни: debug · info · warn · error
```

Строки идут в stderr с префиксом `oblodai:` (метод, путь, статус, номер попытки, задержка ретрая).
**Секреты, подпись и тела запросов в лог не попадают.** Вместо переменной можно задать свой логгер: `new OblodaiClient({ ..., logger })`.

<Note>
  **Проверяете подпись вебхука статичным (старым) вектором?** По умолчанию действует окно свежести
  5 минут — старый `timestamp` отклонит replay-защита (валидная подпись → всё равно ошибка). Для
  офлайн-проверки задайте окно 0: `constructWebhookEvent(secret, raw, hdrs, { maxAgeSeconds: 0 })`.
</Note>

***

## Если не получилось

| Симптом                                 | Причина и что делать                                                                                                                                    |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OblodaiApiError` `401` на каждый вызов | Не заданы `OBLODAI_PUBLIC_ID`/`OBLODAI_SECRET` или перепутаны. SDK подписывает сам.                                                                     |
| `fromEnv()` бросает ошибку              | Обязательная переменная окружения не задана (её имя — в тексте ошибки).                                                                                 |
| Вебхук не проходит проверку             | Нужно **сырое** тело — используйте `express.raw({ type: '*/*' })`, а не `express.json()`; и **webhook‑секрет** из `webhooks.register()`, не API‑секрет. |
| Вебхук вообще не приходит               | Не зарегистрировали endpoint (`webhooks.register(url)`) или сайт недоступен из интернета.                                                               |
| `429`                                   | SDK повторяет сам с учётом `Retry-After`. Не поллите `payments.info()` в цикле. Много операций — шлите их [пачкой](/guides/batch-operations).           |

Полная диагностика — [Что делать, если не работает](/guides/troubleshooting).

## Связанные страницы

<CardGroup cols={2}>
  <Card title="Обзор SDK" href="/sdk/overview" icon="arrow-right" horizontal />

  <Card title="Сквозной пример приложения" href="/guides/end-to-end-example" icon="arrow-right" horizontal />

  <Card title="Настройка вебхуков" href="/guides/webhooks-setup" icon="arrow-right" horizontal />

  <Card title="Коды ошибок" href="/reference/errors-catalog" icon="arrow-right" horizontal />
</CardGroup>
