# Host-to-host (H2H): оплата криптовалютой в вашем собственном интерфейсе

> Как показать платёж CryptoLike на своём экране: адрес, точная сумма, сеть, QR и таймер из инвойса, выбор монеты через API, подтверждение заказа по вебхуку.

Source: https://cryptolike.net/ru/docs/h2h
Updated: 2026-10-18

Host-to-host (H2H) — это схема, в которой ваш сервер создаёт платёж через API CryptoLike, а показывает его ваш собственный интерфейс: страницу CryptoLike плательщик не открывает. В каждом ответе с инвойсом есть всё, что нужно экрану оплаты, — адрес, точная сумма, сеть, данные для QR и срок оплаты, а подписанный вебхук сообщает вашему серверу, что платёж подтверждён. Ниже — когда выбирать H2H, как идёт процесс, что обязательно показать плательщику и что делать, если он отправил меньше, больше или слишком поздно.

## Когда выбирать H2H, страницу оплаты или виджет

Во всех трёх вариантах один и тот же инвойс, одни и те же статусы и вебхуки. Разница в том, кто рисует экран оплаты.

| Вариант | Кто рисует экран | Когда выбирать | Что нужно сделать |
|---|---|---|---|
| Страница оплаты | CryptoLike, по `checkout_url` | Нужен самый короткий путь: редирект и вебхук | Редирект с вашего бэкенда |
| Виджет оплаты | CryptoLike, во фрейме на вашей странице | Покупатель остаётся в вашей корзине, и страница CryptoLike может появиться внутри неё | Ваш origin в списке разрешённых, скрипт и правило CSP |
| H2H | Вы | Мобильное приложение, терминал, бот в мессенджере или корзина, которая целиком должна выглядеть как ваш продукт | Экран оплаты со всеми элементами из списка ниже |

