Интеграция магазина с QryptoPay
Эта статья — продолжение настройки платёжной страницы. К этому моменту у вас уже должны быть готовы модуль, мерчант, кошелёк для приёма платежей и платёжная страница — если что-то из этого ещё не сделано, начните с быстрого старта.
Осталось собрать полный цикл оплаты: подключить к терминалу ваш магазин, научиться генерировать платёжные ссылки и принимать уведомления о платежах по вебхуку.
💡 Не хотите разбираться в интеграции сами?
Наша команда может подключить QryptoPay к вашему магазину — просто напишите нам. Стоимость зависит от сложности проекта и того, на чём он сделан: язык, фреймворк или CMS.
Токены терминала
У каждого терминала — своя пара токенов, публичный и приватный: ими подтверждается, что платёжные намерения и уведомления о них не подделаны. Пока пара не выпущена, терминал не может формировать платёжные ссылки.
Выпустить её можно так: откройте мерчант, найдите карточку нужного терминала и нажмите иконку ключа с подсказкой «Сгенерировать токены», а в диалоге «Создать токены для терминала» — кнопку «Сгенерировать». Панель покажет приватный токен: скопируйте его кнопкой рядом с полем и подтвердите кнопкой «Я скопировал».
⚠️ Приватный токен показывается один раз
На стороне QryptoPay он не хранится, поэтому увидеть его повторно нельзя: если закрыть диалог, не скопировав токен, придётся выпускать новую пару — а прежняя станет недействительной сразу после генерации.
Подробнее — чем публичный токен отличается от приватного и когда пару стоит перевыпускать — в статье Как сгенерировать токены терминала.
Шаг 1. Настройка интеграции
Ключевой элемент интеграции — терминал. У каждого терминала есть следующие параметры:
- ID — уникальный идентификатор;
- Пара токенов — публичный и приватный (используются для идентификации платежей);
- Вебхук — URL-адрес вашего магазина, на который QryptoPay отправляет уведомления о статусах платежей;
- Ключ для вебхука — используется для авторизации уведомлений на вебхуке.
⚠️ Важно
Приватный токен необходимо хранить в надёжном месте. Если он будет скомпрометирован, злоумышленники могут нарушить логику обработки ваших платежей. Поэтому после генерации QryptoPay не хранит приватный токен на своей стороне.
В случае утери или компрометации вы можете сгенерировать новую пару токенов — не забудьте обновить значения на стороне вашего магазина.
Ключ для вебхука менее критичен, но также рекомендуется не раскрывать его третьим лицам, чтобы исключить подделку уведомлений на вебхук.
Рекомендуем начинать подключение с тестового терминала — в отличие от основного, он позволяет проверить корректность интеграции без фактического перевода криптовалюты.
Сначала сгенерируйте пару токенов для тестового терминала и сохраните у себя приватный токен.
В настройках вашего магазина сохраните параметры терминала в переменных окружения. Например, файл .env может выглядеть так:
TERMINAL_ID: <ID терминала>
PRIVATE_TOKEN: <сгенерированный приватный токен>
WEBHOOK_KEY: <ключ для вебхука из настроек>
PAYMENT_URL: <домен платежной страницы>Далее необходимо реализовать метод, который генерирует токен оплаты, используемый при создании платёжной ссылки на стороне QryptoPay.
Ниже приведён пример реализации такого метода на псевдокоде:
function generate_payment_token(private_key_b64, terminal_uuid) -> string
# декодируем приватный ключ из base64/base64url в seed (сырые байты)
seed := decode_base64_any(private_key_b64)
# генерируем уникальный nonce (обычно uuid4)
nonce := uuid_v4()
# собираем payload (обязательные поля + данные платежа)
payload := {
ts: current_unix_time_seconds(),
nonce: nonce,
terminal_uuid: terminal_uuid, // ID терминала в QryptoPay
amount_fiat: transaction.amount, // сумма к оплате в USD, в формате 00.00, decimal, больше 0
payment_mid: transaction.uuid, // ID транзакции внутри вашего магазина, string
back_to_store_link: link, // ссылка на ваш сайт, чтобы клиент мог вернуться после оплаты (можно добавить параметры если нужно), string
customer: {
id: customer.uuid, // ID клиента из вашей системы (обязательно), string
email: customer.email or "" // email клиента из вашей системы (опционально), string
},
metadata: { // любые другие параметры, которые вы хотите передать
key: value // в формате metadata.key=value
}
}
# сериализуем payload в детерминированное (каноничное) представление байт
payload_bytes := canonical_encode(payload)
# кодируем payload в безопасный для передачи формат
payload_part := base64url_no_padding(payload_bytes)
# рассчитываем криптографическую часть токена на основе приватного ключа и payload_part
proof_bytes := sign(seed, bytes(payload_part, ASCII))
# кодируем криптографическую часть токена
proof_part := base64url_no_padding(proof_bytes)
# итоговый формат ключа: "<payload_b64u>.<proof_b64u>"
return payload_part + "." + proof_part
end⚠️ Важно
Для генерации токена оплаты, помимо данных транзакции, необходимо передать три обязательных параметра: текущую временную метку, nonce (рекомендуется uuid4) и ID терминала.
Временная метка используется для расчёта срока жизни ссылки (чуть больше 1 часа). Nonce нужен для безопасности и предотвращает создание нескольких платёжных намерений по одной ссылке (защита от спама). Без ID терминала QryptoPay не сможет обработать платёж.
Используя полученный токен, выполните POST-запрос в QryptoPay, чтобы получить платёжную ссылку. Например:
POST /public/api/payments/intents/create/
Host: qpay.yoursite.com
Content-Type: application/json
{
"key": "токен оплаты"
}⚠️ Важно
На момент выполнения запроса платёжная страница должна быть уже создана на сервере, где установлен QryptoPay.
В случае успешного выполнения запроса вы получите ответ с платёжной ссылкой. Эту ссылку необходимо передать клиенту для перехода к оплате.
{
"service_id": "be535ba0-7f84-4cd3-9454-b26c4a938479",
"url": "https://qpay.yoursite.com/?payment=be535ba0-7f84-4cd3-9454-b26c4a979225",
"expires_at": "2026-01-30T08:21:56.526112Z"
}В случае ошибки сервер может вернуть один из следующих кодов:
- 400 — некорректный запрос (ошибка формата или отсутствуют обязательные параметры).
- 403 — неверная подпись запроса или токен оплаты истёк.
- 409 — повторное использование одноразового токена. Переданный
nonceуже использовался для данного терминала. - 444 — временный запрет на создание платёжного намерения (например, из-за ограничений лицензии или превышения лимитов).
- 445 — создание платёжных намерений запрещено — лицензия исчерпана или доступ ограничен на постоянной основе.
- 500 — внутренняя ошибка сервера.
⚠️ Важно
Если клиент перешёл по платёжной ссылке, выбрал валюту и начал процесс оплаты (нажал кнопку Продолжить), изменить выбранную валюту он уже не сможет. В этом случае клиенту необходимо вернуться на ваш сайт и сформировать новую ссылку на оплату. Такое поведение реализовано в целях безопасности.
⚠️ Важно
Если вы передаёте email клиента в payload на сторону QryptoPay, и в дальнейшем email клиента изменится, при следующем платеже QryptoPay найдёт клиента по ID и, в случае несовпадения email, перезапишет его. Обновлённый email будет применён также и к ранее созданным платежам.
Общий алгоритм генерации платёжной ссылки выглядит следующим образом:
- в вашем магазине создаётся платёжное намерение (например, транзакция);
- сумма к оплате конвертируется в USD (QryptoPay принимает входящие суммы только в USD);
- формируется токен оплаты с необходимыми параметрами, который направляется на ваш сервер с QryptoPay;
- QryptoPay возвращает платёжную ссылку, которую необходимо передать клиенту для перехода к оплате.
После этого клиент совершает платёж на платёжной странице и при необходимости возвращается в магазин. Уведомление об успешном или неуспешном платеже поступит в ваш магазин с задержкой, так как транзакции в блокчейне должны дождаться подтверждений сети. Обычно это занимает от 1–2 минут (Tron, USDT TRC-20) до 10–30 минут (Bitcoin).
Теперь вы можете сформировать платёжную ссылку, перейти по ней, выбрать валюту и нажать кнопку оплаты. Если всё настроено корректно, платёж отобразится в вашем мерчанте QryptoPay в тестовом терминале.
Шаг 2. Настройка вебхука
После того как клиент выполнит оплату, QryptoPay начнёт сканировать блокчейны в поисках соответствующей транзакции. Если оплата выполнена строго по инструкциям на платёжной странице, на вебхук будет отправлено уведомление об успешном платеже.
Уведомление отправляется HTTP-запросом со следующими параметрами:
POST /your/webhook/url
Content-Type: application/json
X-Term-UUID: 2671f44b-a025-44d3-b2f1-a0ea07b8acb7
X-Timestamp: 1738150000
X-Body-SHA256: 9c4d2b0a2f5b7d6c8a... # 64 hex символа
X-Signature: 7f1c3d5e9a... # 64 hex символа
{
... JSON-тело уведомления ...
}Для обработки уведомлений вам потребуется метод, который валидирует подпись вебхука и подтверждает, что запрос не был перехвачен и изменён. Ниже приведён пример такой проверки на псевдокоде:
function validate_webhook_response() -> bool
# собираем сообщение для HMAC: "<terminal_uuid>:<ts>:<body_sha256_hex>"
message_str := string(term_uuid_header) + ":" + string(ts) + ":" + computed_body_hash
message_bytes := utf8_bytes(message_str)
# вычисляем ожидаемую подпись: HMAC-SHA256(secret, message), hex
expected_sig := hmac_sha256_hex(key = utf8_bytes(secret), msg = message_bytes)
# сравниваем подпись в постоянное время
if not constant_time_equals(expected_sig, sig_header) then
return false
end
return true
end⚠️ Важно
Рекомендуем считать запрос вебхука недействительным, если выполняется хотя бы одно из условий:
- отсутствует любой из обязательных заголовков;
X-Term-UUIDне совпадает с ID терминала, который использовался при генерации токена оплаты для этой транзакции;X-Timestampне является числом (int) или с момента указанной временной метки прошло более 300 секунд (рекомендуемое значение).
Если проверка прошла успешно, вы можете десериализовать тело запроса. В результате вы получите объект примерно следующего вида:
{
"payment_result": payment_result, // результат платежа: success, mismatch, unexpected
"amount_coins": str(coins), // сумма в криптовалюте, формат 00.00
"expected_amount_coins": "0.00", // ожидалось по счёту в криптовалюте, формат 00.00
"is_underpaid": false, // true, если пришло меньше ожидаемого
"amount_fiat": str(amount_usd), // сумма в USD, формат 00.00
"surcharge_fiat": str(surcharge), // удержанная комиссия в USD, формат 00.00, 0.00 если комиссия выключена
"amount_fiat_net": str(net), // остаток вам за вычетом комиссии, USD, формат 00.00; amount_fiat = amount_fiat_net + surcharge_fiat
"fiat_code": "USD", // фиатная валюта
"coins_asset": "USDC", // криптовалюта: BTC, ETH, USDT, USDC и т.д.
"coins_chain": "ETH", // сеть: BTC, ETH, TRX и т.д.
"service_id": "qryptopay_pi_uuid", // внутренний идентификатор платежа в QryptoPay
"payment_mid": "string", // ID транзакции в вашем магазине, null если статус unexpected
"customer": {
"id": "your_id", // ID клиента в вашей системе
"email": "string" // email клиента, null если не был передан
}
"metadata": { // null если не передана изначально или статус unexpected
"key1": value1,
},
"transaction_ids": [ // связанные транзакции в блокччейне (одна или несколько)
"686...fbe",
"8be...6ab"
]
}Поля surcharge_fiat и amount_fiat_net приходят всегда, даже если комиссия за оплату криптовалютой выключена — тогда surcharge_fiat равен 0.00, а amount_fiat_net совпадает с amount_fiat, так что уже написанные интеграции не ломаются. Подробнее о том, как считается комиссия — в статье Настройка комиссий для валют в QryptoPay.
Поля expected_amount_coins и is_underpaid тоже приходят всегда, независимо от сценария и даже если допустимая недоплата выключена — по той же причине, старые интеграции не ломаются. Что это за настройка — в разделе «Допустимая недоплата» ниже.
⚠️ Важно
Если валидация прошла успешно, верните в ответ 200 OK. Иначе QryptoPay будет повторно отправлять уведомления о платеже до тех пор, пока не получит ответ 200.
Полученные данные можно использовать для постобработки платежа. Часть параметров возвращается в исходном виде — например, все значения metadata, переданные через платёжную ссылку.
⚠️ Важно
Если в QryptoPay уже сохранён email клиента, но в очередном платеже email не передан, в уведомлении на вебхук будет указан email из базы QryptoPay.
💡 Совет
Рекомендуем дополнительно валидировать данные из вебхука, сопоставляя их с исходными параметрами транзакции (например, amount_fiat_net с суммой заказа, ID платежа и ID клиента).
Далее на стороне вашего магазина необходимо реализовать метод, который корректно обработает входящее уведомление. Возможны три сценария:
success— оплата прошла успешно, в том числе если пришло чуть меньше суммы счёта, но в пределах допустимой недоплаты (если она включена — подробнее ниже). В этом случае рекомендуется сверить значениеamount_fiat_net(сумма за вычетом комиссии за оплату криптовалютой), полученное в уведомлении, с вашей внутренней суммой заказа перед финализацией платежа —amount_fiatможет быть больше суммы заказа на размер этой комиссии;mismatch— клиент переплатил или недоплатил больше, чем позволяет допустимая недоплата;unexpected— клиент перевёл средства на кошелёк без предварительного формирования платёжной ссылки.
Логику обработки каждого сценария вы можете определить самостоятельно. Например, в случае mismatch можно сравнить сумму из уведомления с ожидаемой суммой и, если оплата была неполной, уведомить клиента о необходимости доплаты. В случае unexpected возможен вариант автоматического пополнения баланса клиента с последующим уведомлением о платеже.
После реализации обработчика заполните поле вебхука в настройках тестового терминала и повторите процесс оплаты — сформируйте новую ссылку и перейдите по ней. Для тестового терминала платёж обрабатывается сразу, а уведомление моментально отправляется на вебхук.
Если уведомление успешно получено на стороне вашего магазина, тестовую интеграцию можно считать завершённой.
Допустимая недоплата
Иногда клиент переводит чуть меньше суммы счёта: кошелёк округлил перевод или удержал часть на сетевой сбор, и вместо 102 USDT приходит 101.99. По умолчанию такой счёт не закрывается — QryptoPay ждёт полную сумму. Если вы готовы принимать такие платежи, включите допустимую недоплату: тогда платёж приходит на вебхук с payment_result: "success" и is_underpaid: true, и магазины, которые уже считают success подтверждением оплаты, закрывают заказ без доработок.
Для подключения опции откройте QryptoPay → «Настройки» → «Валюты», включите переключатель в блоке «Допустимая недоплата» и задайте, насколько меньше клиент может заплатить: процент от суммы счёта и, если нужно, максимальный размер недоплаты в долларах.
Чтобы отличить такой платёж от оплаченного ровно в сумму счёта, сверяйте amount_coins (сколько пришло) с expected_amount_coins (сколько ожидалось по счёту) и проверяйте is_underpaid. Недостачу берёт на себя магазин — она вычитается из amount_fiat_net, а комиссия за оплату криптовалютой, если она настроена, удерживается полностью (подробнее — в статье Настройка комиссий для валют в QryptoPay). Переплата приходит как mismatch.
Шаг 3. Проверка полного цикла
Прежде чем принимать настоящие оплаты, пройдите весь путь клиента тестовым терминалом:
- Создайте заказ в своём магазине и сформируйте для него ссылку на оплату.
- Перейдите по ссылке — откроется платёжная страница на вашем домене.
- Выберите валюту и подтвердите оплату.
- Дождитесь уведомления на вебхук: на тестовом терминале платёж обрабатывается сразу, реальный перевод криптовалюты не нужен.
- Проверьте, что заказ в магазине перешёл в оплаченный статус.
Если все пять пунктов прошли, интеграция работает целиком: ссылки создаются, страница принимает оплату, магазин узнаёт о платеже. Если что-то оборвалось на середине, причину стоит искать на этом же шаге: когда платёж не появился в мерчанте, дело в платёжном токене, а когда платёж есть, но уведомление не пришло — в обработчике вебхука.
Шаг 4. Переход на основной терминал
Теперь можно принимать настоящие оплаты. Для этого выполните следующие действия:
- сгенерируйте новую пару токенов для основного терминала;
- отредактируйте настройки терминала и укажите адрес вебхука;
- замените в файле
.envвашего магазина значенияID,PRIVATE_TOKENиWEBHOOK_KEYна данные из основного терминала.
После этого вы можете проверить работу терминала, создав тестовый платёж. Обратите внимание: на этом этапе потребуется выполнить реальный перевод в криптовалюте, для которой был добавлен кошелёк. Рекомендуем начинать с минимальных сумм.
Если все настройки выполнены корректно, платёж будет успешно зачислен в вашем магазине.
Что дальше
Магазин подключён и принимает платежи через основной терминал. Дальше — как управлять модулем:
- Настройка уведомлений QryptoPay на электронную почту — узнавайте о заблокированных платежах и лицензии.
- Управление кошельками — балансы, резерв на комиссии, отключение кошелька.
- Настройка комиссий для валют в QryptoPay — переложите расход на перевод на покупателя.
- Мониторинг состояния QryptoPay — узнавайте о сбое сервера раньше клиентов.