# Как принимать криптовалюту на PHP

> Приём Bitcoin и USDT на сайте на PHP: подпись запроса через hash_hmac, создание платежа, переход на страницу оплаты и проверка подписи вебхука — с примерами кода.

Source: https://cryptolike.net/ru/guides/accept-crypto-payments-php
Published: 2026-09-24 · Updated: 2026-10-04 · Author: CryptoLike team

Сайт на PHP принимает криптовалюту через CryptoLike тремя фрагментами кода: функция, которая подписывает запросы к API через `hash_hmac`, вызов, который создаёт платёж по заказу, и обработчик вебхука, который отмечает заказ оплаченным. Кроме cURL, никаких расширений не нужно, а код работает на PHP 7.4 и 8.x — в Laravel, Symfony или на чистом PHP.

Руководство — для собственного сайта или приложения. Магазину на WooCommerce, OpenCart, PrestaShop или Magento 2 код не нужен: поставьте [плагин для WooCommerce](https://cryptolike.net/ru/integrations/woocommerce) или модуль для своей платформы.

## Что понадобится

Аккаунт CryptoLike, PHP-сервер, который может делать исходящие HTTPS-запросы, и публичный адрес `https://` для вебхука. Храните API-секрет и секрет вебхука в переменных окружения — в примерах это `CRYPTOLIKE_API_KEY_ID`, `CRYPTOLIKE_API_SECRET` и `CRYPTOLIKE_WEBHOOK_SECRET`, а также `CRYPTOLIKE_API_URL` для тестового адреса API (без него — рабочий). Сначала проверьте всё с тестовыми ключами в тестовых сетях, чтобы не тратить настоящие деньги.

## 1. Создайте тестовый API-ключ

В кабинете откройте **Разработчикам → API-ключи → Создать ключ** и дайте ключу права `create_invoice` и `read`. Ключи с префиксом `ck_test_` работают с тестовыми сетями. Тестовые ключи выдаёт тестовая среда: поддержка CryptoLike даст доступ к её кабинету, где вы создадите такой же ключ, и адрес её API (`CRYPTOLIKE_API_URL` в примерах). Рабочий кабинет выпускает только боевые ключи, а в основных сетях тестовый ключ получает `401 auth.test_key`; на рабочий ключ переходите, когда весь сценарий заработает. Секрет показывается один раз: сразу положите его в окружение сервера, а не в репозиторий или страницу.

## 2. Подписывайте запросы через hash_hmac

Каждый запрос несёт три заголовка: ID ключа, время Unix в секундах и подпись HMAC-SHA256 от четырёх строк — метки времени, метода, пути с query и SHA-256 тела. Эта функция делает всё через cURL:

```php
function cryptolike(string $method, string $path, ?array $payload = null, ?string $idempotencyKey = null): array
{
    $body = $payload === null ? '' : json_encode($payload, JSON_UNESCAPED_SLASHES);
    $ts = (string) time();
    $canonical = implode("\n", [$ts, strtoupper($method), $path, hash('sha256', $body)]);
    $headers = [
        'X-API-Key: ' . getenv('CRYPTOLIKE_API_KEY_ID'),
        'X-Timestamp: ' . $ts,
        'X-Signature: ' . hash_hmac('sha256', $canonical, getenv('CRYPTOLIKE_API_SECRET')),
        'Content-Type: application/json',
    ];
    if ($idempotencyKey !== null) {
        $headers[] = 'Idempotency-Key: ' . $idempotencyKey;
    }
    $ch = curl_init((getenv('CRYPTOLIKE_API_URL') ?: 'https://api.cryptolike.net') . $path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_POSTFIELDS => $body === '' ? null : $body,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
    ]);
    $response = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    $data = json_decode((string) $response, true) ?? [];
    if ($status >= 400) {
        throw new RuntimeException(($data['error']['code'] ?? 'http_' . $status) . ': ' . ($data['error']['message'] ?? ''));
    }
    return $data;
}
```

Хешировать нужно ровно ту строку, которую вы отправляете, поэтому функция кодирует тело один раз. Перед первым настоящим вызовом сверьте подпись с эталонным вектором из раздела [аутентификация API](https://cryptolike.net/ru/docs/auth): `401 auth.invalid_signature` почти всегда означает другую строку тела или время в миллисекундах.

## 3. Создайте платёж по заказу

Создавайте один платёж на заказ с сервера. Укажите цену в своей валюте и дайте покупателю выбрать монету или передайте `coin_id` из `GET /v1/currencies`, чтобы зафиксировать её. Без `coin_id` страница оплаты показывает монеты, доступные для инвойсов, с ориентировочной суммой ≈ у каждой; чтобы предложить для этого заказа только часть из них, добавьте `allowed_coin_ids` — id монет с `invoice_enabled: true` из подписанного `GET /v1/currencies`. Номер заказа в качестве ключа идемпотентности вернёт тот же платёж при двойном клике или повторе:

```php
$invoice = cryptolike('POST', '/v1/invoices', [
    'amount' => '49.90',
    'currency' => 'USD',
    'order_id' => $order->id,
    'idempotency_by_order_id' => true,
    'return_url' => 'https://example.com/orders/' . $order->id,
], 'order-' . $order->id);

$order->saveInvoiceId($invoice['id']);
```

Суммы — десятичные строки, а не float: `'49.90'`, а не `49.9`. В ответе есть `id`, `status` (`new`) и `checkout_url`.

## 4. Отправьте покупателя на страницу оплаты

Перенаправьте на `checkout_url`: там видны сумма, сеть, адрес с QR-кодом, оставшееся время и статус в реальном времени, а после оплаты покупатель вернётся на `return_url`. Чтобы покупатель не уходил с сайта, встройте ту же страницу [виджетом оплаты](https://cryptolike.net/ru/docs/widget). Ваша страница получает только ID инвойса, API-секрет остаётся на сервере.

```php
header('Location: ' . $invoice['checkout_url'], true, 303);
exit;
```

## 5. Проверьте вебхук и отметьте заказ оплаченным

Добавьте эндпоинт в кабинете в разделе **Разработчикам → Вебхуки** с событиями `invoice.*` и сохраните его секрет. Обработчик проверяет подпись по сырому телу, отклоняет старые метки времени, пропускает уже обработанные события и быстро отвечает `200`:

```php
$raw = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$secret = (string) getenv('CRYPTOLIKE_WEBHOOK_SECRET');
$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $raw, $secret);

if ($secret === '' || !ctype_digit($ts) || abs(time() - (int) $ts) > 300 || !hash_equals($expected, $signature)) {
    http_response_code(401);
    exit;
}

$event = json_decode($raw, true);
if (!alreadyProcessed($event['event_id'])) {
    if ($event['type'] === 'invoice.paid') {
        markOrderPaid($event['data']['invoice']['order_id']);
    }
    rememberEvent($event['event_id']);
}
http_response_code(200);
```

Читайте тело через `php://input`, а не `$_POST`, и не декодируйте и не кодируйте его заново до проверки. Доставка «хотя бы один раз» и может нарушать порядок, поэтому верьте статусу внутри `data`, а не порядку прихода. Подробности и тестовый вектор — в руководстве [по проверке подписи вебхуков](https://cryptolike.net/ru/guides/verify-crypto-payment-webhooks).

## 6. Проверьте в тестовой сети и запускайте

Создайте платёж тестовым ключом, оплатите его тестовыми монетами в той сети, что указана на странице, и дождитесь `invoice.pending`, а затем `invoice.paid`. Потом обработайте остальные исходы: `invoice.underpaid` (покупатель может доплатить на тот же адрес до срока), `invoice.expired` (предложите новый платёж) и `invoice.reverted` (остановите выполнение заказа). Замените тестовый ключ рабочим и пройдите чек-лист запуска из [руководства разработчика](https://cryptolike.net/ru/docs).

## Частые ошибки

| Ошибка | Что произошло | Что сделать |
|---|---|---|
| `401 auth.invalid_signature` | Подписанная строка не совпадает с запросом | Хешируйте ровно отправляемое тело; подписывайте путь вместе с query; время — в секундах, не в миллисекундах |
| `401 auth.expired_timestamp` | Часы сервера расходятся больше чем на 5 минут | Синхронизируйте часы через NTP |
| `403 auth.permission_denied` | У ключа нет нужного права | Добавьте ключу `create_invoice` или `read` в кабинете |
| `409 conflict` | У заказа уже есть платёж на другую сумму или по нему пришли деньги | Возьмите `details.invoice_id`; второй платёж не создавайте |
| `422 idempotency.mismatch` | Тот же `Idempotency-Key` пришёл с другим телом | Один ключ — на один заказ и один запрос |
| `422 no_allowed_coins` | Ни одна монета из `allowed_coin_ids` не доступна для инвойсов в этой валюте | Передайте другие id или уберите поле, чтобы предложить все монеты |

## Удобнее с SDK?

Официальный [PHP SDK](https://cryptolike.net/ru/integrations/sdk-php) оборачивает те же вызовы для PHP 7.4 – 8.x без зависимостей: подпись запросов, повторы при `429` и `5xx`, один открытый инвойс на заказ и `Webhook::verify()`. Код этого руководства — то, что SDK делает внутри, так что начать можно с любого варианта.

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

### Нужен ли Composer или библиотека, чтобы принимать криптовалюту на PHP?

Нет. Для подписи запроса CryptoLike достаточно `hash()` и `hash_hmac()` из стандартной библиотеки PHP, а запросы идут через cURL. Функции из шага 2 хватает на всю интеграцию. Официальный PHP SDK добавляет повторы, постраничные списки и помощники для магазинов и тоже работает без Composer — достаточно скопировать его файлы в проект.

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

Да. Создайте платёж на сервере, как в шаге 3, а затем встройте ту же страницу оплаты виджетом вместо перенаправления. Сначала добавьте адрес сайта в разрешённые источники: карточка **Разрешённые origin** на странице «Настройки» в кабинете. Виджет читает только публичные данные платежа, поэтому API-секрет в браузер не попадает.

### Как понять, к какому заказу относится вебхук?

Объект `invoice` в вебхуке содержит `order_id`, который вы передали при создании платежа, и `metadata`, если вы её отправляли. Найдите заказ по этому значению, проверьте статус внутри `data` и сохраните `event_id`, чтобы повторная доставка того же события подтверждалась без повторного изменения заказа.
