PHP SDK
一个文件,无依赖。createInvoice()、getInvoice() 和 verifyWebhook() — 将其放入任何项目中。
api.payora.money一个API调用,带有订单ID和法币或加密货币金额,返回一个托管支付URL。
他们选择一种币,扫描二维码或打开钱包,结账在最终确认时实时确认。
验证HMAC签名,记入订单一次,然后观察付款到账您的余额。
一个文件,无依赖。createInvoice()、getInvoice() 和 verifyWebhook() — 将其放入任何项目中。
请求和 webhook 使用 HMAC-SHA256 签名,带有时间戳偏差和幂等性密钥。
通过 HTTPS 传输的纯 JSON — 可从任何技术栈集成,而不仅仅是 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密钥和秘密。完整的SDK在您的仪表板中。
除了 /v1/health 之外的每个调用都携带三个签名头。签名证明请求来自您的 api_secret 持有者,并且在传输过程中没有被更改。
| 头部 | 值 | 描述 |
|---|---|---|
X-Payora-Key | pk_… | 您的公共api_key。标识商店。 |
X-Payora-Timestamp | unix seconds | 您签署请求的时刻,以Unix秒为单位。 |
X-Payora-Signature | hex · 64 | 时间戳 + "." + 原始主体的小写十六进制HMAC-SHA256,使用您的api_secret进行加密。 |
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请求,主体为空,因此签名字符串是时间戳后跟一个点。
API拒绝与其时钟相差超过120秒的时间戳。保持您的服务器与NTP同步。
将api_secret保留在您的服务器上。它对请求和Webhook进行签名:持有它的人可以以您的名义创建发票并伪造回调。
<?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 外,所有均为可选。法定货币模式允许付款人选择您账户上启用的任何币种;加密货币模式还需要 currency。
| 参数 | 类型 | 描述 |
|---|---|---|
order_id | string · required | 您的唯一订单参考(≤190 个字符)。重复提交将被拒绝,状态码为 409——防止重复提交。 |
amount | string · required | 法定金额(mode=fiat)或确切的加密金额(mode=crypto)。 |
mode | fiat | crypto | 默认法币:结账时每个币的汇率被锁定。加密模式提前固定币种+金额。 |
fiat_currency | string | 法定模式的ISO法定代码(默认USD)。 |
currency | string | 加密模式的币(例如:TON,TRX,USDT_TRON)。 |
return_url | https URL | 结账的 “返回商店” 按钮将客户发送回——您的网站。仅限绝对 http(s)。 |
lang | en·ru·uk·es·de | 结账语言,因此来自您俄罗斯商店的客户看到的是俄语结账——而不是英语。也可以在支付 URL 上设置为 ?lang=ru。 |
ttl | seconds | 发票可支付的时间(120–86400 秒,默认 1800)。 |
customer_email | 可选——收据 + 发送给付款人。 | |
notes | string | 存储在发票上的自由格式备注。 |
return_url 和 lang 也可以作为查询参数附加到 API 提供的 pay_url — <pay_url>&lang=ru&return_url=https://shop.example/thanks。始终将买家发送到 API 返回的 pay_url:它携带一个每发票唯一的访问令牌,因此无法通过猜测发票号码访问结账页面。请勿从发票 ID 重建链接。
填写字段并复制一个准备好的 cURL 命令。它在您的 shell 中使用 $PAYORA_KEY 和 $PAYORA_SECRET 自行签名——此页面从未看到您的密钥并且不发送任何内容。
BODY='{"order_id":"ORDER-42","mode":"fiat","amount":"9.99","fiat_currency":"USD","return_url":"https://shop.example/thanks","lang":"zh","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 会向您的回调 URL 发送一个签名的 JSON 事件。首先验证签名,确认订单一次,返回 2xx。
| 事件 | status | 触发时 |
|---|---|---|
invoice.paid | paid | 支付在链上确认。携带金额、币种和 tx_hash。 |
invoice.underpaid | underpaid | 发票在收到的金额少于预期后过期。 |
invoice.expired | expired | 发票在未收到任何内容的情况下过期。 |
invoice.cancelled | cancelled | 发票已被取消。已到达的资金(如果有)在事件中描述。 |
invoice.cancel_settled | — | 在已取消的发票上到达的资金已被处理:已记入或退回。 |
invoice.late_payment | — | 在发票被取消后到达的款项。每个此类存款发送一次;结果说明它是被记入还是正在退回。 |
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 | 与请求相同的方案:使用时间戳 + "." + 原始主体与您的 api_secret 进行 HMAC-SHA256。 |
| 字段 | 类型 | 描述 |
|---|---|---|
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 代码;加密模式发票为 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 次尝试。每次尝试都将使用新的时间戳重新签名;主体和事件 ID 保持不变,因此按 ID 去重。
在最后一次尝试后,事件被标记为失败,商店所有者会收到一封电子邮件。重定向永远不会被跟随,回调 URL 必须可以从互联网访问 — 不允许使用 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输入一个密钥、时间戳和原始主体,以查看 Payora 将发送的确切签名——或检查您收到的签名。
在您的浏览器中使用 WebCrypto 运行。您输入的内容不会离开此页面。在共享计算机上使用测试密钥,而不是实时密钥。
1758038400.{…}X-Payora-Timestamp: 1758038400
X-Payora-Signature: …粘贴一个签名进行比较
通过一次签名的幂等请求将加密货币发送到多达 500 个钱包——用于 payroll、联盟支付或提款。批次总额加上每项费用会立即保留在您的 internal balance 中;operator signs and broadcasts,然后触发一个签名的 payout.sent webhook。与其他所有内容相同的 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取消仍在待处理的批次以释放保留。请参阅架构写作。
每个端点通过HTTPS以普通JSON格式在api.payora.money上进行通信。除了/v1/health,其他都使用相同的HMAC-SHA256请求认证。机器可读的规范位于openapi.json。
| 端点 | 访问 | 描述 |
|---|---|---|
GET/v1/health | no auth | 服务状态以及启用货币的列表。 |
POST/v1/invoice | auth | 创建发票(法币或加密模式)。支持Idempotency-Key。返回201,包含invoice_id,status,pay_url和expires;加密模式添加地址、金额和深度链接。 |
GET/v1/invoice/{id} | auth | 发票状态(仅限自己的发票):status,remaining,以及charge——货币、地址、预期/收到的金额和tx_hash。 |
POST/v1/payout | auth · Pro+ | 创建一个大额支付批次(严格验证,资金立即被占用)。支持Idempotency-Key。返回201,批次视图:batch_id,status,总计,费用,项目。 |
GET/v1/payout | auth | 列出您最近的支付批次(?limit=,默认50)。 |
GET/v1/payout/{id} | auth | 支付批次状态和项目(仅限自己的批次)。 |
POST/v1/payout/{id}/cancel | auth | 取消仍在待处理的批次并释放被占用的余额。如果批次不再可取消,则返回409。 |
没有匹配的端点。
无依赖的单个文件。每个SDK以相同的方式签署请求并验证Webhook。
PHP 7.4+ 与 cURL,单个文件。createInvoice、getInvoice 和 verifyWebhook。
仅使用内置 https 和 crypto 的 Node.js。发票、余额、支付和 verifyWebhook。
仅使用 Python 标准库。发票、余额、支付和 verify_webhook。
一个为您签署每个调用的 Postman 集合。
每个端点的机器可读规范,供代码生成器和API客户端使用。
一个现成的模块为您完成上述所有操作:发票、重定向和经过验证的Webhook。
安装单文件 PHP SDK(无依赖)或直接调用 REST API。使用 createInvoice() 创建发票,将客户重定向到其 pay_url,并处理一个 webhook 事件:status=paid。一个合格的开发者在 30-60 分钟内可以上线。
是的。一切都是通过 HTTPS 的普通 JSON,因此您可以使用任何语言进行集成。PHP SDK 是对相同端点的便利封装——创建发票、获取发票和验证 webhook 签名。
是的。在您创建发票时传递 return_url(或在支付链接上使用 ?return_url=),结账时在成功屏幕上显示“返回商店”按钮,客户在支付时也会看到。传递 lang(en、ru、uk、es、de),或在链接上使用 ?lang=,这样来自您俄罗斯商店的客户就会进入俄语结账页面,而不是英语页面。两者都是可选的;return_url 必须是绝对的 http(s) URL。
请求和 webhook 使用您的 api_secret 通过时间戳和原始主体进行 HMAC-SHA256 身份验证,具有 ±120 秒的偏差窗口和幂等性密钥。签名在恒定时间内进行比较,以防止时间攻击。
创建一个免费账户。您的公共 api_key 和秘密 api_secret 会出现在仪表板中,以及可下载的 SDK 和完整的 webhook 参考。可以随时从设置中轮换密钥。
一个带有发票 ID、您的 order_id、状态(例如已支付)、金额和货币,以及一个用于幂等性的唯一事件 ID 的签名 JSON 事件。在记入订单之前始终验证签名,然后记录事件 ID 以避免在重试时重复处理。
是的。POST /v1/payout可以在一个请求中将加密货币发送到多达500个钱包——每个项目一个{地址,金额,选填标签},支持您账户上启用的任何币种。它使用与发票API相同的HMAC-SHA256身份验证和幂等性密钥,因此重试请求不会创建重复的批次。批次总额加上每项费用会立即保留在您的余额上;运营商签名并广播,然后触发已签名的payout.sent webhook。验证非常严格——任何无效地址或过于精确的金额都会拒绝整个批次(422),因此工资运行不会静默丢失收件人。
在您的系统中构建收件人列表,然后每次运行时调用 POST /v1/payout,使用稳定的 Idempotency-Key(例如 payroll-2026-01)。轮询 GET /v1/payout/{id} 或等待 payout.sent webhook 确认交付并记录每个 tx hash。提交批次保留资金,但不会单独移动它们——操作员签名并广播,因此仅通过 API 调用不会有任何资金流出。