Skip to content

Интеграция магазина с 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. Проверка полного цикла

Прежде чем принимать настоящие оплаты, пройдите весь путь клиента тестовым терминалом:

  1. Создайте заказ в своём магазине и сформируйте для него ссылку на оплату.
  2. Перейдите по ссылке — откроется платёжная страница на вашем домене.
  3. Выберите валюту и подтвердите оплату.
  4. Дождитесь уведомления на вебхук: на тестовом терминале платёж обрабатывается сразу, реальный перевод криптовалюты не нужен.
  5. Проверьте, что заказ в магазине перешёл в оплаченный статус.

Если все пять пунктов прошли, интеграция работает целиком: ссылки создаются, страница принимает оплату, магазин узнаёт о платеже. Если что-то оборвалось на середине, причину стоит искать на этом же шаге: когда платёж не появился в мерчанте, дело в платёжном токене, а когда платёж есть, но уведомление не пришло — в обработчике вебхука.

Шаг 4. Переход на основной терминал

Теперь можно принимать настоящие оплаты. Для этого выполните следующие действия:

  • сгенерируйте новую пару токенов для основного терминала;
  • отредактируйте настройки терминала и укажите адрес вебхука;
  • замените в файле .env вашего магазина значения ID, PRIVATE_TOKEN и WEBHOOK_KEY на данные из основного терминала.

После этого вы можете проверить работу терминала, создав тестовый платёж. Обратите внимание: на этом этапе потребуется выполнить реальный перевод в криптовалюте, для которой был добавлен кошелёк. Рекомендуем начинать с минимальных сумм.

Если все настройки выполнены корректно, платёж будет успешно зачислен в вашем магазине.

Что дальше

Магазин подключён и принимает платежи через основной терминал. Дальше — как управлять модулем:

BeAdmin © 2025. Все права защищены.