PHP-SDK
Один файл, без зависимостей. createInvoice(), getInvoice() и verifyWebhook() — кладётся в любой проект.
Один PHP-SDK без зависимостей: создать счёт, отправить на готовый чек-аут и проверить подпись вебхука. Для любого другого языка есть REST.
api.payora.moneyОдин вызов API с номером заказа и суммой в фиате или крипте возвращает ссылку на оплату.
Он выбирает монету, сканирует QR или открывает кошелёк — оплата подтверждается вживую по финализации.
Проверьте подпись HMAC, зачтите заказ один раз — и платёж уже на вашем балансе.
Один файл, без зависимостей. createInvoice(), getInvoice() и verifyWebhook() — кладётся в любой проект.
Запросы и вебхуки подписаны HMAC-SHA256 с допуском по времени и ключами идемпотентности.
Обычный JSON поверх HTTPS — интеграция с любого стека, не только с PHP.
<?php
require 'Payora.php';
$payora = new Payora('https://api.payora.money', getenv('PAYORA_KEY'), getenv('PAYORA_SECRET'));
// 1 — create an invoice (fiat-priced; the payer picks a coin)
$inv = $payora->createInvoice([
'order_id' => 'ORDER-42',
'mode' => 'fiat',
'amount' => '9.99',
'return_url' => 'https://shop.example/thanks',
'lang' => 'en',
], 'ORDER-42'); // Idempotency-Key: a retry never creates a second invoice
header('Location: ' . $inv['pay_url']);
// 2 — webhook endpoint: ALWAYS verify the signature first
$event = $payora->verifyWebhook(file_get_contents('php://input'), getallheaders());
if ($event === null) { http_response_code(400); exit; }
if ($event['status'] === 'paid') {
creditOrderOnce($event['id'], $event['order_id'], $event['amount'], $event['currency']);
}
http_response_code(200); // 2xx stops retriesconst Payora = require('./payora');
const payora = new Payora('https://api.payora.money', process.env.PAYORA_KEY, process.env.PAYORA_SECRET);
// 1 — create an invoice and redirect
app.post('/checkout', async (req, res) => {
const inv = await payora.createInvoice({ order_id: 'ORDER-42', mode: 'fiat', amount: '9.99' }, 'ORDER-42');
res.redirect(inv.pay_url);
});
// 2 — webhook: verify over the RAW body
app.post('/payora/webhook', express.raw({ type: '*/*' }), (req, res) => {
const event = payora.verifyWebhook(req.body, req.headers);
if (event === null) return res.status(400).end();
if (event.status === 'paid') creditOrderOnce(event.id, event.order_id);
res.status(200).end();
});import os
from payora import Payora
payora = Payora('https://api.payora.money', os.environ['PAYORA_KEY'], os.environ['PAYORA_SECRET'])
# 1 — create an invoice and redirect
inv = payora.create_invoice({'order_id': 'ORDER-42', 'mode': 'fiat', 'amount': '9.99'}, 'ORDER-42')
return redirect(inv['pay_url'])
# 2 — webhook: verify over the RAW body
event = payora.verify_webhook(request.get_data(), dict(request.headers))
if event is None:
return ('', 400)
if event['status'] == 'paid':
credit_order_once(event['id'], event['order_id'])
return ('', 200)# signature = lowercase hex HMAC-SHA256(api_secret, timestamp + "." + raw_body)
BODY='{"order_id":"ORDER-42","mode":"fiat","amount":"9.99"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$PAYORA_SECRET" | cut -d' ' -f2)
curl -X POST https://api.payora.money/v1/invoice \
-H "X-Payora-Key: $PAYORA_KEY" -H "X-Payora-Timestamp: $TS" \
-H "X-Payora-Signature: $SIG" -H 'Idempotency-Key: ORDER-42' \
-H 'Content-Type: application/json' -d "$BODY"
# → 201 { "invoice_id": …, "status": "pending", "pay_url": "https://pay.payora.money/i/…", "expires": … }Получите свой API key и secret, создав аккаунт. Полный SDK — в вашей панели.
Каждый вызов, кроме /v1/health, несёт три подписанных заголовка. Подпись доказывает, что запрос отправил владелец вашего api_secret и по дороге его не изменили.
| Заголовок | Значение | Описание |
|---|---|---|
X-Payora-Key | pk_… | Ваш публичный api_key. Определяет магазин. |
X-Payora-Timestamp | unix seconds | Момент подписи запроса, в секундах Unix. |
X-Payora-Signature | hex · 64 | HMAC-SHA256 от timestamp + "." + сырое тело на ключе api_secret, строчным hex. |
X-Payora-Test | 1 | Помечает счёт как проверку интеграции: счёт настоящий и может принять монеты, но в показатели спроса и конверсии не идёт. |
Idempotency-Key | string | Необязателен для POST /v1/invoice и POST /v1/payout. Повтор с тем же ключом возвращает первый результат вместо дубликата; тот же ключ с другим телом получает 409. |
Content-Type | application/json | Обязателен для запросов с телом. |
hex( HMAC-SHA256( api_secret, timestamp + "." + raw_body ) )Подписывайте ровно те байты, которые отправляете. Повторная сборка JSON после подписи — даже другой порядок ключей или лишний пробел — ломает подпись.
У GET-запроса тело пустое, поэтому подписывается timestamp с точкой в конце.
API отклоняет timestamp, который расходится с его часами больше чем на 120 секунд. Синхронизируйте время сервера по NTP.
Храните api_secret только на сервере. Им подписываются и запросы, и вебхуки: у кого он есть, тот может создавать счета от вашего имени и подделывать уведомления.
<?php
$body = json_encode(['order_id' => 'ORDER-42', 'mode' => 'fiat', 'amount' => '9.99'], JSON_UNESCAPED_SLASHES);
$ts = (string) time();
$sig = hash_hmac('sha256', $ts . '.' . $body, $apiSecret); // lowercase hex
$ch = curl_init('https://api.payora.money/v1/invoice');
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-Payora-Key: ' . $apiKey,
'X-Payora-Timestamp: ' . $ts,
'X-Payora-Signature: ' . $sig,
'Idempotency-Key: ORDER-42',
'Content-Type: application/json',
],
]);
$invoice = json_decode(curl_exec($ch), true);const crypto = require('crypto');
const body = JSON.stringify({ order_id: 'ORDER-42', mode: 'fiat', amount: '9.99' });
const ts = String(Math.floor(Date.now() / 1000));
const sig = crypto.createHmac('sha256', API_SECRET).update(ts + '.' + body).digest('hex');
const res = await fetch('https://api.payora.money/v1/invoice', {
method: 'POST',
headers: {
'X-Payora-Key': API_KEY,
'X-Payora-Timestamp': ts,
'X-Payora-Signature': sig,
'Idempotency-Key': 'ORDER-42',
'Content-Type': 'application/json',
},
body, // send exactly the bytes you signed
});import hashlib, hmac, json, time, urllib.request
body = json.dumps({'order_id': 'ORDER-42', 'mode': 'fiat', 'amount': '9.99'}, separators=(',', ':'))
ts = str(int(time.time()))
sig = hmac.new(API_SECRET.encode(), (ts + '.' + body).encode(), hashlib.sha256).hexdigest()
req = urllib.request.Request('https://api.payora.money/v1/invoice', data=body.encode(), method='POST', headers={
'X-Payora-Key': API_KEY,
'X-Payora-Timestamp': ts,
'X-Payora-Signature': sig,
'Idempotency-Key': 'ORDER-42',
'Content-Type': 'application/json',
})
invoice = json.load(urllib.request.urlopen(req))# GET has an empty body: the signed string is just the timestamp and a dot
TS=$(date +%s)
SIG=$(printf '%s.' "$TS" | openssl dgst -sha256 -hmac "$PAYORA_SECRET" | cut -d' ' -f2)
curl https://api.payora.money/v1/invoice/1042 \
-H "X-Payora-Key: $PAYORA_KEY" \
-H "X-Payora-Timestamp: $TS" \
-H "X-Payora-Signature: $SIG"Всё необязательно, кроме order_id и amount. В режиме fiat плательщик может выбрать любую монету, включённую в вашем аккаунте; режиму crypto также нужен currency.
| Параметр | Тип | Описание |
|---|---|---|
order_id | string · required | Ваш уникальный идентификатор заказа (≤190 символов). Повтор отклоняется с 409 — защита от двойной отправки. |
amount | string · required | Сумма в фиате (mode=fiat) или точная сумма в крипте (mode=crypto). |
mode | fiat | crypto | По умолчанию fiat: курс фиксируется по каждой монете на checkout. crypto заранее фиксирует монету и сумму. |
fiat_currency | string | ISO-код фиата для режима fiat (default USD). |
currency | string | Монета для режима crypto (например, TON, TRX, USDT_TRON). |
return_url | https URL | Куда кнопка «Вернуться в магазин» на checkout возвращает клиента — на ваш сайт. Только абсолютный http(s). |
lang | en·ru·uk·es·de | Язык checkout, чтобы клиент из вашего российского магазина видел checkout на русском, а не на английском. Также задаётся как ?lang=ru в URL оплаты. |
ttl | seconds | Сколько счёт остаётся доступным для оплаты (120–86400 секунд, по умолчанию 1800). |
customer_email | Необязательно — попадает в чек и отправляется плательщику по почте. | |
notes | string | Произвольная заметка, сохраняемая в счёте. |
return_url и lang также работают как query-параметры, добавляемые к pay_url, который отдаёт API — <pay_url>&lang=ru&return_url=https://shop.example/thanks. Всегда отправляйте покупателя на pay_url, возвращённый API: он несёт одноразовый токен доступа к счёту, поэтому страницу checkout нельзя открыть, угадав номер счёта. Не собирайте ссылку заново из id счёта.
Заполните поля и скопируйте готовую команду cURL. Она подписывается сама в вашей оболочке через $PAYORA_KEY и $PAYORA_SECRET — эта страница не видит ваших ключей и ничего не отправляет.
BODY='{"order_id":"ORDER-42","mode":"fiat","amount":"9.99","fiat_currency":"USD","return_url":"https://shop.example/thanks","lang":"ru","ttl":1800}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$PAYORA_SECRET" | cut -d' ' -f2)
curl -X POST https://api.payora.money/v1/invoice \
-H "X-Payora-Key: $PAYORA_KEY" \
-H "X-Payora-Timestamp: $TS" \
-H "X-Payora-Signature: $SIG" \
-H 'Idempotency-Key: ORDER-42' \
-H 'Content-Type: application/json' \
-d "$BODY"С этой страницы ничего не отправляется.
Когда счёт или выплата меняет состояние, Payora отправляет POST с подписанным JSON-событием на ваш адрес уведомлений. Сначала проверьте подпись, зачислите заказ один раз, ответьте 2xx.
| Событие | status | Когда приходит |
|---|---|---|
invoice.paid | paid | Платёж подтверждён в сети. Содержит сумму, монету и tx_hash. |
invoice.underpaid | underpaid | Срок счёта истёк, а пришло меньше ожидаемой суммы. |
invoice.expired | expired | Срок счёта истёк, оплаты не было. |
invoice.cancelled | cancelled | Счёт отменён. Если деньги уже пришли, это описано в событии. |
invoice.cancel_settled | — | С деньгами, пришедшими на отменённый счёт, всё решено: зачислены или возвращены. |
invoice.late_payment | — | Деньги пришли на счёт уже после его отмены. Отправляется на каждое такое поступление; в result — зачислены они или возвращаются. |
payout.sent | sent | Пакет выплат подписан и отправлен в сеть. Содержит каждую выплату с её tx_hash. |
| Заголовок | Значение | Описание |
|---|---|---|
X-Payora-Id | uuid | Уникальный id события, тот же, что id в теле. Сохраняйте его, чтобы пропускать повторы. |
X-Payora-Event | invoice.paid | Название события. |
X-Payora-Timestamp | unix seconds | Когда подписана эта попытка. |
X-Payora-Signature | hex · 64 | Та же схема, что у запросов: HMAC-SHA256 от timestamp + "." + сырое тело на ключе api_secret. |
| Поле | Тип | Описание |
|---|---|---|
id | uuid | Id события для защиты от повторов. |
event | string | Название события. |
invoice_id | int | Номер счёта в Payora. |
order_id | string | Ваш номер заказа, как он был передан при создании счёта. |
status | string | paid для invoice.paid. |
currency | string | Монета, которой заплатили, например USDT_TRON. |
amount | decimal string | Полученная сумма. |
amount_units | integer string | Та же сумма в наименьших единицах монеты — для точных сравнений. |
expected | decimal string | Сумма, выставленная в счёте. |
fiat_amount | decimal string | null | Цена счёта в фиате, как вы её выставили; null у счетов в крипто-режиме. |
fiat_currency | string | null | ISO-код валюты fiat_amount; null у счетов в крипто-режиме. |
tx_hash | string | Хеш транзакции в сети. |
paid_at | unix seconds | Когда создано событие. |
test | bool | true для тестовых счетов. |
{
"id": "6f1c2a4e-8b0d-4c1e-9a57-2f3d9e1b7c40",
"event": "invoice.paid",
"invoice_id": 1042,
"order_id": "ORDER-42",
"status": "paid",
"currency": "USDT_TRON",
"amount": "9.99",
"amount_units": "9990000",
"expected": "9.99",
"fiat_amount": "9.99",
"fiat_currency": "USD",
"tx_hash": "3f9a…c21e",
"paid_at": 1758038400,
"test": false
}Если полученная сумма отличается от суммы счёта, добавляются amount_received и amount_expected.
Любой ответ, кроме 2xx, или отсутствие ответа за 15 секунд — повтор с экспоненциальной паузой, не больше часа между попытками, всего до 12 попыток. Каждая попытка подписывается заново с новым timestamp; тело и id события не меняются, поэтому отсекайте повторы по id.
После последней попытки событие помечается неудавшимся, а владелец магазина получает письмо. Перенаправления не выполняются, а адрес уведомлений должен быть доступен из интернета — без localhost и частных адресов.
hash_equals, crypto.timingSafeEqual, hmac.compare_digest.<?php
require 'Payora.php';
$payora = new Payora('https://api.payora.money', getenv('PAYORA_KEY'), getenv('PAYORA_SECRET'));
$raw = file_get_contents('php://input'); // raw, before any parsing
$event = $payora->verifyWebhook($raw, getallheaders()); // HMAC + ±300 s window, constant-time
if ($event === null) { http_response_code(400); exit; } // bad / forged / stale
if ($event['event'] === 'invoice.paid' && !alreadyProcessed($event['id'])) {
creditOrder($event['order_id'], $event['amount'], $event['currency'], $event['tx_hash']);
rememberProcessed($event['id']);
}
http_response_code(200); // 2xx stops retries// Express: capture the RAW body for this route
app.post('/payora/webhook', express.raw({ type: '*/*' }), (req, res) => {
const event = payora.verifyWebhook(req.body, req.headers); // HMAC + ±300 s, timingSafeEqual
if (event === null) return res.status(400).end();
if (event.event === 'invoice.paid' && !alreadyProcessed(event.id)) {
creditOrder(event.order_id, event.amount, event.currency, event.tx_hash);
rememberProcessed(event.id);
}
res.status(200).end(); // 2xx stops retries
});# Flask: request.get_data() is the exact raw body
@app.post('/payora/webhook')
def payora_webhook():
event = payora.verify_webhook(request.get_data(), dict(request.headers))
if event is None:
return ('', 400) # bad / forged / stale
if event['event'] == 'invoice.paid' and not already_processed(event['id']):
credit_order(event['order_id'], event['amount'], event['currency'], event['tx_hash'])
remember_processed(event['id'])
return ('', 200) # 2xx stops retries# Recompute the signature of a delivery you saved byte-for-byte to body.json
TS='1758038400' # X-Payora-Timestamp
SIG='…' # X-Payora-Signature
EXPECTED=$(printf '%s.' "$TS" | cat - body.json | openssl dgst -sha256 -hmac "$PAYORA_SECRET" | cut -d' ' -f2)
[ "$EXPECTED" = "$SIG" ] && echo valid || echo forgedВведите секрет, timestamp и сырое тело — и увидите ровно ту подпись, которую отправит Payora. Или проверьте полученную.
Работает в вашем браузере через WebCrypto. Ничего из введённого не покидает эту страницу. На чужом компьютере используйте тестовый секрет, а не боевой.
1758038400.{…}X-Payora-Timestamp: 1758038400
X-Payora-Signature: …Вставьте подпись для сравнения
Отправляйте крипту на 500 кошельков одним подписанным идемпотентным запросом — зарплата, партнёрские выплаты, выводы. Сумма пачки плюс построчная комиссия сразу удерживается на внутреннем балансе; дальше оператор подписывает и транслирует, и срабатывает подписанный вебхук payout.sent. Авторизация HMAC та же, что и везде.
# create a payout batch — funds held instantly, sent by the operator
curl -X POST https://api.payora.money/v1/payout \
-H 'X-Payora-Key: pk_…' -H 'X-Payora-Timestamp: 1700000000' \
-H 'X-Payora-Signature: <hmac-sha256>' -H 'Idempotency-Key: payroll-2026-01' \
-d '{"currency":"USDT_TRON","items":[
{"address":"TR7…","amount":"250.00"},
{"address":"TX9…","amount":"99.50","tag":"batch-A"}]}'
# → 201 Created
{ "batch_id": 812, "status": "pending", "currency": "USDT_TRON",
"item_count": 2, "total": "349.50", "fee": "…", "items": [ … ] }
# poll status, or receive a signed payout.sent webhook when it is sent
curl https://api.payora.money/v1/payout/812 -H 'X-Payora-Key: …' …<?php
// The one-file PHP SDK covers invoices and webhooks; payouts are one signed POST.
$body = json_encode(['currency' => 'USDT_TRON', 'items' => [
['address' => 'TR7…', 'amount' => '250.00'],
['address' => 'TX9…', 'amount' => '99.50', 'tag' => 'batch-A'],
]], JSON_UNESCAPED_SLASHES);
$ts = (string) time();
$sig = hash_hmac('sha256', $ts . '.' . $body, $apiSecret);
$ch = curl_init('https://api.payora.money/v1/payout');
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-Payora-Key: ' . $apiKey, 'X-Payora-Timestamp: ' . $ts, 'X-Payora-Signature: ' . $sig,
'Idempotency-Key: payroll-2026-01', 'Content-Type: application/json']]);
$batch = json_decode(curl_exec($ch), true); // 201: batch_id, status, totals, fee, itemsconst batch = await payora.createPayout({
currency: 'USDT_TRON',
items: [
{ address: 'TR7…', amount: '250.00' },
{ address: 'TX9…', amount: '99.50', tag: 'batch-A' },
],
}, 'payroll-2026-01'); // Idempotency-Key: a retried run never doubles
const status = await payora.getPayout(batch.batch_id);
// still pending? await payora.cancelPayout(batch.batch_id) releases the holdbatch = payora.create_payout({
'currency': 'USDT_TRON',
'items': [
{'address': 'TR7…', 'amount': '250.00'},
{'address': 'TX9…', 'amount': '99.50', 'tag': 'batch-A'},
],
}, 'payroll-2026-01') # Idempotency-Key: a retried run never doubles
status = payora.get_payout(batch['batch_id'])
# still pending? payora.cancel_payout(batch['batch_id']) releases the holdПроверка строгая: любой неверный адрес или сумма с избыточной точностью отклоняют весь пакет (HTTP 422) с индексами проблемных позиций — при выплате зарплат ни один получатель не пропадёт молча. Отмените ещё ожидающий пакет через POST /v1/payout/{id}/cancel, чтобы снять удержание. См. разбор архитектуры.
Каждый эндпоинт общается обычным JSON поверх HTTPS по адресу api.payora.money. Все, кроме /v1/health, используют одну и ту же аутентификацию запросов по HMAC-SHA256. Машиночитаемая спецификация находится по адресу openapi.json.
| Метод | Доступ | Описание |
|---|---|---|
GET/v1/health | no auth | Статус сервиса плюс список включённых валют. |
POST/v1/invoice | auth | Создайте счёт (режим fiat или crypto). Поддерживает Idempotency-Key. Возвращает 201 с invoice_id, status, pay_url и expires; в режиме crypto добавляются адрес, сумма и deeplink. |
GET/v1/invoice/{id} | auth | Статус счёта (только собственные счета): status, remaining и charge — currency, адрес, ожидаемая/полученная суммы и tx_hash. |
POST/v1/payout | auth · Pro+ | Создайте пакет массовых выплат (строгая проверка, средства удерживаются мгновенно). Поддерживает Idempotency-Key. Возвращает 201 с представлением пакета: batch_id, status, итоги, комиссия, позиции. |
GET/v1/payout | auth | Список ваших недавних пакетов выплат (?limit=, default 50). |
GET/v1/payout/{id} | auth | Статус пакета выплат и его позиции (только собственные пакеты). |
POST/v1/payout/{id}/cancel | auth | Отмените ещё ожидающий пакет и освободите удержанный баланс. Возвращает 409, если пакет уже нельзя отменить. |
Ни один метод не подходит.
Одиночные файлы без зависимостей. Все SDK одинаково подписывают запросы и проверяют вебхуки.
PHP 7.4+ и cURL, один файл. createInvoice, getInvoice и verifyWebhook.
Node.js только со встроенными https и crypto. Счета, баланс, выплаты и verifyWebhook.
Только стандартная библиотека Python. Счета, баланс, выплаты и verify_webhook.
Коллекция Postman, которая сама подписывает каждый вызов.
Машиночитаемая спецификация всех методов — для генераторов кода и API-клиентов.
Готовый модуль сделает всё это за вас: счёт, переход на оплату и проверенный вебхук.
Прямые ответы, в том числе на неудобные вопросы: хранение средств, комиссии и что будет, если что-то пойдёт не так.
Справочный центрПоставьте однофайловый PHP-SDK (без зависимостей) или обращайтесь к REST API напрямую. Создайте счёт через createInvoice(), отправьте покупателя на его pay_url и обработайте одно событие вебхука: status=paid. Опытный разработчик запускается за 30–60 минут.
Да. Всё это обычный JSON поверх HTTPS, поэтому интегрироваться можно с любого языка. PHP-SDK — просто удобная обёртка над теми же адресами: создать счёт, получить счёт и проверить подпись вебхука.
Да. Передайте return_url при создании счёта (или ?return_url= в платёжной ссылке) — и на экране успеха, и во время оплаты чек-аут покажет кнопку «Вернуться в магазин». Передайте lang (en, ru, uk, es, de) или ?lang= в ссылке, чтобы покупатель из русского магазина попал на русский чек-аут, а не на английский. Оба параметра необязательны; return_url должен быть абсолютным http(s)-адресом.
Запросы и вебхуки подписываются HMAC-SHA256 по метке времени и сырому телу с вашим api_secret, допуск по времени ±120 секунд, плюс ключи идемпотентности. Подписи сравниваются за постоянное время, чтобы исключить атаку по времени.
Создайте бесплатный аккаунт. Публичный api_key и секретный api_secret появятся в кабинете вместе с SDK для скачивания и полным описанием вебхуков. Ключи можно в любой момент перевыпустить в настройках.
Подписанное событие в JSON: идентификатор счёта, ваш order_id, статус (например, paid), сумма и валюта, а также уникальный id события для идемпотентности. Всегда проверяйте подпись до зачисления заказа и запоминайте id события, чтобы повторная доставка не обработалась дважды.
Да. POST /v1/payout отправляет крипту на 500 кошельков одним запросом — по одному {address, amount, optional tag} на строку, в любой включённой у вас монете. Та же авторизация HMAC-SHA256 и тот же Idempotency-Key, что и у API счетов, поэтому повтор запроса не создаёт вторую пачку. Сумма пачки плюс построчная комиссия резервируется на балансе сразу; подписывает и транслирует оператор, после чего срабатывает подписанный вебхук payout.sent. Проверка строгая: один неверный адрес или слишком точная сумма отклоняют всю пачку (422), чтобы зарплатный прогон не потерял получателя молча.
Соберите список получателей у себя, затем вызовите POST /v1/payout один раз за прогон с устойчивым Idempotency-Key (например, payroll-2026-01). Опрашивайте GET /v1/payout/{id} или дождитесь вебхука payout.sent, чтобы подтвердить доставку и записать хеши. Отправка пачки резервирует средства, но сама их не двигает: подписывает и транслирует оператор, поэтому по одному вызову API ничего не уходит.
Один PHP-SDK без зависимостей: создать счёт, отправить на готовый чек-аут и проверить подпись вебхука. Для любого другого языка есть REST.