trufill Payments

Один платёж.
От запроса до заказа.

Создание платежа, переход на 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.

  1. Убедитесь, что id совпадает с сохранённым платежом.
  2. Сравните merchant_order_id, amount.value и amount.currency с вашим заказом.
  3. Проверьте status === "succeeded" и соответствие test режиму интеграции.
  4. Зафиксируйте выдачу заказа один раз в транзакции вашей базы данных.

Возврат покупателя

После успешной оплаты 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 и возврата. Проверяйте режим события перед выдачей заказа.