К содержимому

РазработчикамГайд

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

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

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

Руководство — для собственного сайта или приложения. Магазину на WooCommerce, OpenCart, PrestaShop или Magento 2 код не нужен: поставьте плагин для 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: 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. Чтобы покупатель не уходил с сайта, встройте ту же страницу виджетом оплаты. Ваша страница получает только 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, а не порядку прихода. Подробности и тестовый вектор — в руководстве по проверке подписи вебхуков.

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

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

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

Ошибка Что произошло Что сделать
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 оборачивает те же вызовы для 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, чтобы повторная доставка того же события подтверждалась без повторного изменения заказа.

Термины на странице

Читать дальше

Начните с тестового платежа

Создайте аккаунт, попросите у поддержки доступ к тестовой среде и посмотрите, как платёж проходит путь от pending до paid в тестовой сети.