Один платёж.
От запроса до заказа.
Создание платежа, переход на checkout и подтверждение на вашем сервере. Ключ API используется только на backend магазина.
Создайте платёж
Получите у Trufill ключ магазина и webhook secret, зарегистрируйте URL обработчика webhook и разрешённый origin для возврата покупателя. Примеры используют домен
shop.example — замените его своим зарегистрированным адресом.
Запрос cURL
curl https://payments.trufill.xyz/v1/payments \
--request POST \
--header "Authorization: Bearer $TRUFILL_API_KEY" \
--header "Idempotency-Key: order-123-payment-1" \
--header "Content-Type: application/json" \
--data '{
"amount": {"value": "10.00", "currency": "USD"},
"merchant_order_id": "order-123",
"description": "Hosting subscription",
"return_url": "https://shop.example/payments/return"
}'
Сервер отвечает
201 Created. Сохраните
id вместе с заказом и перенаправьте браузер на
confirmation.confirmation_url.
Ответ API
{
"id": "pay_<64 lowercase hex characters>",
"status": "pending",
"amount": {"value": "10.00", "currency": "USD"},
"merchant_order_id": "order-123",
"description": "Hosting subscription",
"created_at": "2026-11-13T12:00:00.000Z",
"expires_at": "2026-11-13T12:30:00.000Z",
"confirmation": {
"type": "redirect",
"confirmation_url": "https://payments.trufill.xyz/checkout/pay_<64 hex>"
},
"test": true
}
В примере ID сокращён для чтения. Настоящий
payment.id содержит префикс
pay_ и 64 шестнадцатеричных символа. Все URL платежа возвращает сервер.
Один ключ на одну попытку создания
Заранее сохраните
Idempotency-Key с заказом. Повтор с тем же ключом и теми же полями вернёт тот же платёж. Изменение полей с прежним ключом вернёт
409. После сетевого тайм-аута повторите запрос с сохранённым ключом.
Ключ содержит от 16 до 128 символов из
A–Z a–z 0–9 : _ -. Для нового платежа используйте новый ключ.
Примеры для backend
JavaScript
Пример JavaScript
// Server-side JavaScript. Save the key with the order before the first attempt.
const response = await fetch("https://payments.trufill.xyz/v1/payments", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TRUFILL_API_KEY}`,
"Idempotency-Key": "order-123-payment-1",
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: { value: "10.00", currency: "USD" },
merchant_order_id: "order-123",
description: "Hosting subscription",
return_url: "https://shop.example/payments/return",
}),
});
const payment = await response.json();
if (!response.ok) throw new Error(payment.error.message);
// Persist payment.id alongside order-123.
// Respond to your buyer's browser with a 303 redirect to this URL.
const checkoutUrl = payment.confirmation.confirmation_url;
PHP
Пример PHP
<?php
// Server-side PHP with the cURL extension.
$curl = curl_init('https://payments.trufill.xyz/v1/payments');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('TRUFILL_API_KEY'),
'Idempotency-Key: order-123-payment-1',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => ['value' => '10.00', 'currency' => 'USD'],
'merchant_order_id' => 'order-123',
'description' => 'Hosting subscription',
'return_url' => 'https://shop.example/payments/return',
], JSON_THROW_ON_ERROR),
]);
$body = curl_exec($curl);
if ($body === false) throw new RuntimeException(curl_error($curl));
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
$payment = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
if ($status !== 201) throw new RuntimeException($payment['error']['message']);
// Persist $payment['id'] alongside order-123 before redirecting.
header('Location: ' . $payment['confirmation']['confirmation_url'], true, 303);
exit;
Суммы передаются десятичными строками. Вынесите секреты в переменные окружения или серверное хранилище секретов.
Проверьте платёж через API
Проверка статуса
curl "https://payments.trufill.xyz/v1/payments/$PAYMENT_ID" \
--header "Authorization: Bearer $TRUFILL_API_KEY"
Readback использует тот же Bearer key магазина. Он возвращает платёж со статусом, исходной суммой и номером заказа. После выбора сети появляется
selected_network, после подтверждения —
settlement.
- Убедитесь, что
idсовпадает с сохранённым платежом. - Сравните
merchant_order_id,amount.valueиamount.currencyс вашим заказом. - Проверьте
status === "succeeded"и соответствиеtestрежиму интеграции. - Зафиксируйте выдачу заказа один раз в транзакции вашей базы данных.
Возврат покупателя
После успешной оплаты Trufill отправит браузер на зарегистрированный
return_url с параметром
payment_id. Покажите пользователю состояние заказа с вашего backend. Решение о выдаче заказа принимайте по проверенному webhook и readback.
Подтвердите подпись webhook
Trufill отправляет
POST на URL, заданный при подключении магазина. Событие
payment.succeeded содержит публичный объект платежа в
data. Ниже сокращённый пример тела.
Пример уведомления
{
"id": "payment.succeeded:pay_<64 hex>",
"event": "payment.succeeded",
"data": {
"id": "pay_<64 hex>",
"status": "succeeded",
"amount": {"value": "10.00", "currency": "USD"},
"merchant_order_id": "order-123",
"selected_network": "tron",
"test": true,
"settlement": {
"network": "tron",
"asset": "USDT",
"merchant_net": "9.95",
"processing_fee": "0.05",
"network_fee": "4.950001",
"simulated": true
}
}
}
Заголовок
x-trufill-idempotency-key содержит ID события. Формат
x-trufill-signature —
t=<Unix seconds>,v1=<hex digest>.
Подпись —
HMAC-SHA256 с webhook secret от точной последовательности
timestamp + "." + eventId + "." + rawBody. Используйте исходные байты тела и окно допустимого времени 300 секунд.
Проверка подписи
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyTrufillWebhook(rawBody, headers, secret) {
const id = headers["x-trufill-idempotency-key"];
const signature = headers["x-trufill-signature"];
if (typeof id !== "string" || typeof signature !== "string") return false;
const match = /^t=([1-9][0-9]{0,15}),v1=([a-f0-9]{64})$/.exec(signature);
if (!match) return false;
const timestamp = Number(match[1]);
if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${id}.`)
.update(rawBody)
.digest();
const actual = Buffer.from(match[2], "hex");
return timingSafeEqual(expected, actual);
}
// rawBody is the exact request Buffer, captured before JSON parsing.
// After verification, parse JSON, check event and read back payment.id.
// Fulfill the order in one transaction, deduplicating event.id/payment.id.
// Acknowledge with 2xx only after the durable transaction commits.
После проверки подписи выполните readback. Доставки могут повторяться. Храните ID обработанных событий и связывайте выдачу заказа с постоянным
payment.id. Возвращайте
2xx после надёжного сохранения результата. При временной ошибке верните
5xx, чтобы доставка повторилась.
Поля и статусы
POST /v1/payments
| Поле | Значение |
|---|---|
amount.value
|
Обязательная строка с двумя десятичными знаками. По умолчанию от 10,00 USD. Минимум согласуется для магазина, верхний предел 1 000 000,00 USD. |
amount.currency
|
Обязательно
"USD".
|
merchant_order_id
|
Обязательная строка 1–128 символов из
A–Z a–z 0–9 . _ : -.
|
description
|
Необязательная строка до 280 символов, без управляющих символов. |
return_url
|
Обязательный HTTPS URL с зарегистрированным origin, без credentials и fragment. Query-параметры допустимы. |
Webhook URL задаётся при подключении магазина. Каждый платёж сохраняет исходную сумму и отдельную выбранную сеть.
Объект Payment
| Поле | Значение |
|---|---|
id
|
Постоянный ID платежа. |
status
|
pending — ожидает оплату,
succeeded — подтверждён,
expired — срок оплаты истёк.
|
created_at,
expires_at
|
Время в ISO 8601 UTC. Счёт действует 30 минут. |
confirmation
|
type: "redirect" и ссылка
confirmation_url.
|
test
|
true для симуляции,
false для реальной оплаты.
|
selected_network
|
После выбора —
base,
arbitrum,
ethereum или
tron.
|
settlement
|
После успеха —
network,
asset,
merchant_net,
processing_fee,
network_fee. Для реальных платежей возможны
transaction_hash и
payout_transaction_hash, для тестовых —
simulated: true.
|
return_url
|
После успеха — зарегистрированный адрес возврата с
payment_id.
|
Счёт и сетевые расходы
Комиссия магазина — 0,5% от исходного счёта. Для счёта 10,00 USD комиссия составляет 0,05 USD, целевая сумма магазину — 9,95 USD.
Сетевая доплата покупателя зависит от профиля расчётов сети. Checkout получает готовую серверную котировку и показывает счёт, сетевую доплату и итог до оплаты.
| Тестовый пример | Счёт | Сетевая доплата | Итог покупателя |
|---|---|---|---|
| Base, без минимального сетевого сбора | 10,00 USD | 0,000001 USD | 10,000001 USD |
| Tron, минимальный сбор 5,00 USD | 10,00 USD | 4,950001 USD | 14,950001 USD |
Это иллюстративные профили sandbox. Значения live зависят от конфигурации подключённых сетей. Котировка может содержать до шести знаков после точки. В примерах одна миллионная единицы покрывает округление расчётов. Используйте точное значение сервера.
Для профиля с минимальным сбором сервис увеличивает входящую сумму так, чтобы покрыть минимальный сбор и сохранить целевую сумму магазину. Окончательная котировка фиксируется при выборе сети. При объединении выплат остаток минимального сбора может увеличить сумму магазину. Для учёта используйте фактический
settlement.merchant_net. Комиссия за отправку из кошелька покупателя оплачивается отдельно и показывается самим кошельком.
Ошибки и повторы
Ошибка возвращается с соответствующим HTTP-статусом и объектом
error.
Пример ошибки
{"error":{"code":"http_400","message":"Invalid request fields"}}
| HTTP | Действие |
|---|---|
| 400 | Проверьте формат полей, сумму, Idempotency-Key и зарегистрированный origin возврата. |
| 401 | Проверьте Bearer key и его принадлежность магазину. |
| 404 | Проверьте ID. Платёж другого магазина также возвращает 404. |
| 409 | Ключ создания уже связан с другими полями либо состояние платежа не допускает действие. |
| 413 | Уменьшите размер запроса. |
| 429 | Снизьте частоту запросов, повторите с задержкой. |
| 5xx | Повторите с задержкой. Для создания платежа сохраните прежний Idempotency-Key и тело. |
Логируйте ID платежа и HTTP-статус. Храните ключи API и webhook secret в серверном окружении.
Тестовый и live-режим
Публичное демо создаёт изолированный тестовый платёж. Выбор сети и подтверждение моделируются сервером, реальные средства не переводятся. Тестовые ключи магазина позволяют проверить собственный webhook и return URL.
Live-режим включается командой Trufill после регистрации магазина и настройки доступных сетей. Checkout показывает сети, подключённые для конкретного магазина. Секреты API и настройки провайдеров хранятся на сервере.
Для перехода в live отдельно проверьте ключ магазина,
test === false, реальные адреса webhook и возврата. Проверяйте режим события перед выдачей заказа.