Подключение сайта к «Приёму»
Пошагово, от пустого кабинета до работающей кнопки оплаты. Займёт около получаса.
Всё, что здесь написано, проверено на боевом.
Шаг 0. Регистрация
Зарегистрируйтесь на https://app.priem.io/signup — название бизнеса,
почта, пароль. Кабинет откроется сразу.
Принимать платежи можно будет после проверки. Пока она не пройдена, ценники и API отвечают отказом. Это не бюрократия: деньги ваших клиентов идут через наш счёт у провайдера, и отвечать за то, кто через нас торгует, приходится нам.
Проверка занимает от нескольких часов. Пока она идёт, всё остальное настраивать можно — проект, адрес коллбэка, ключи, ценники. Как подтвердим, они заработают без каких-либо действий с вашей стороны.
Пароль пока нельзя сменить самому — напишите нам, если понадобится.
Что это стоит
| Кому платится | Сколько |
|---|---|
| Нам, с каждого платежа | 1,5%, удерживается при зачислении |
| Сети, при выводе | Фиксированно, около 1 USDT в TRON |
| Сети, при оплате | Платит клиент сверх ценника |
Комиссия сети при оплате не зависит от суммы, поэтому на мелком чеке она заметна, а на крупном почти нет. Клиент видит её до подтверждения:
| Чем платит | Чек $5 | Чек $30 | Чек $100 |
|---|---|---|---|
| USDT в BSC | +1,3% | +0,5% | +0,3% |
| USDT в TON | +9,3% | +1,8% | +0,8% |
| USDT в TRON | +16,3% | +3,0% | +1,1% |
| USDT в Ethereum | +19,5% | +3,5% | +1,3% |
Ниже $10 продавать через криптовалюту тяжело: на трёх долларах даже недорогая сеть съедает заметную долю, а TRON и Ethereum добавляют четверть цены. Если у вас мелкие тарифы, имеет смысл продавать их пакетами.
Шаг 1. Проект
Первый проект создаётся при регистрации сам. Откройте
https://app.priem.io/projects и проверьте настройки:
| Поле | Что это |
|---|---|
| Валюта выплаты | В чём хотите получать. По умолчанию USDT |
| Адрес коллбэка | Куда сообщить об оплате. Заполните на шаге 4 |
Чем платить, клиент выбирает сам на странице оплаты — доступны все валюты и сети Changelly. Ограничить набор пока нельзя.
Там же лежит секрет подписи — длинная строка, ею вы проверяете, что коллбэк пришёл от нас. Понадобится на шаге 4.
Проектов может быть несколько — по одному на сайт или на направление. У каждого свой секрет, свои ключи и свой адрес коллбэка.
Шаг 2. Ценник — самый быстрый способ
Если нужна просто кнопка «оплатить» с фиксированной ценой, кода не потребуется.
На https://app.priem.io/price-tags создайте ценник:
- Цена — фиксированная или та, которую клиент вводит сам
- Использование — многоразовый для товара в каталоге, одноразовый для счёта конкретному человеку
Скопируйте ссылку вида https://api.priem.io/pay/<идентификатор> и поставьте
её кнопкой:
<a href="https://api.priem.io/pay/ВАШ-ИДЕНТИФИКАТОР"
style="display:inline-block;padding:.8rem 1.4rem;background:#17171a;
color:#fff;border-radius:8px;text-decoration:none">
Оплатить криптовалютой
</a>
На этом приём платежей уже работает. Дальше — если нужна сумма из корзины.
Оплата картой
На кассе рядом с «Криптовалютой» есть кнопка «Картой». Клиент покупает биткоин у Changelly, тот приходит прямо на адрес счёта, и платёж проводится как обычный — вам ничего настраивать не нужно.
Биткоин выбран из-за однозначности: у него одна сеть, и доставка не может уйти не туда.
Минимальная сумма покупки картой — 10 долларов. Измерено на живом виджете. Ниже порога кнопка «Картой» на кассе не показывается вовсе, и клиенту сказано почему.
Учтите: клиент отправляет больше ценника, сверху ложится комиссия сети. За счёт в $30 он заплатит около $34.
Шаг 3. Динамическая сумма через API
Выпустите ключ на https://app.priem.io/api-keys. Он показывается один раз.
Ваш сервер создаёт платёж и ведёт клиента на полученную ссылку:
const response = await fetch('https://api.priem.io/v1/payments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PRIEM_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: order.total.toFixed(2), // строкой, не числом
currency: 'USD',
idempotencyKey: `order-${order.id}`, // ваш номер заказа
}),
});
const payment = await response.json();
// payment.id — сохраните в заказе, по нему придёт коллбэк
// payment.paymentUrl — сюда отправляйте клиента
idempotencyKey берите из номера заказа, а не из времени и не случайный.
Повторный запрос с тем же ключом вернёт тот же платёж вместо второго счёта.
Ключ из Date.now() не защищает ни от чего.
Ключ API держите на сервере. В браузер он попадать не должен: с ним создают платежи от вашего имени.
Шаг 4. Коллбэк — как узнать об оплате
В карточке проекта впишите адрес коллбэка: https://ваш-сайт/priem/hook.
Только https, только публичное имя — внутренние адреса мы отклоняем.
Когда деньги дошли, мы шлём туда POST:
{
"event": "payment.settled",
"paymentId": "f0a528fa-...",
"state": "settled",
"amount": "49.00",
"currency": "USD",
"payCurrency": "USDC",
"payoutCurrency": "USDT",
"creditedAmount": "48.26"
}
Обработчик на Express:
import express from 'express';
import { createHmac, timingSafeEqual } from 'node:crypto';
const app = express();
// ВАЖНО: сырое тело, до разбора JSON. Подпись считается по байтам,
// и разобранный-собранный JSON её не пройдёт.
app.post('/priem/hook', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('X-Priem-Timestamp');
const signature = req.get('X-Priem-Signature');
const expected = createHmac('sha256', process.env.PRIEM_WEBHOOK_SECRET)
.update(`${timestamp}.${req.body}`, 'utf8')
.digest('hex');
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(signature ?? '', 'utf8');
if (a.length !== b.length || !timingSafeEqual(a, b)) {
return res.sendStatus(401);
}
// не старше пяти минут: иначе это повтор перехваченного запроса
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString('utf8'));
// Тот же платёж может прийти дважды — мы повторяем при неудаче.
// Отгружайте по факту, а не по приходу коллбэка.
markOrderPaid(event.paymentId, event.creditedAmount);
res.sendStatus(200); // ответьте 2xx, иначе будем повторять
});
Секрет подписи — из карточки проекта. Положите его в переменную окружения, не в код.
Шаг 5. Проверка до боевого запуска
Пройдите этот список — каждый пункт закрывает ошибку, которую легко сделать:
- Ключ API лежит на сервере, а не в браузере
-
idempotencyKeyберётся из номера заказа - Подпись считается по сырому телу, до
JSON.parse - Обработчик отвечает
2xxи терпит повтор того жеpaymentId - Заказ отгружается по состоянию
settled, а не по факту прихода коллбэка - Секрет подписи в переменной окружения
Затем сделайте настоящий платёж на маленькую сумму. Не на $2 — на такой сумме комиссия сети даёт около 40% накрутки, и вы решите, что что-то сломано. Возьмите $20–30.
Шаг 6. Вывод денег
На https://app.priem.io/payouts заведите адрес кошелька. Выводить на него
можно через сутки — отсрочка защищает на случай, если кабинетом завладеет
кто-то другой.
Сеть указывается отдельно и обязательна: USDT в TRON и USDT в BSC — разные адреса, отправка не в ту сеть теряет деньги безвозвратно.
Комиссию сети платите вы, она списывается сверх суммы, которую получит адресат. Она фиксированная — около 1 USDT в TRON независимо от суммы, — поэтому выводить накопленное разом дешевле, чем каждый платёж отдельно.
Если что-то не сходится
Платёж не создаётся, ответ говорит про подтверждение. Проверка ещё не пройдена. Настраивать всё остальное можно, приём включится сам.
Клиент заплатил, а коллбэк не пришёл. Посмотрите платёж через
GET /v1/payments/{id} — состояние там всегда актуально. Коллбэк мы повторяем
семь раз в течение полутора суток, но полагаться только на него не стоит.
Клиент прислал меньше. В пределах допуска проекта платёж пройдёт, и вы получите полную сумму ценника. Сверх допуска — платёж не завершится, клиенту предложат доплатить. Учтите, что доплата стоит ему ещё одной комиссии сети, поэтому слишком узкий допуск оборачивается брошенными заказами, а не доплатами.
Подпись не сходится. Почти всегда причина одна: тело разобрали до проверки. Нужны именно те байты, что пришли.
Подробности по API — в разделе «Документация» кабинета.
Приём: как подключиться
Приём принимает у вашего клиента криптовалюту или фиат и зачисляет вам выбранную монету. Клиент платит чем хочет — вы получаете то, что заказали.
Есть два пути, и они не исключают друг друга:
- Ценник — вы создаёте его в кабинете, получаете ссылку и ставите её на сайт. Кода не требуется вовсе.
- API — ваш сайт сам создаёт платёж на нужную сумму и ведёт клиента на оплату.
Дальше в обоих случаях приходит коллбэк на ваш адрес: платёж прошёл.
Что нужно завести
- Проект —
https://app.priem.io/projects. Задайте валюту, в которой хотите получать деньги, и валюты, которыми клиент может платить. - Адрес коллбэка — там же. Только
https, только публичное имя. - Ключ API —
https://app.priem.io/api-keys. Показывается один раз; восстановить его неоткуда, только выпустить новый.
Ценник без кода
На https://app.priem.io/price-tags создайте ценник и скопируйте ссылку вида
https://api.priem.io/pay/<идентификатор>. Поставьте её кнопкой на сайт.
Два свойства решаются при создании:
- Цена — фиксированная или та, которую клиент вводит сам.
- Использование — многоразовый или одноразовый. Одноразовый закрывается в тот момент, когда первый клиент переходит к оплате: двое, открывшие ссылку одновременно, не заплатят дважды за один товар.
API
Адрес: https://api.priem.io. Аутентификация — заголовок
Authorization: Bearer <ключ>. Проект определяется ключом, поэтому в теле
его указывать не нужно и нельзя.
Создать платёж
curl -X POST https://api.priem.io/v1/payments \
-H "Authorization: Bearer priem_..." \
-H "Content-Type: application/json" \
-d '{
"amount": "49.00",
"currency": "USD",
"idempotencyKey": "zakaz-12345"
}'
{
"id": "f0a528fa-5292-4f04-a4a0-747b6da94873",
"state": "created",
"amount": "49.00",
"currency": "USD",
"paymentUrl": "https://app.pay.changelly.com/payment/a3e5b893-..."
}
Ведите клиента на paymentUrl — там он выберет монету и сеть.
idempotencyKey обязателен и должен быть вашим. Возьмите номер заказа
или что-то столь же устойчивое. Повторный запрос с тем же ключом вернёт
тот же платёж, а не выставит второй счёт. Ключ, собранный из времени или
случайности, перестаёт защищать от того, ради чего заведён.
Если предыдущий запрос с этим ключом ещё выполняется, ответ будет 400
с текстом payment is already being created, try again — повторите через
мгновение.
Отказы
Все поля тела — строки, включая сумму: "25.00", а не 25.00.
| Код | Когда | Что делать |
|---|---|---|
400 |
поля не хватает, сумма не строка, валюта незнакомая | исправить запрос, текст в message |
400 |
payment is already being created, try again |
повторить через мгновение |
401 |
ключ не тот или не передан | проверить заголовок Authorization |
403 |
merchant verification is pending… — проверка ещё идёт |
ждать, приём включится сам |
403 |
merchant is suspended… — приём приостановлен |
написать нам |
502 / 503 |
подвёл провайдер платежей | повторить с тем же idempotencyKey |
500 |
наш сбой | прислать нам requestId из тела ответа |
Каждый ответ несёт заголовок x-request-id, а тело отказа 500 — то же
значение полем requestId. Это самое полезное, что можно приложить к письму
о поломке: по нему мы находим нужную строку в журнале сразу. Свой идентификатор
тоже принимается — пришлите его тем же заголовком, и он вернётся в ответе.
Прочитать платёж
curl https://api.priem.io/v1/payments/<id> \
-H "Authorization: Bearer priem_..."
{
"id": "f0a528fa-...",
"state": "settled",
"amount": "49.00",
"currency": "USD",
"paymentUrl": "https://app.pay.changelly.com/payment/...",
"payCurrency": "USDC",
"depositAddress": "0x..."
}
Чужой платёж и несуществующий отвечают одинаково — 404.
Пока не подтверждены
До прохождения проверки создание платежа отвечает 403 с текстом
merchant verification is pending, payments are disabled until it completes.
Ссылка ценника в это же время отвечает покупателю «приём платежей у этого
продавца пока не подключён».
Настраивать проект, ключи и ценники можно сразу — приём включится сам, без действий с вашей стороны.
Состояния платежа
| Состояние | Что это значит |
|---|---|
created |
Платёж заведён, клиент ещё не выбрал монету |
awaiting_payment |
Монета выбрана, ждём перевода |
onramp_pending |
Клиент платит картой, ждём покупку крипты |
detected |
Перевод виден в сети, ещё не подтверждён |
confirming / confirmed |
Набираются подтверждения |
converting |
Меняем полученное на вашу валюту |
conversion_retry |
Обмен не прошёл с первого раза, пробуем снова |
settled |
Деньги на вашем балансе. Отгружайте заказ |
underpaid / overpaid |
Заплатили меньше или больше нужного |
expired / failed |
Не состоялся |
refunded |
Возвращён клиенту |
Отгружайте заказ по settled и только по нему.
Коллбэк
Когда платёж дошёл до settled, мы шлём POST на ваш адрес.
{
"event": "payment.settled",
"paymentId": "f0a528fa-...",
"projectId": "34c80b2d-...",
"state": "settled",
"amount": "49.00",
"currency": "USD",
"payCurrency": "USDC",
"payoutCurrency": "USDT",
"creditedAmount": "48.26"
}
creditedAmount — сколько зачислено вам после нашей комиссии, в payoutCurrency.
Заголовки:
| Заголовок | Что в нём |
|---|---|
X-Priem-Timestamp |
Время подписи, секунды Unix |
X-Priem-Signature |
HMAC-SHA256, шестнадцатеричная строка |
Ответьте 2xx. Любой другой ответ мы считаем неудачей и повторяем: всего семь
попыток с промежутками 1 минута, 5 минут, 30 минут, 2 часа, 6 часов и сутки —
последняя примерно через 33 часа после первой. Дальше мы перестаём: бесконечные
повторы одного сломанного мерчанта забили бы очередь всем остальным.
Обработчик должен терпеть повтор — один и тот же paymentId может прийти дважды.
И не полагайтесь на один лишь коллбэк: если ваш сервер лежал больше полутора
суток, состояние надо забрать опросом.
Проверка подписи
Подписывается строка {timestamp}.{тело как есть}. Секрет — в карточке проекта
в кабинете.
Считайте подпись по сырому телу, до разбора JSON. Если разобрать и собрать обратно, изменится порядок ключей или пробелы, и подпись не сойдётся.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(secret, rawBody, timestamp, signature) {
const expected = createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`, 'utf8')
.digest('hex');
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(signature, 'utf8');
// сравнение за постоянное время: обычное `===` выдаёт длину общего префикса
return a.length === b.length && timingSafeEqual(a, b);
}
import hmac, hashlib
def verify(secret: str, raw_body: bytes, timestamp: str, signature: str) -> bool:
expected = hmac.new(
secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
Проверяйте и возраст: слишком старый X-Priem-Timestamp — повтор перехваченного
запроса. Пять минут разумны.
Смена секрета
Перевыпуск в кабинете действует сразу: подписи начинают считаться новым секретом. Обновите его у себя в тот же момент, иначе начнёте отвергать наши коллбэки.
Недоплата
Клиент может прислать чуть меньше — из-за скачка курса или комиссии кошелька. В настройках проекта задан допуск: насколько меньше вы согласны принять.
- В пределах допуска — платёж переходит в
settled, и вы получаете полную сумму ценника. Разницу берёт на себя «Приём». - Сверх допуска — платёж не завершается. Клиенту предлагается доплатить остаток до истечения срока счёта.
Допуск по умолчанию — 1%. Учтите, что доплата стоит клиенту ещё одной комиссии сети: при недоплате на 0.09 USDT в TRON с него попросят почти 0.9. Поэтому слишком узкий допуск оборачивается не доплатами, а брошенными заказами.
Деньги
Комиссия удерживается при зачислении и видна в creditedAmount.
Вывод — https://app.priem.io/payouts. Сначала заведите адрес; выводить
на него можно через сутки после добавления. Отсрочка неприятна ровно один
раз, а защищает от того, кто получил доступ к вашему кабинету и хочет увести
деньги на свой кошелёк немедленно.
Комиссию сети платите вы: она списывается сверх суммы, которую получит адресат.
Сеть у адреса указывается отдельно и обязательна. USDT в TRON и USDT в BSC — разные адреса, и отправка не в ту сеть теряет деньги безвозвратно.
Что стоит проверить до боевого запуска
- Обработчик коллбэка отвечает
2xxи терпит повтор того жеpaymentId. - Подпись считается по сырому телу.
- Заказ отгружается по
settled, а не по факту прихода коллбэка. idempotencyKeyберётся из вашего заказа, а не из времени.