واجهة Payout API
أرسل عمليات سحب من رصيد التاجر إلى أي عنوان بلوكتشين.
تتيح لك واجهة Payout API برمجيًا سحب الأموال من رصيد التاجر إلى أي عنوان بلوكتشين.
لجميع نقاط نهاية Payout، يجب استخدام Payout API key منفصل لإنشاء توقيع sign. هذا المفتاح يختلف عن API key العادي ويجب إنشاؤه من إعدادات المشروع.
إنشاء عملية سحب
ينشئ طلب سحب من رصيد التاجر.
/v1/payoutمعاملات الطلب
| الحقل | النوع | مطلوب | الوصف |
|---|---|---|---|
currency | string | نعم | عملة السحب (راجع References) |
network | string | نعم | رمز الشبكة (راجع References) |
amount | string | نعم | مبلغ السحب |
to_address | string | نعم | عنوان البلوكتشين للمستلم |
order_id | string | لا | مفتاح idempotency — فريد داخل المشروع. تكرار POST بنفس order_id لا يُنشئ عملية سحب جديدة — بدلًا من ذلك تُرجع الموجودة |
url_callback | string | لا | عنوان URL لـ webhooks السحب. اتركه فارغًا لتعطيل webhooks لهذه العملية |
memo | string | null | لا | تاج / memo الوجهة. يُستخدم حاليًا فقط في شبكات TON وSOL؛ بحد أقصى 255 حرفًا |
from_currency | string | لا | الرصيد المصدري الذي يُخصم منه ويُحوَّل تلقائيًا إلى currency لحظة السحب. يتيح لك السحب بأصول متقلّبة (BTC، ETH، …) مع الاحتفاظ برصيدك بعملة مستقرّة مثل USDT — لست مضطرًا لحيازة العملة المتقلّبة بنفسك. مرّر "USDT" لخصم رصيد USDT |
fee_option | string | لا | كيف يتم احتساب العمولات. deduct (الافتراضي) — رسوم الشبكة + رسوم المنصة تُخصم من amount، ويستلم المستلم amount - fees. add — تُضاف الرسوم فوقه، ويُخصم من التاجر amount + fees، ويستلم المستلم amount بالضبط |
Idempotency. داخل مشروع، تكون عملية السحب فريدة بواسطة order_id. إعادة إرسال POST بنفس order_id آمن — تُرجع واجهة API عملية السحب الموجودة بدلًا من إنشاء نسخة مكررة. مرّر دائمًا order_id في عمليات السحب الإنتاجية.
أمثلة الطلبات
curl -X POST https://api.2328.io/api/v1/payout \
-H "Content-Type: application/json" \
-H "User-Agent: MyShop/1.0 (+https://myshop.example)" \
-H "project: YOUR_PROJECT_UUID" \
-H "sign: YOUR_HMAC_SIGNATURE" \
-d '{"currency":"TRX","network":"TRX-TRC20","amount":"1.00","to_address":"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t","order_id":"9ed25264-8be4-439f-acf5-2a8732538d27","url_callback":"https://your-site.com/webhook/payout","memo":null,"fee_option":"deduct"}'<?php
function apiSign(string $body, string $apiKey): string {
return hash_hmac('sha256', base64_encode($body), $apiKey);
}
$project = 'YOUR_PROJECT_UUID';
$apiKey = 'YOUR_PAYOUT_API_KEY';
$data = [
'currency' => 'TRX',
'network' => 'TRX-TRC20',
'amount' => '1.00',
'to_address' => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
'order_id' => '9ed25264-8be4-439f-acf5-2a8732538d27',
'url_callback' => 'https://your-site.com/webhook/payout',
'memo' => null,
'fee_option' => 'deduct',
];
$body = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$sign = apiSign($body, $apiKey);
$ch = curl_init('https://api.2328.io/api/v1/payout');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'User-Agent: MyShop/1.0 (+https://myshop.example)',
"project: $project",
"sign: $sign",
],
]);
$response = json_decode(curl_exec($ch), true);import { createHmac } from "crypto";
function apiSign(body, apiKey) {
const base64 = Buffer.from(body, "utf8").toString("base64");
return createHmac("sha256", apiKey).update(base64).digest("hex");
}
const PROJECT_UUID = "YOUR_PROJECT_UUID";
const PAYOUT_API_KEY = process.env.PAYOUT_API_KEY;
const data = {
currency: "TRX",
network: "TRX-TRC20",
amount: "1.00",
to_address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
order_id: "9ed25264-8be4-439f-acf5-2a8732538d27",
url_callback: "https://your-site.com/webhook/payout",
memo: null,
fee_option: "deduct",
};
const body = JSON.stringify(data);
const sign = apiSign(body, PAYOUT_API_KEY);
const res = await fetch("https://api.2328.io/api/v1/payout", {
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": "MyShop/1.0 (+https://myshop.example)",
project: PROJECT_UUID,
sign,
},
body,
});
const json = await res.json();import json
import hmac
import hashlib
import base64
import httpx
def api_sign(body: str, api_key: str) -> str:
b64 = base64.b64encode(body.encode("utf-8")).decode()
return hmac.new(api_key.encode(), b64.encode(), hashlib.sha256).hexdigest()
PROJECT_UUID = "YOUR_PROJECT_UUID"
PAYOUT_API_KEY = "YOUR_PAYOUT_API_KEY"
data = {
"currency": "TRX",
"network": "TRX-TRC20",
"amount": "1.00",
"to_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"order_id": "9ed25264-8be4-439f-acf5-2a8732538d27",
"url_callback": "https://your-site.com/webhook/payout",
"memo": None,
"fee_option": "deduct",
}
body = json.dumps(data, separators=(",", ":"), ensure_ascii=False)
sign = api_sign(body, PAYOUT_API_KEY)
r = httpx.post(
"https://api.2328.io/api/v1/payout",
headers={
"Content-Type": "application/json",
"User-Agent": "MyShop/1.0 (+https://myshop.example)",
"project": PROJECT_UUID,
"sign": sign,
},
content=body.encode("utf-8"),
)
response = r.json()package main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"net/http"
)
func apiSign(body []byte, apiKey string) string {
b64 := base64.StdEncoding.EncodeToString(body)
h := hmac.New(sha256.New, []byte(apiKey))
h.Write([]byte(b64))
return hex.EncodeToString(h.Sum(nil))
}
func marshalCanonical(v any) ([]byte, error) {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
if err := enc.Encode(v); err != nil {
return nil, err
}
return bytes.TrimRight(buf.Bytes(), "\n"), nil
}
type CreatePayout struct {
Currency string `json:"currency"`
Network string `json:"network"`
Amount string `json:"amount"`
ToAddress string `json:"to_address"`
OrderID string `json:"order_id"`
URLCallback string `json:"url_callback"`
Memo *string `json:"memo"`
FeeOption string `json:"fee_option"`
}
func main() {
const projectUUID = "YOUR_PROJECT_UUID"
const payoutAPIKey = "YOUR_PAYOUT_API_KEY"
data := CreatePayout{
Currency: "TRX",
Network: "TRX-TRC20",
Amount: "1.00",
ToAddress: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
OrderID: "9ed25264-8be4-439f-acf5-2a8732538d27",
URLCallback: "https://your-site.com/webhook/payout",
Memo: nil,
FeeOption: "deduct",
}
body, err := marshalCanonical(data)
if err != nil {
panic(err)
}
sign := apiSign(body, payoutAPIKey)
req, _ := http.NewRequest("POST",
"https://api.2328.io/api/v1/payout",
bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "MyShop/1.0 (+https://myshop.example)")
req.Header.Set("project", projectUUID)
req.Header.Set("sign", sign)
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
}مثال على الاستجابة
{
"state": 0,
"result": {
"uuid": "019dea62-1727-72aa-ac2c-eaf2ade193ef",
"order_id": "9ed25264-8be4-439f-acf5-2a8732538d27",
"status": "pending",
"currency": "TRX",
"network": "TRX-TRC20",
"amount": "1.00",
"merchant_amount": "1",
"network_amount": "0.89",
"amount_usd": "0.33",
"to_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"memo": null,
"txid": null,
"block_number": null,
"error_type": null,
"created_at": "2026-05-02T23:29:50+03:00",
"updated_at": "2026-05-02T23:29:50+03:00"
}
}العمولات. الافتراضي fee_option: deduct — رسوم الشبكة + رسوم المنصة تُخصم من amount (يستلم المستلم amount - fees). مرّر fee_option: add لإضافة الرسوم فوق المبلغ — سيستلم المستلم amount بالضبط، ويُخصم من التاجر amount + fees.
حساب عملية السحب
يحسب مبالغ ورسوم السحب دون إنشاء عملية سحب ودون خصم من رصيدك. استخدمه لعرض المبلغ الدقيق الذي سيستلمه المستخدم (أو يدفعه) قبل التأكيد.
/v1/payout/calcمعاملات الطلب
مطابقة لـ إنشاء عملية سحب — نفس الحقول، نفس التوقيع. الحقول order_id و url_callback و to_address و memo مقبولة لكنها مُتجاهَلة: لا تُخزَّن أي عملية سحب ولا تُرسَل أي callbacks.
مثال الطلب
curl -X POST https://api.2328.io/api/v1/payout/calc \
-H "Content-Type: application/json" \
-H "User-Agent: MyShop/1.0 (+https://myshop.example)" \
-H "project: YOUR_PROJECT_UUID" \
-H "sign: YOUR_HMAC_SIGNATURE" \
-d '{"currency":"USDT","network":"TRX-TRC20","amount":"100","fee_option":"add"}'مثال على الاستجابة
{
"state": 0,
"result": {
"currency": "USDT",
"network": "TRX-TRC20",
"amount": "100",
"fee_option": "add",
"merchant_amount": "103.00000000",
"network_amount": "100",
"total_fee": "3.00000000",
"total_fee_usd": "3.00000000"
}
}معاينة فقط. هذه النقطة الطرفية للقراءة فقط — لا يُخصم من الرصيد ولا تُنشَأ أي عملية سحب. اتصل بها بالقدر الذي تحتاجه لعرض تفصيل الرسوم في واجهتك.
حالة السحب
احصل على حالة طلب السحب.
/v1/payout/status/{uuid}معاملات المسار
| الحقل | النوع | مطلوب | الوصف |
|---|---|---|---|
uuid | string | نعم | معرّف السحب UUID (من result.uuid عند الإنشاء) |
مثال على الاستجابة
{
"state": 0,
"result": {
"uuid": "019dff1f-0dbd-7277-8d45-271e7775388f",
"order_id": "4dfdcc84402b1185b71cbe399321533e",
"status": "completed",
"currency": "TRX",
"network": "TRX-TRC20",
"amount": "3.00",
"merchant_amount": "3.00",
"network_amount": "3.00",
"amount_usd": "1.04",
"to_address": "THauRv5tcucQRohXg8NiyGTk16DX1XQG5x",
"memo": null,
"txid": "9242e533703704ef3eaba840f70b4a26333e72c943377ee375fea17badb53def",
"block_number": null,
"error_type": null,
"created_at": "2026-05-07T00:08:38+03:00",
"updated_at": "2026-05-07T00:08:54+03:00",
"from_currency": "USDT",
"debited_amount": "1.050735",
"debited_currency": "USDT"
}
}لطلب GET هذا، يُحسب التوقيع من محتوى فارغ:
hash_hmac('sha256', base64_encode(''), $apiKey)
حقول الاستجابة
الحقول المرجعة في result من POST /v1/payout وGET /v1/payout/status/{uuid}:
| الحقل | النوع | الوصف |
|---|---|---|
uuid | string | معرّف السحب UUID المعيّن من النظام |
order_id | string | معرّف السحب الداخلي الخاص بك (فريد داخل المشروع) |
status | string | حالة السحب الحالية (انظر أدناه) |
currency | string | عملة السحب |
network | string | رمز الشبكة |
amount | string | مبلغ السحب كما طُلب |
merchant_amount | string | المبلغ المخصوم من رصيد التاجر |
network_amount | string | المبلغ المُرسل فعليًا على البلوكتشين (بعد رسوم الشبكة + المنصة) |
amount_usd | string | المكافئ بالدولار الأمريكي لمبلغ السحب |
to_address | string | عنوان البلوكتشين للمستلم |
memo | string | null | تاج / memo الوجهة (TON، SOL). null خلاف ذلك |
txid | string | null | هاش معاملة البلوكتشين. null حتى يتم إرسال المعاملة |
block_number | int | null | رقم البلوك الذي تم تضمين المعاملة فيه. null حتى يتم التضمين |
error_type | string | null | سبب الفشل عند status = failed (راجع أنواع الأخطاء أدناه). null خلاف ذلك |
created_at | string (ISO 8601) | وقت إنشاء السحب |
updated_at | string (ISO 8601) | وقت آخر تغيير حالة |
from_currency | string | null | الرصيد المصدري الذي خُصم منه السحب عند استخدام التحويل التلقائي (مثلًا USDT لسحب بـ BTC). يكون null إذا لم يحدث تحويل |
debited_amount | string | null | المبلغ المخصوم فعليًا من الرصيد المصدري بعد التحويل. يكون موجودًا فقط عند استخدام التحويل التلقائي |
debited_currency | string | null | عملة debited_amount — الرصيد الذي خُصمت منه الأموال |
حالات السحب
يمكن أن يأخذ حقل status القيم التالية:
| الحالة | الوصف |
|---|---|
pending | تم إنشاؤها، بانتظار المعالجة |
completed | تمت بنجاح — تم تعيين txid |
failed | خطأ في الإرسال — راجع error_type |
cancelled | ملغاة |
أنواع الأخطاء
عندما يكون status = failed، يصف حقل error_type السبب:
| الرمز | الوصف |
|---|---|
aml_risk | تم حظر السحب بواسطة فحوصات مخاطر AML (تم تصنيف عنوان المستلم على أنه عالي المخاطر) |
إشعارات webhook
عندما تتغير حالة عملية سحب، يرسل النظام webhook بطريقة POST إلى عنوان url_callback الذي تم تمريره عند إنشاء السحب. إذا لم يتم توفير url_callback، فلن يتم إرسال أي webhooks لتلك العملية.
- الطريقة:
POST - Content-Type:
application/json - التوقيع: حقل
signفي محتوى الطلب، محسوب باستخدام Payout API key (نفس المفتاح المستخدم لتوقيع طلبات السحب).
يعكس المحتوى كائن result من GET /v1/payout/status/{uuid} بالإضافة إلى حقل sign للتحقق.
المحتوى
{
"uuid": "019dff1f-0dbd-7277-8d45-271e7775388f",
"order_id": "4dfdcc84402b1185b71cbe399321533e",
"status": "completed",
"currency": "TRX",
"network": "TRX-TRC20",
"amount": "3.00",
"merchant_amount": "3.00",
"network_amount": "3.00",
"amount_usd": "1.04",
"to_address": "THauRv5tcucQRohXg8NiyGTk16DX1XQG5x",
"memo": null,
"txid": "9242e533703704ef3eaba840f70b4a26333e72c943377ee375fea17badb53def",
"block_number": null,
"error_type": null,
"created_at": "2026-05-07T00:08:38+03:00",
"updated_at": "2026-05-07T00:08:54+03:00",
"from_currency": "USDT",
"debited_amount": "1.050735",
"debited_currency": "USDT",
"sign": "925ad7bf3d6841864101f7cc2c7e30652e70a06cdb04dbe07a0129480000ce4a"
}التحقق من التوقيع. استخدم نفس الخوارزمية المستخدمة في webhooks المدفوعات، لكن وقّع باستخدام Payout API key بدلًا من API key العادي. أزل حقل sign، رمّز المحتوى المتبقي بـ JSON، رمّزه بـ Base64، ثم احسب hash_hmac('sha256', $base64, $payoutApiKey) وقارنه بـ sign المستلم.