Страница оплаты и виджет уже показывают предупреждение о сети, таймер, живой статус и тексты для недоплаты или истёкшего платежа на английском, русском и испанском. В H2H всё это — ваша работа, поэтому начните со [страницы оплаты или виджета](https://cryptolike.net/ru/docs#customer-payment), если интерфейс не обязан быть полностью вашим. Виджет описан в [руководстве по виджету оплаты](https://cryptolike.net/ru/docs/widget).

## Как проходит платёж

1. **Ваш сервер создаёт инвойс** — `POST /v1/invoices` с подписью вашего API-ключа и заголовком `Idempotency-Key`. Передайте `coin_id`, чтобы сразу зафиксировать монету, или не передавайте — тогда плательщик выберет её в вашем интерфейсе.
2. **Если монета не задана, плательщик выбирает её.** Сервер читает `GET /v1/invoices/{id}/coin-options`, интерфейс показывает список, сервер фиксирует выбор запросом `POST /v1/invoices/{id}/coin`.
3. **Интерфейс показывает платёж** из ответа с инвойсом: сеть, адрес, `payer_amount`, QR, таймер до `ttl_expires_at` и статус.
4. **Сервер следит за статусом** по вебхукам `invoice.*`, а счётчик подтверждений берёт из `GET /v1/invoices/{id}` — не чаще раза в несколько секунд. Ваша страница спрашивает ваш сервер, а не API CryptoLike.
5. **Сервер подтверждает заказ** только по проверенному вебхуку `invoice.paid`, после чего интерфейс показывает подтверждение.

Секрет API остаётся на вашем сервере. Браузер или приложение получают только то, что решил показать ваш сервер, и запросы не подписывают. Подпись описана в разделе [аутентификация](https://cryptolike.net/ru/docs/auth).

## Создание инвойса

Запрос ниже сразу фиксирует монету — USDT в сети TRON. Без `coin_id` в ответе поля монеты, `address` и платёжный блок равны `null`, пока плательщик не выберет монету.

```http
POST /v1/invoices HTTP/1.1
Host: api.cryptolike.net
Content-Type: application/json
Idempotency-Key: order-10042
X-API-Key: <key_id>
X-Timestamp: <unix seconds>
X-Signature: <HMAC-SHA256 of the request>

{"amount": "49.90", "currency": "USD", "coin_id": "6f1c2a3e-8d4b-4c5a-9e7f-0a1b2c3d4e5f", "order_id": "10042", "description": "Order #10042 — annual plan", "ttl_seconds": 1800, "fee_paid_by": "merchant"}
```

В ответ приходит инвойс (значения — из примера в справочнике API):

```json
{
  "id": "4e2bc88c-cb79-4afc-b864-c1ba8e9d50c9",
  "status": "new",
  "currency": "USD",
  "amount": "49.90",
  "coin_id": "6f1c2a3e-8d4b-4c5a-9e7f-0a1b2c3d4e5f",
  "ticker": "USDT",
  "network": "tron",
  "address": "TXYZopYRdj2D9XRtbG411XZZ3kM5VW9t7g",
  "amount_crypto": "49.90",
  "payer_amount": "49.90",
  "paid_amount": "0",
  "remaining_amount": "49.90",
  "overpaid_amount": "0",
  "amount_fiat": "49.90",
  "fiat_currency": "USD",
  "rate_locked": "1.0004",
  "rate_locked_at": "2026-09-16T12:00:00Z",
  "late_payment": false,
  "fee_paid_by": "merchant",
  "order_id": "10042",
  "description": "Order #10042 — annual plan",
  "return_url": null,
  "webhook_url": null,
  "metadata": null,
  "allowed_coin_ids": null,
  "checkout_url": "https://cryptolike.net/pay/4e2bc88c-cb79-4afc-b864-c1ba8e9d50c9",
  "ttl_expires_at": "2026-09-16T12:30:00Z",
  "expired_at": null,
  "created_at": "2026-09-16T12:00:00Z",
  "updated_at": "2026-09-16T12:00:00Z",
  "payment_uri": "tron:TXYZopYRdj2D9XRtbG411XZZ3kM5VW9t7g?amount=49.9&token=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
  "qr_payload": "TXYZopYRdj2D9XRtbG411XZZ3kM5VW9t7g",
  "confirmations": null,
  "required_confirmations": 20
}
```

Поля, которые нужны экрану:

- `payer_amount` и `ticker` — ровно столько плательщик должен отправить. При `fee_paid_by: payer` комиссия платформы уже включена, поэтому `payer_amount` больше, чем `amount_crypto`. Показывайте `payer_amount`, а не `amount` и не `amount_crypto`. Комиссии — на странице [тарифов](https://cryptolike.net/ru/pricing).
- `network` — `btc`, `ltc`, `eth` или `tron`. Один тикер бывает в двух сетях (USDT в TRON и в Ethereum), и именно сеть определяет, куда отправлять.
- `address` — адрес для оплаты этого инвойса.
- `payment_uri` — ссылка, которая открывает кошелёк с адресом и суммой, которую ещё осталось оплатить: BIP-21 в Bitcoin и Litecoin, EIP-681 в Ethereum, ссылка `tron:` в TRON.
- `qr_payload` — что закодировать в QR: голый адрес во всех сетях, без суммы. Многие плательщики платят с биржи, а форма вывода на бирже принимает только адрес. Если нужен «QR с суммой» для кошельков, предложите его переключателем в Bitcoin и Litecoin и закодируйте там `payment_uri`.
- `ttl_expires_at` — срок оплаты. Его задаёт `ttl_seconds` — от 60 секунд до 30 дней; без него действует настройка аккаунта (по умолчанию 30 минут). Для цены в USD или другой фиатной валюте курс в `rate_locked` до этого момента не меняется.
- `confirmations` и `required_confirmations` — счётчик подтверждений; `confirmations` равен `null`, пока ничего не пришло.

Храните каждую сумму той строкой, которую вернул API: не округляйте её и не пропускайте через число с плавающей точкой.

## Выбор монеты в вашем интерфейсе

Для инвойса без `coin_id` список монет и выбор работают через два запроса — это тот же список и та же операция, что на странице оплаты.

```http
GET /v1/invoices/4e2bc88c-cb79-4afc-b864-c1ba8e9d50c9/coin-options
```

```json
{
  "coin_options": [
    { "coin_id": "6f1c2a3e-8d4b-4c5a-9e7f-0a1b2c3d4e5f", "ticker": "USDT", "name": "Tether USD", "network": "tron", "network_name": "TRON",
      "decimals": 6, "confirmations": 20, "estimated_amount": "49.90", "rate_as_of": "2026-09-16T12:00:03Z",
      "estimate_reason": null, "estimate_message": null },
    { "coin_id": "a01d6e2b-3c4f-4a5b-8c7d-9e0f1a2b3c4d", "ticker": "BTC", "name": "Bitcoin", "network": "btc", "network_name": "Bitcoin",
      "decimals": 8, "confirmations": 2, "estimated_amount": null, "rate_as_of": null,
      "estimate_reason": "rate_stale",
      "estimate_message": { "key": "checkout.estimate.rate_stale", "default": "The exchange rate is updating. Choose this currency to see the exact amount." } }
  ]
}
```

- Рядом с каждым тикером показывайте `network_name`, а `estimated_amount` — со знаком `≈`: это оценка. Если она равна `null`, покажите вместо неё текст из `estimate_message`; монету всё равно можно выбрать.
- В список попадают только монеты, которые вы предлагаете для инвойсов и которые подходят к валюте инвойса; если у инвойса есть `allowed_coin_ids`, список сужается до них. Монеты на обслуживании в него не входят.
- Если монета уже выбрана, запрос отвечает `409` с `details.coin_id`, а в самом инвойсе уже есть адрес.

Зафиксируйте выбор со своего сервера, с `Idempotency-Key`:

```http
POST /v1/invoices/4e2bc88c-cb79-4afc-b864-c1ba8e9d50c9/coin HTTP/1.1
Content-Type: application/json
Idempotency-Key: order-10042-coin

{"coin_id": "6f1c2a3e-8d4b-4c5a-9e7f-0a1b2c3d4e5f"}
```

В ответ приходит инвойс с `address`, `payer_amount`, `payment_uri`, `qr_payload` и `ttl_expires_at` — в той же форме, что выше. При выборе платёж оценивается заново и курс фиксируется, поэтому показывайте `payer_amount` из этого ответа, а не оценку. Отказы: `400` с `details.field: coin_id` — монеты нет среди вариантов; `409` — монета уже выбрана (даже та же самая) или инвойс уже не в статусе `new`; `503` — курс или новый адрес сейчас недоступны: подождите несколько секунд и повторите тот же запрос. Повтор с тем же `Idempotency-Key` вернёт первый ответ. Монету можно выбрать один раз; если у плательщика открыта ещё и страница оплаты, она сразу переключится на платёж.

## Что обязательно показать на экране оплаты

Все эти элементы есть на странице оплаты CryptoLike; на вашем экране нужен каждый из них.

| Элемент | Поле | Зачем |
|---|---|---|
| Сеть словами, для токена — со стандартом (TRON, TRC20) | `network`, `ticker` | Одинаковые тикеры и форматы адресов есть в нескольких сетях |
| Предупреждение о неправильной сети рядом с адресом | `ticker`, `network` | «Отправляйте на этот адрес только USDT в сети TRON. Средства, отправленные в другой сети, вернуть невозможно.» |
| Адрес с кнопкой копирования | `address` | Плательщик вставляет его в кошелёк; средства на адрес с опечаткой вернуть нельзя |
| Точная сумма с кнопкой копирования | `payer_amount`, `ticker` | Перевод меньше суммы становится недоплатой |
| QR-код и сумма рядом с ним | `qr_payload` | QR содержит только адрес во всех сетях |
| Ссылка «Открыть в кошельке» | `payment_uri` | Одно нажатие на телефоне, где установлен кошелёк |
| Сколько осталось времени | `ttl_expires_at` | После срока инвойс истекает, а более поздний перевод считается поздней оплатой |
| Статус и подтверждения, пока платёж ждёт | `status`, `confirmations`, `required_confirmations` | «Платёж обнаружен, 3 из 20 подтверждений» — плательщик не отправит второй раз |
| Экран результата | `status`, `paid_amount`, `remaining_amount` | Оплачен, недоплачен с остатком, истёк или отменён — и следующий шаг для каждого случая |

Отсчитывайте таймер по часам вашего сервера, а не устройства плательщика. Когда время вышло, скройте адрес и QR и предложите создать новый платёж. Статусы — `new`, `pending`, `paid`, `underpaid`, `expired` и `cancelled`; почему перевод ждёт, прежде чем засчитаться, объясняет статья о [подтверждениях](https://cryptolike.net/ru/glossary/confirmations).

## Недоплата, переплата и поздняя оплата

- **Недоплата.** Подтверждённый перевод меньше `payer_amount` переводит инвойс в `underpaid` и отправляет `invoice.underpaid`; нехватка в пределах допуска недоплаты вашего аккаунта всё равно считается `paid`. Инвойс открыт для доплаты на тот же адрес до `ttl_expires_at`: покажите `remaining_amount` и обновлённый `payment_uri` — теперь в нём остаток. После срока недоплата окончательная; попросите плательщика связаться с вами. Подробнее — [недоплата](https://cryptolike.net/ru/glossary/underpaid-payment).
- **Переплата.** Статус `paid`, а `overpaid_amount` больше нуля; его несёт `invoice.paid`, который приходит повторно, когда следующий перевод увеличивает переплату. Вся сумма зачисляется на ваш баланс; разницу урегулируйте с покупателем.
- **Поздняя оплата.** Перевод, подтверждённый после закрытия инвойса (`expired`, `cancelled` или окончательный `underpaid`), всё равно зачисляется на ваш баланс: инвойс получает `late_payment: true`, вам приходит `invoice.late_payment`, статус не меняется. Так же бывает, когда перевод к сроку уже обнаружен, но ещё не подтверждён: инвойс истекает, а перевод приходит как поздняя оплата. Решите, выполнить заказ или вернуть деньги. Подробнее — [истёкший платёж](https://cryptolike.net/ru/glossary/expired-payment).
- **Откат.** Если реорганизация сети убирает подтверждённый перевод, вам приходит `invoice.reverted` с пересчитанными суммами. Остановите выполнение заказа до нового `invoice.paid`.

## Подтверждайте заказ только по вебхуку

Выполняйте заказ, когда ваш сервер получил `invoice.paid` с верной [подписью вебхука](https://cryptolike.net/ru/glossary/webhook-signature), — а не когда плательщик нажал «Я оплатил» и не по статусу, который увидела ваша страница. Доставка — как минимум один раз: сохраняйте `event_id`, отвечайте `2xx` в течение 10 секунд и пропускайте повторы. Вебхуки одинаковы для H2H, страницы оплаты и виджета и не содержат `payment_uri`, `qr_payload` и счётчика подтверждений — когда они нужны, читайте `GET /v1/invoices/{id}`. Типы событий, повторы и тестовый вектор — в [справочнике по вебхукам](https://cryptolike.net/ru/docs/webhooks), пошагово — в руководстве [как проверять вебхуки платежей](https://cryptolike.net/ru/guides/verify-crypto-payment-webhooks).

Перед запуском проведите в тестовой сети по одному платежу на каждый случай: оплачен, недоплачен и потом доплачен, истёк и пришёл с опозданием. Тестовая среда описана в [руководстве разработчика](https://cryptolike.net/ru/docs#sandbox).

## Вопросы и ответы

### Нужен ли виджет для H2H?

Нет. В H2H экран рисует ваш интерфейс по полям инвойса, и в браузер плательщика ничего от CryptoLike не загружается. Виджет — другой вариант: страница оплаты CryptoLike во фрейме внутри вашей корзины.

### Можно ли всё-таки отправить плательщика на страницу оплаты?

Да. У каждого инвойса есть `checkout_url`, и страница оплаты показывает тот же инвойс. Монета, выбранная через API, сразу переключает открытую страницу на платёж, поэтому страницу можно предложить как запасной вариант — например, плательщику, у которого кошелёк на другом устройстве.

### Меняются ли вебхуки в H2H?

Нет. События, подпись и повторы те же, что со страницей оплаты. Платёжного блока инвойса — `payment_uri`, `qr_payload`, `confirmations` и `required_confirmations` — в теле вебхука нет; читайте инвойс через `GET /v1/invoices/{id}`.

### Какую сумму отправляет плательщик, если комиссию платит он?

`payer_amount`. При `fee_paid_by: payer` в неё уже входит комиссия платформы, а `amount_crypto` — то, что получаете вы. Показывайте `payer_amount` и храните её той строкой, которую вернул API.
