في هذه الصفحة
دليل التكامل
وثائق المطورين
أنشئوا فاتورة، ووجّهوا العميل إلى صفحة الدفع، واستلموا إشعار webhook موقّعًا عند السداد. ابدأوا بالخطوات الأربع أدناه.
ما الذي ندعمه
تقبل البوابة USDT وUSDC. يطابق هذا الجدول GET /v1/services، وقد جرى التحقق منه في 4 نوفمبر 2026. اطّلعوا على الكتالوج الحالي قبل بناء أداة اختيار طريقة الدفع.
| الأصل | الشبكة | التأكيدات |
|---|---|---|
| USDT | Tron | 20 |
| USDT | Ethereum | 12 |
| USDT | Solana | 32 |
| USDT | Polygon | 8 |
| USDT | Arbitrum | 12 |
| USDT | TON | 5 |
| USDC | Ethereum | 12 |
| USDC | Solana | 32 |
| USDC | Polygon | 8 |
| USDC | Base | 12 |
| USDC | Arbitrum | 12 |
المدفوعات احتجازية (custodial): تصل الأموال إلى عناوين تتحكم بها المنصة. رصيد التاجر لديكم هو المبلغ المستحق لكم. أما محفظة Telegram المنفصلة فهي غير احتجازية (non-custodial).
القيود
- المدفوعات الصادرة والاسترداد غير متاحة بعد: يُظهر
GET /v1/servicesالقيمتينboundaries.livePayoutsوboundaries.liveRefundsعلى أنهماfalse. - لا يفحص أي مزوّد KYT المدفوعات الواردة.
- تُرفض الفواتير التي تتجاوز رسومها 20% من المبلغ مع الخطأ
AMOUNT_BELOW_MINIMUM. - لا توجد اتفاقية SLA تعاقدية.
الرسوم الأولية 0.40% إضافةً إلى رسوم الشبكة. اطلبوا عرض سعر لمعرفة إجمالي الرسوم والحد الأدنى للمبلغ؛ راجعوا الأسعار.
المصادقة
تستخدم الاستدعاءات بين الخوادم الترويسة x-api-key. يقتصر نطاق مفاتيح المشروع على مشروع تاجر واحد. احتفظوا بها على خادمكم.
تبدأ مفاتيح الاختبار بـ ncp_test_…، وتبدأ المفاتيح المباشرة بـ ncp_live_…. أما المفاتيح الصادرة سابقًا فتبدأ بـ ncp_sandbox_، وتظل تعمل.
curl -sS "https://api.veilnebu.la/v1/balances" \
-H "x-api-key: $PROJECT_API_KEY"توقيع طلبات الكتابة
تحتاج طلبات GET إلى x-api-key فقط. أما كل طلب POST وPUT وPATCH وDELETE يُصادَق عليه بمفتاح مشروع فيحتاج أيضًا إلى ترويستين، وإلا يردّ API بـ 401 Invalid API signature:
x-ncp-timestamp: بصيغة ISO-8601، وفي حدود 5 دقائق من الوقت الحالي.x-ncp-signature:hex(HMAC-SHA256(your API key, "<timestamp>.<METHOD>.<path incl. query>.<raw body>")).
اكتبوا أسماء الطرق بأحرف كبيرة. وقّعوا البايتات التي ترسلونها كما هي بالضبط، بما في ذلك سلسلة الاستعلام في المسار. الطلب الذي لا يحتوي على متن يوقّع سلسلة فارغة. تتطلب الأدوات المساعدة أدناه مفتاح مشروعكم؛ وتتطلب أداة shell المساعدة Bash وcurl وOpenSSL، بينما تستخدم أداة Node.js المساعدة fetch المدمجة.
export API_BASE_URL="https://api.veilnebu.la"
export PROJECT_API_KEY="ncp_test_…"
ncp() {
local method="$1" path="$2" body="" ts sig
shift 2
if [ "$#" -gt 0 ]; then body="$1"; shift; fi
ts=$(date -u +%Y-%m-%dT%H:%M:%S.000Z) || return
sig=$(printf '%s.%s.%s.%s' "$ts" "$method" "$path" "$body" |
openssl dgst -sha256 -hmac "$PROJECT_API_KEY" -hex) || return
sig=${sig##* }
if [ -n "$body" ]; then
curl -sS --request "$method" "$API_BASE_URL$path" \
-H "x-api-key: $PROJECT_API_KEY" \
-H "x-ncp-timestamp: $ts" -H "x-ncp-signature: $sig" \
-H "content-type: application/json" --data-binary "$body" "$@"
else
curl -sS --request "$method" "$API_BASE_URL$path" \
-H "x-api-key: $PROJECT_API_KEY" \
-H "x-ncp-timestamp: $ts" -H "x-ncp-signature: $sig" "$@"
fi
}إنشاء أول فاتورة
أنشئوا مشروعًا في لوحة التحكم، وانسخوا مفتاح API الخاص به، وأنشئوا فاتورة. تحتوي الاستجابة على checkoutUrl: وجّهوا عميلكم إليه.
ncp POST /v1/payments '{
"amountMinor":"10000",
"currencyCode":"USD",
"settlementAssetCode":"USDT",
"externalId":"order-1001"
}' -H "idempotency-key: order-1001"مقتطف من الاستجابة. يُنشأ المعرّف لكل فاتورة على حدة.
{
"id": "cm3k9x2f40001qz8e7h5v1abc",
"status": "new",
"amountMinor": "10000",
"currencyCode": "USD",
"settlementAssetCode": "USDT",
"checkoutUrl": "https://app.veilnebu.la/checkout/cm3k9x2f40001qz8e7h5v1abc"
}amountMinor سلسلة نصية لعدد صحيح بأصغر وحدة في العملة: 10000 = 100.00 USD. لا يُقبل الحقل العشري amount إلا في نقطة نهاية المتصفح /v1/public/payments.
الدفع عبر المتصفح والمصادر (origins) المسموح بها
أنشئوا مفتاحًا قابلًا للنشر (publishable) في لوحة التحكم، وأدرجوا المصادر الدقيقة التي يستخدمها متجركم. ترسله طلبات المتصفح في x-publishable-key إلى POST /v1/public/payments.
يجب أن يكون كلٌّ من المصدر (Origin) وsuccessUrl وcancelUrl ضمن قائمة المصادر المسموح بها للمفتاح. المصدر هو المخطط (scheme) والمضيف والمنفذ الاختياري، دون مسار أو حرف بدل. تُرفض المصادر المفقودة أو غير المدرجة.
يقبل المبلغ العشري حتى رقمين بعد الفاصلة للعملات الورقية، وحتى عدد الخانات العشرية الخاص بالأصل للعملات المشفرة (USDT وUSDC: 6 على كل شبكة ندعمها اليوم).
const response = await fetch('https://api.veilnebu.la/v1/public/payments', {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-publishable-key': 'pk_test_…',
},
body: JSON.stringify({
amount: '100.00',
currencyCode: 'USD',
settlementAssetCode: 'USDT',
successUrl: 'https://shop.example/thanks',
cancelUrl: 'https://shop.example/cart',
}),
});
if (!response.ok) throw new Error(await response.text());
const invoice = await response.json();
window.location.assign(invoice.checkoutUrl);حالات الدفع
| الحالة | الإجراء التالي |
|---|---|
new | مشاركة رابط صفحة الدفع |
pending | بانتظار الدفع |
confirming | بانتظار التأكيدات |
paid | جاهزة للتسوية |
partially_paid | اطلبوا من العميل إكمال الدفع |
overpaid | مراجعة المبلغ الإضافي المقيَّد |
expired | إنشاء فاتورة جديدة |
cancelled | لا يلزم أي إجراء |
failed | فتح التفاصيل |
refunded | لا يلزم أي إجراء |
مفاتيح منع التكرار
عند إنشاء الفواتير، أرسلوا Idempotency-Key ثابتًا مشتقًا من معرّف طلبكم. إعادة المحاولة بالمفتاح والمتن نفسيهما تُرجع الفاتورة الأصلية. أما إعادة استخدامه مع متن مختلف فتُرجع 409. أبقوا المفتاح والمتن دون تغيير عند إعادة المحاولة بعد انتهاء المهلة.
راجعوا مثال أول فاتورة للاطلاع على الطلب.
الأخطاء
الأخطاء بصيغة JSON. message سلسلة نصية، أو مصفوفة من السلاسل النصية في أخطاء التحقق.
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid API key"
}| حالة HTTP | المعنى والإجراء |
|---|---|
400 | حقل غير صالح؛ يذكر message اسم الحقل. صحّحوه قبل إعادة المحاولة. |
401 | Invalid API key أو Invalid API signature. تحققوا من المفتاح والبايتات الموقّعة والطابع الزمني. |
404 | Payment invoice not found. تحققوا من المعرّف والمشروع. |
409 | Idempotency-Key نفسه مع متن مختلف، أو Insufficient merchant payable balance. |
429 | تم تجاوز حد المعدل. انتظروا قبل إعادة المحاولة. |
5xx | أعيدوا المحاولة بـ Idempotency-Key والمتن نفسيهما. |
إشعارات Webhook (HMAC-SHA256)
يحمل كل تسليم الترويسة x-nebula-signature: t=<unix>,v1=<hex>. تحققوا من HMAC باستخدام سر نقطة النهاية الذي أُعيد عند إنشائها. ارفضوا الطوابع الزمنية التي تبعد أكثر من 300 ثانية عن الوقت الحالي.
التحقق في Node.js
لا تستخدم أداة التحقق في Node.js سوى وحدة crypto المدمجة. مرّروا إليها ترويسة التوقيع وبايتات المتن الخام وسر نقطة النهاية.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(header, rawBody, secret) {
const parts = {};
for (const piece of (header ?? '').split(',')) {
const separator = piece.indexOf('=');
if (separator <= 0) continue;
parts[piece.slice(0, separator).trim()] = piece.slice(separator + 1).trim();
}
if (!/^[0-9]{1,16}$/.test(parts.t ?? '')) return false;
if (!/^[0-9a-f]{64}$/.test(parts.v1 ?? '')) return false;
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = createHmac('sha256', secret)
.update(parts.t + '.')
.update(rawBody)
.digest('hex');
if (expected.length !== parts.v1.length) return false;
return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}تحققوا مقابل متن الطلب الخام، قبل تحليل JSON. يتم التسليم مرة واحدة على الأقل: أزيلوا التكرار بالاعتماد على x-nebula-event-id، الذي يطابق eventId في الحمولة. تحتفظ إعادات المحاولة التلقائية بمعرّف التسليم نفسه؛ أما إعادة الإرسال اليدوية فتُنشئ معرّف تسليم جديدًا بمعرّف الحدث نفسه. احفظوا الحدث قبل الرد بـ 2xx.
اختبار إشعارات webhook محليًا
يجب أن تستخدم عناوين URL الخاصة بإشعارات webhook مضيفًا عامًا على الإنترنت. تُرفض العناوين المحلية والخاصة. اكشفوا المستقبِل لديكم عبر نفق HTTPS عام، ثم سجّلوا عنوان URL الخاص به باستخدام أداة التوقيع المساعدة.
ncp POST /v1/webhooks/endpoints '{
"url":"https://receiver.example/webhook",
"events":["payment.paid","payment.reorged"]
}'استبدلوا عنوان URL بعنوان المستقبِل لديكم. احفظوا سر نقطة النهاية الذي يُعاد عند الإنشاء.
أنواع الأحداث
المدفوعات الصادرة والاسترداد غير متاحة بعد (يُظهر GET /v1/services القيمة boundaries.livePayouts على أنها false). وحتى ذلك الحين، تقتصر المدفوعات الصادرة وحزمها على تسجيل التحويل في رصيدكم؛ ولا يعني payout.completed وrefund.completed أن الأموال قد غادرت.
يطابق الكتالوج أدناه GET /v1/services. وجود إدخال في الكتالوج لا يعني أن حركة الأموال المقابلة متاحة فعليًا.
| الحدث | المعنى |
|---|---|
webhook. | تسليم تجريبي مطلوب لنقطة نهاية webhook. |
payment. | اختار العميل طريقة دفع؛ بانتظار الإيداع الأول. |
payment. | رُصد الإيداع؛ بانتظار التأكيدات المطلوبة. |
payment. | سُدّدت الفاتورة بالكامل وسُوّيت في السجل. |
payment. | استُلم مبلغ أقل من المطلوب؛ بانتظار الباقي. |
payment. | استُلم مبلغ أكبر من المطلوب؛ سُوّيت في السجل مع المبلغ الزائد. |
payment. | انتهت صلاحية الفاتورة دون أموال كافية. |
payment. | أُلغيت الفاتورة قبل الدفع. |
payment. | فشل الدفع بسبب خطأ في السلسلة أو في سياسة الرسوم أو في الرصد. |
payment. | حدث مخاطر استشاري لإيداع مقيَّد بالفعل. فحص KYT للمدفوعات الواردة غير مفعّل. |
payment. | أُسقط إيداع مؤكَّد بسبب إعادة تنظيم السلسلة؛ لم تعد الفاتورة مدفوعة، لذا أوقفوا تنفيذ الطلب أو تراجعوا عنه. |
payment. | أُودع رمز غير مدعوم؛ ولم يُقيَّد في الرصيد. |
payout. | حالة معالجة الدفعة الصادرة. التحويلات الفعلية غير متاحة. |
payout. | سُجّلت الدفعة الصادرة في الرصيد؛ وهذا لا يعني أن الأموال قد غادرت. |
payout. | فشلت الدفعة الصادرة. |
payout. | أُلغيت الدفعة الصادرة قبل اكتمالها. |
refund. | حالة معالجة الاسترداد. عمليات الاسترداد الفعلية غير متاحة. |
refund. | سُجّل الاسترداد في الرصيد؛ وهذا لا يعني أن الأموال قد غادرت. |
refund. | فشل الاسترداد. |
internal_transfer. | نُفّذ تحويل رصيد بين التجار لدى المرسِل. |
internal_transfer. | قُيّد تحويل رصيد بين التجار لصالح المستلِم. |
static_wallet. | أُنشئ عنوان إيداع طويل الأمد لعميل. |
static_wallet. | حُظرت محفظة ثابتة. |
static_wallet. | رُفع الحظر عن محفظة ثابتة. |
static_wallet. | رُصد إيداع في محفظة ثابتة وسُوّي في السجل. |
dispute. | فُتح نزاع على فاتورة. |
dispute. | حُلّ النزاع. الحل وحده لا يثبت حدوث استرداد على السلسلة. |
dispute. | رُفض النزاع؛ لا يلزم أي إجراء من التاجر. |
subscription. | أنشأت دورة فوترة فاتورة جديدة. |
سياسة إعادة المحاولة
إذا لم تردّ نقطة النهاية لديكم بـ 2xx، نحاول 8 مرات إجمالًا: المحاولة الأولى، ثم إعادة المحاولة بعد دقيقة واحدة، و5 دقائق، و30 دقيقة، وساعتين، و6 ساعات، و12 ساعة، و24 ساعة. يبدأ كل تأخير بعد المحاولة الفاشلة السابقة. تُحتسب أخطاء الشبكة وانتهاء المهلة أيضًا حالات فشل. للتسليمات التجريبية محاولة واحدة فقط.
بعد آخر فشل، يُعلَّم التسليم على أنه فاشل؛ أعيدوا إرساله باستخدام POST /v1/webhooks/deliveries/{deliveryId}/resend. وكحل احتياطي، أجروا التسوية باستخدام GET /v1/payments/{id}.
المدفوعات الصادرة والاسترداد غير متاحة بعد (يُظهر GET /v1/services القيمة boundaries.livePayouts على أنها false). وحتى ذلك الحين، تقتصر المدفوعات الصادرة وحزمها على تسجيل التحويل في رصيدكم؛ ولا يعني payout.completed وrefund.completed أن الأموال قد غادرت.
حزم المدفوعات الصادرة
تجمع الحزمة سجلات التحويل. تُعاد سياسة الموافقة وعدد الموافقات المطلوب في approvalPolicy وapprovalsRequired. عند وجود عدة موافِقين، لا يمكن لمنشئ الحزمة الموافقة على حزمته. ويجوز للمشغّل المؤهل الوحيد الموافقة على حزمة أنشأها بنفسه خلال جلسته. وافقوا على الحزم باستخدام WebAuthn في لوحة التحكم.
export PROJECT_API_KEY="ncp_test_…"
ncp POST /v1/payout-batches '{
"assetCode":"USDT",
"networkCode":"TRON",
"externalId":"batch-1001",
"items":[{
"destinationAddress":"T…",
"amountMinor":"15000000",
"externalRef":"recipient-1001"
}]
}' -H "idempotency-key: batch-1001"استبدلوا الوجهة بعنوان صالح في مشروع الاختبار الخاص بكم.
الفوترة المتكررة
يُنشئ الاشتراك فاتورة جديدة في كل دورة فوترة ويُبلغ عنها عبر subscription.invoice_created. ولا يخصم من محفظة العميل تلقائيًا. أرسلوا إلى العميل عنوان URL الجديد لصفحة الدفع.
interval: DAY أو WEEK أو MONTH أو YEAR.
ncp POST /v1/subscriptions '{
"customerRef":"customer-42",
"assetCode":"USDT",
"networkCode":"TRON",
"amountMinor":"9000000",
"interval":"MONTH",
"intervalCount":1
}'يمكنكم الإيقاف المؤقت أو الاستئناف أو الإلغاء باستخدام عمليات دورة الحياة في مرجع API.
فحص المخاطر
تُفحص وجهات المدفوعات الصادرة والاسترداد مقابل قوائم العقوبات العامة قبل إنشاء التحويل؛ وأي تطابق يؤدي إلى رفض الطلب. لا تُفحص المدفوعات الواردة.
بوت Telegram للتجار
يعمل @veilnebula_bot مع مشروع واحد في كل مرة. اربطوه في محادثة خاصة باستخدام مفتاح API لذلك المشروع. يحاول البوت حذف رسالتكم ويشفّر المفتاح المخزَّن. إذا فشل الحذف، فألغوا المفتاح واستبدلوه بمفتاح جديد. لا تنشروا أي مفتاح في محادثة مشتركة أبدًا.
تقتصر الإشعارات على المشروع المرتبط والمفتاح النشط. افتحوا لوحة التحكم للموافقة على المدفوعات الصادرة.
/link <API_KEY>
/balance
/invoices
/notify on
/unlink