Сайт на 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:
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. Номер заказа в качестве ключа идемпотентности вернёт тот же платёж при двойном клике или повторе:
$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-секрет остаётся на сервере.
header('Location: ' . $invoice['checkout_url'], true, 303);
exit;5. Проверьте вебхук и отметьте заказ оплаченным
Добавьте эндпоинт в кабинете в разделе Разработчикам → Вебхуки с событиями invoice.* и сохраните его секрет. Обработчик проверяет подпись по сырому телу, отклоняет старые метки времени, пропускает уже обработанные события и быстро отвечает 200:
$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, чтобы повторная доставка того же события подтверждалась без повторного изменения заказа.