Paysell

اقبل المدفوعات بالعملات الرقمية

تُسوّي Paysell مدفوعات TON وUSDT على شبكة TON. تُنشئ فاتورة، نمنحك رابطًا، وتحصل على استدعاء رجوع (callback) موقّع بمجرد تأكيد الأموال على السلسلة وإضافتها إلى رصيدك.

نظرة عامة#

ما الذي تفعله Paysell، وما لا تفعله.

Paysell معالج مدفوعات، وليست محفظة. لن تتعامل أبدًا مع مفاتيح خاصة، أو تراقب البلوك تشين، أو تقرر متى تصبح المعاملة نهائية — هذا الجزء نتولاه نحن.

تحصل كل فاتورة على عنوان استلام خاص بها. عندما يدفعها المشتري، ننتظر تأكيد الشبكة للتحويل، نخصم عمولتنا، ونضيف الباقي إلى رصيدك. يمكنك السحب إلى أي عنوان تريده.

تُحفظ الأرصدة لدينا وهي المصدر الوحيد للحقيقة. اعرضها، لكن لا تحتفظ أبدًا بنسخة ثانية باعتبارها مرجعية — فأي عدّادين ينحرفان عن بعضهما في النهاية، وعندها لن يعرف أحد أيهما الصحيح.

كيف تتم عملية الدفع#

ست خطوات، معظمها من جانبنا.

ست خطوات، معظمها من جانبنا:

  1. 1

    يضغط عميلك على الدفع

    يستدعي خادمك واجهتنا البرمجية بالمبلغ ومرجع طلبك الخاص.

  2. 2

    نمنح عنوانًا

    يُؤخذ عنوان استلام جديد من مجموعة مُعدة مسبقًا ويُربط بهذه الفاتورة. يخص العنوان فاتورة واحدة مفتوحة فقط، وبهذا يُطابَق الدفع معها.

  3. 3

    يرسل العميل العملات

    يمسح رمز QR أو ينسخ العنوان. وجّهه إلى payment_url التي نُعيدها، وستتولى الصفحة كل شيء عنك — المبلغ والعنوان ورمز QR والعد التنازلي والحالة المباشرة.

  4. 4

    نكتشف التحويل

    يُستعلَم عن مصدرين مستقلين لبيانات البلوك تشين، وتُقارَن إجاباتهما. إذا اختلفا، نتوقف بدلاً من اختيار الإجابة الأنسب.

  5. 5

    ننتظر النهائية (finality)

    الإدراج في السلسلة الرئيسية بالإضافة إلى ثلاث كتل فوقها. نحو خمس عشرة ثانية — فالدفعة التي تبدو مكتملة ثم تختفي لاحقًا ستكون خسارتك، لذا لا نخاطر بذلك.

  6. 6

    تُضاف إلى الرصيد، ويصلك إشعار

    تُخصم العمولة، ويصل الباقي إلى رصيدك، ويُرسَل ويب هوك موقّع إلى خادمك يحمل order_id الخاص بك.

من الدفع إلى الاستدعاء الرجعي: نحو دقيقة واحدة — نحو خمس عشرة ثانية لتأكيدات الشبكة، والباقي هو مسحنا للعناوين المراقَبة.

إلى أين يذهب المال#

العمولة، وعلى أي أساس تُحسب.

العمولة 0.2%، وتُثبَّت لمتجرك عند لحظة تسجيله. إذا تغيّر السعر القياسي لاحقًا، لن يتغيّر سعرك — فهو مكتوب في كل فاتورة كرقم، لا كإشارة إلى إعداد.

تُحسَب العمولة على ما وصل فعليًا، لا على ما طلبته الفاتورة. أصدرت فاتورة بـ5 USDT ووصلك 20، تُحسَب العمولة على 20. إذا دُفع أقل، تُحسَب على ما وصل.

example
Invoice:   5.000000 USDT
Received: 20.000000 USDT   (the buyer sent more)
Fee 0.2%:  0.040000 USDT   (on 20, not on 5)
Credited: 19.960000 USDT

يُضاف المبلغ الزائد بالكامل إلى الرصيد — نحن لا نحتفظ بالفرق. الدفع الناقص يُبقي الفاتورة مفتوحة ليتمكن المشتري من إتمام المبلغ إلى نفس العنوان.

إخراج العملات من عنوان الاستلام يكلّف غازًا على الشبكة، ونحن من يدفعه — هذا الجزء لا يمسّ رصيدك أبدًا. أما السحب إلى عنوانك الخاص فأمر مختلف: له رسومه الخاصة التي تُخصم من المبلغ الذي تطلبه، والأرقام الدقيقة موجودة في جدول الرسوم.

البدء السريع#

خمس دقائق حتى أول فاتورة لك.

خمس خطوات. اثنتان منها نقرات في لوحة حسابك، وواحدة طلب واحد من خادمك، والأخيرتان تحدثان من تلقاء نفسيهما.

  1. 1

    أنشئ متجرًا

    من لوحة حسابك. يبدأ فورًا في قبول المدفوعات — دون انتظار المراجعة. يجري التحقق بهدوء في الخلفية ولا يُقيّد سوى عمليات السحب، لا المدفوعات الواردة.

  2. 2

    أصدر مفتاح API

    متجرك ← مفاتيح API ← مفتاح جديد. يُعرض المفتاح وسرّ الويب هوك مرة واحدة فقط، ولا يُعرضان بعدها أبدًا. احفظهما كما تحفظ كلمة مرور قاعدة بيانات، ولا ترسلهما أبدًا إلى المتصفح.

  3. 3

    أنشئ فاتورة

    طلب واحد من خادمك، ورابط واحد في المقابل. الأمثلة الأربعة أدناه ترسل الشيء نفسه تمامًا.

  4. 4

    أرسل المشتري إلى payment_url

    هذه هي صفحة الدفع بكاملها — المبلغ والعنوان ورمز QR والعد التنازلي والحالة الحيّة — ولا شيء عليك بناؤه. لمعرفة ما يراه المشتري فعليًا انظر صفحة الدفع.

  5. 5

    انتظر الويب هوك

    بمجرد تأكيد المال على السلسلة وقيده في رصيدك، نرسل إلى خادمك طلب POST يحمل حدث payment.credited موقَّعًا. تحقق من التوقيع، ثم علّم الطلب كمدفوع — لكن فقط عندما تكون data.status هي paid أو overpaid. انظر الويب هوك.

The same request, four ways

curl -X POST https://paysell.me/api/merchant/v1/invoices \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "asset": "USDT_TON",
    "amount": "5",
    "order_id": "order-1042",
    "idempotency_key": "order-1042"
  }'

أعِد توجيه المشتري إلى payment_url في الاستجابة. انتهى الأمر — والباقي سيصل عبر ويب هوك.

What to do next

المصادقة#

مفتاح API الخاص بك، وكيفية استخدامه.

يحمل كل طلب مفتاحك في ترويسة Authorization:

http
Authorization: Bearer sk_live_IH4SNdnYsv-yabTuPCVln4vOvvGoEyxA

كل مفتاح يُصدَر هنا يبدأ بـsk_live_. أما بادئة sk_test_ فلا توجد إلا في نسخة منشورة موجَّهة إلى شبكة الاختبار، ونحن لا نقدّم مثل هذه النسخة — انظر الاختبار. نحفظ تجزئة (hash) أحادية الاتجاه، لا المفتاح نفسه، فلا أحد، ولا حتى نحن، يمكنه عرضه لك مجددًا. فقدته؟ أصدر واحدًا جديدًا وأبطل القديم.

يُشتَق المتجر من المفتاح، ولهذا لا يحمل أي طلب معرّف متجر. لا يمكن لمفتاح أن يعمل إلا على متجره الخاص.

يحمل المسار رقم إصدار: /api/merchant/v1/…. داخل الإصدار الواحد نضيف حقولًا فقط — لا يُعاد تسمية شيء ولا يتغيّر معنى شيء في صمت. أي تغيير قد يكسر شيفرتكم يحصل على بادئة جديدة، /v2، ويظل /v1 يعمل مدةً معلنة.

ينشئ هذا المفتاح فواتير باسمك. احتفظ به على جانب الخادم. أي شيء في جافاسكريبت المتصفح عام، مهما بدا مخفيًا جيدًا.

Endpoints at a glance#

Four calls, three of them authenticated.

This is the whole merchant API. Balances, payouts and history are not in it — they live in your account area, where a person is looking at them.

EndpointMethodAuthWhat it does
/invoicesPOSTAPI keyOpen an invoice and get a payment link. Details.
/invoices/{invoice_id}GETAPI keyRead one invoice's current state. Details.
/invoices/{invoice_id}/cancelPOSTAPI keyClose an invoice that is still open and free its address. Details.
/public/invoices/{invoice_id}GETnoneWhat the hosted checkout page reads. Only needed if you build your own. Details.

Every path is relative to https://paysell.me/api/merchant/v1. There is no list endpoint and no refund endpoint — see Refunds.

إنشاء فاتورة#

POST /api/merchant/v1/invoices

POST/api/merchant/v1/invoices

محتوى الطلب

الحقلالنوعإلزاميالوصف
assetstringنعمإما TON أو USDT_TON.
amountstringنعمالوحدات العادية للعملة، كسلسلة نصية: "5" تعني 5 USDT. بعدد خانات عشرية لا يتجاوز ما تملكه العملة. انظر المبالغ.
order_idstringلامرجعك الخاص، حتى 200 حرف. يعود في كل ويب هوك — وهكذا تُطابق الدفعة بالطلب.
descriptionstringلاحتى 1000 حرف. يُعرض للمشتري في صفحة الدفع.
ttl_minutesnumberلامدة بقاء الفاتورة قابلة للدفع، بالدقائق. 1–1440؛ وإن أغفلته طُبِّق الافتراضي — وهو اليوم ساعتان.
idempotency_keystringلاحتى 200 حرف. أرسل القيمة نفسها عند إعادة المحاولة وستحصل على الفاتورة نفسها بدلاً من فاتورة ثانية. وهو حقل في جسم الطلب، لا ترويسة Idempotency-Key — فهذه الترويسة لا تُقرأ هنا.

الاستجابة · 201

json
{
  "invoice_id": "12c22c1a-a496-4c1e-abe3-72661ef8706e",
  "payment_url": "https://paysell.me/pay/12c22c1a-a496-4c1e-abe3-72661ef8706e",
  "address": "UQAvDJp7QDwqRcuNQBiK2GhBt71Xh1_UMYPCzMkQAoBPmZKl",
  "asset": "USDT_TON",
  "amount": "5",
  "amount_minor": "5000000",
  "status": "pending",
  "paid": "0",
  "paid_minor": "0",
  "order_id": "order-1042",
  "description": "Pro subscription",
  "expires_at": "2026-09-06T17:20:55Z",
  "created_at": "2026-09-06T15:20:55Z"
}

كيفية استخدامها في طلبك

الحقلما ينبغي فعله به
invoice_idاحفظه مع طلبك. هو ما يُعرّف الدفعة في كل مكان آخر.
payment_urlوجّه المشتري إلى هنا. لا حاجة لبناء أي شيء آخر.
addressفقط إذا كنت تبني صفحة الدفع الخاصة بك. اعرضه تمامًا كما وصل — انظر التحذير أدناه.
amountالمبلغ بالوحدات العادية، تمامًا كما أرسلته. اعرض هذا.
amount_minorالمبلغ نفسه عددًا صحيحًا بالوحدة الصغرى. احسب بهذا.
expires_atاعرض عدًا تنازليًا. بعد انقضائه تتوقف مراقبة العنوان لهذه الفاتورة.
statusهنا دائمًا pending. التغييرات الحقيقية تصل عبر الويب هوك.
إذا بنيت صفحتك الخاصة، اطبع العنوان تمامًا كما أُعيد. إنه بصيغة غير قابلة للارتداد (UQ… على الشبكة الرئيسية، 0Q… على شبكة الاختبار). تحويله أو تجميله أو استبداله بترميز آخر لنفس العنوان سيجعل العملات المُرسَلة إلى محفظة لم تُنشر بعد ترتدّ إلى المُرسِل.

قراءة فاتورة#

GET /api/merchant/v1/invoices/{invoice_id}

GET/api/merchant/v1/invoices/{invoice_id}

الشكل نفسه أعلاه، مع عكس status وpaid وpaid_minor للحالة الراهنة: paid هو ما وصل بالوحدات العادية، وpaid_minor هو المبلغ نفسه عددًا صحيحًا بالوحدة الصغرى. مفيد كخيار احتياطي عند فقدان ويب هوك، أو في صفحة شكر.

استعلم عنها كحد أقصى كل بضع ثوانٍ، واعتبر الويب هوك القناة الأساسية. الفواتير التي تخص متجرًا آخر تُجيب بـ404 — لا 403، بحيث لا يمكن سبر معرّف للتحقق من وجوده.

إلغاء فاتورة#

POST /api/merchant/v1/invoices/{invoice_id}/cancel

POST/api/merchant/v1/invoices/{invoice_id}/cancel

يُغلق فاتورة ما تزال مفتوحة — pending أو underpaid — ويُحرّر عنوانها. استخدمه عندما يتخلى العميل عن الدفع — العناوين مورد محدود، وإعادتها تُبقي المجمّع بحالة جيدة.

الفاتورة التي لم تعد مفتوحة تُجيب بـ409. وإلغاء فاتورة underpaid لا يعيد العملات إلى أحد: المال المُقيَّد يبقى في رصيدك، وكل ما يُغلق هو قبول الدفعة المكمّلة.

الويب هوك#

ما الذي يصل، وكيفية التحقق منه.

حدّد عنوان الويب هوك عند إنشاء المفتاح. نرسل إليه طلب POST عندما يُقيَّد الدفع — وكذلك عندما يُرفض إيداع احتُجز لفحص إضافي. كل عملية تسليم موقَّعة، ونواصل إعادة المحاولة نحو يوم ونصف حتى تُجيب بـ2xx. سلّم البضاعة عند status: paid أو overpaid، لا لمجرد وصول الاستدعاء.

الأحداث

الحدثمتىما الذي يحمله الجسم
payment.creditedتأكّد التحويل على السلسلة، واقتُطعت عمولتنا، والباقي في رصيدك.الحقول المذكورة أدناه.
payment.rejectedإيداع احتُجز لفحص إضافي (انظر مرجع الحالات) جرى رفضه. المال لن يصل إلى رصيدك.invoice_id وorder_id وasset وamount وtx_hash وreason. لا تسلّم البضاعة؛ وإذا كانت الفاتورة paid بالفعل بتحويل أسبق، فهذا الحدث يخصّ الإيداع الزائد لا تلك الدفعة.

ما الذي يصل

json
{
  "event_id": "99f74f58-efbb-4af1-b0a3-76b0073f9e6b",
  "type": "payment.credited",
  "data": {
    "invoice_id": "12c22c1a-a496-4c1e-abe3-72661ef8706e",
    "order_id": "order-1042",
    "asset": "USDT_TON",
    "amount": "5000000",
    "credited": "4905000",
    "fee": "95000",
    "status": "paid",
    "paid_minor": "5000000",
    "tx_hash": "97a1f0…"
  }
}
json
{
  "event_id": "0a1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d",
  "type": "payment.rejected",
  "data": {
    "invoice_id": "12c22c1a-a496-4c1e-abe3-72661ef8706e",
    "order_id": "order-1042",
    "asset": "USDT_TON",
    "amount": "5000000",
    "tx_hash": "97a1f0…",
    "reason": "could not be matched to any order"
  }
}

تخطيط الحقول

الحقلالمعنى
event_idفريد لكل حدث؛ ويأتي أيضًا في ترويسة X-Paysell-Event-Id. احفظه وتجاهل التكرارات — انظر أدناه.
data.order_idمرجعك. ابحث عن طلبك به.
data.amountما أرسله المشتري في هذا التحويل، بالوحدة الصغرى — بخلاف الواجهة البرمجية التي تستقبل وحدات عادية.
data.feeما اقتطعناه، بالوحدة الصغرى.
data.creditedما وصل إلى رصيدك: amount − fee، بالوحدة الصغرى.
data.paid_minorإجمالي ما استُلم على هذه الفاتورة حتى الآن، بالوحدة الصغرى. وهو الحقل المهم عند underpaid: الحالة تقول إن ما وصل أقل، وهذا الحقل يقول كم أقل.
data.assetالعملة التي وصلت فعلًا. ليست بالضرورة العملة التي طلبتها الفاتورة.
data.asset_mismatchيظهر، وقيمته true، فقط عندما تكون العملة الواصلة غير عملة الفاتورة. تُقيَّد الأموال لك، لكن الفاتورة تبقى غير مدفوعة ولن يصبح status أبدًا paid.
data.invoice_assetيأتي مع asset_mismatch: العملة التي تطلبها الفاتورة فعلًا.
data.statusحالة الفاتورة الآن: pending أو underpaid أو paid أو overpaid أو expired. قارنها بما توقعته.
data.tx_hashالمعاملة على السلسلة، لسجلاتك وللدعم.

الترويسات في كل عملية تسليم

http
X-Paysell-Event:      payment.credited
X-Paysell-Event-Id:   99f74f58-efbb-4af1-b0a3-76b0073f9e6b
X-Paysell-Timestamp:  1789000000
X-Paysell-Signature:  sha256=6f1c0e6a…
الترويسةالمعنى
X-Paysell-Eventنوع الحدث: payment.credited أو payment.rejected.
X-Paysell-Event-Idفريد لكل حدث. هذه هي القيمة التي تُبنى عليها إزالة التكرار.
X-Paysell-Timestampلحظة توقيعنا، بثواني يونكس. وهي جزء من النص الموقَّع.
X-Paysell-Signaturesha256= يتبعها HMAC بصيغة hex. انظر أدناه.

التحقق من التوقيع

يُوقَّع كل طلب بسر الويب هوك الذي يُعرض مرة واحدة عند إنشاء المفتاح. التوقيع هو HMAC-SHA256(secret, "{timestamp}.{raw_body}") — الطابع الزمني من X-Paysell-Timestamp، ثم نقطة حرفية، ثم بايتات الجسم. تحقق منه قبل التصرف: بدون ذلك يستطيع أي شخص يعرف رابطك أن يُسلّمك طلبًا مدفوعًا.

Python:

python
import hmac, hashlib, time

def is_ours(body: bytes, signature: str, timestamp: str, secret: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:      # ±5 minutes
        return False
    signed = timestamp.encode() + b"." + body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    # compare_digest, not ==: a plain comparison leaks the answer through timing
    return hmac.compare_digest("sha256=" + expected, signature)

Node.js:

javascript
const crypto = require("node:crypto")

function isOurs(body, signature, timestamp, secret) {
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false  // ±5 min
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(timestamp + ".").update(body)   // body is the raw Buffer, not a parsed object
    .digest("hex")
  const a = Buffer.from(expected), b = Buffer.from(signature)
  // timingSafeEqual throws when the lengths differ, so check that first
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

وقّع بايتات الجسم الخام، تمامًا كما وصلت. إذا حللت JSON ثم أعدت تسلسله تغيّرت البايتات — ترتيب المفاتيح، المسافات — ولن يتطابق التوقيع. قارن في زمن ثابت (hmac.compare_digest، crypto.timingSafeEqual): المقارنة العادية == تعود أسرع عند اختلاف البايت الأول، وهذا الفارق وحده يكفي لتخمين التوقيع بايتًا بايتًا.

نافذة الطابع الزمني

ارفض كل ما يبتعد طابعه الزمني أكثر من خمس دقائق عن ساعتك أنت، في أي من الاتجاهين. الطابع الزمني موجود داخل النص الموقَّع تحديدًا كي لا يمكن تعديله دون كسر التوقيع؛ والنافذة هي ما يحوّل ذلك إلى حماية. بدونها يبقى الطلب الذي التُقط مرة واحدة صالحًا إلى الأبد ويمكن إعادة تشغيله في أي وقت — فالتوقيع وحده لا تنتهي صلاحيته أبدًا. أبقِ ساعة خادمك مضبوطة على NTP، وإلا بدأ هذا الفحص يرفض عمليات تسليم سليمة.

التكرارات

قد يصل الحدث نفسه أكثر من مرة. هذا ليس خللًا: نُعيد المحاولة حتى تُجيب بـ2xx، وعملية تسليم نجحت لكن استجابتها لم تصلنا تُرسَل مجددًا. سجّل X-Paysell-Event-Id (ويأتي أيضًا باسم event_id في الجسم) واجعل الوصول الثاني بلا أي أثر.

إعادة المحاولات

تنطلق المحاولة الأولى فور تقييد الدفع. وإذا أخفقت — مهلة منتهية، أو رفض اتصال، أو خطأ TLS، أو إعادة توجيه، أو أي حالة خارج 2xx — نُعيد المحاولة وفق جدول ثابت:

دقيقة واحدة → 5 دقائق → 15 دقيقة → ساعة واحدة → 6 ساعات → 24 ساعة

سبع محاولات في المجموع، موزّعة على نحو 31 ساعة. المحاولات الأولى متقاربة لأن السبب المعتاد مستقبِل كان يُعاد تشغيله وقد عاد بالفعل؛ والأخيرة متباعدة لأن الطَّرق المتواصل على خادم متوقف منذ يوم لا ينفع أحدًا.

بعد المحاولة الأخيرة تُوسَم عملية التسليم بـdropped ونتوقف من تلقاء أنفسنا. ولا يضيع شيء: يعرض سطر الدفعة في لوحة حسابك الحالة وعدد المحاولات وصنف الخطأ، مع زر إرسال مرة أخرى يبدأ دورة جديدة من المحاولات السبع كلها. وملاذك الآخر هو GET /api/merchant/v1/invoices/{invoice_id} — فالفاتورة تعرف حالتها دائمًا.

كيف ينبغي أن يبدو عنوان الويب هوك

يُفحص العنوان عند حفظه، ثم يُفحص مجددًا قبل كل عملية تسليم. العنوان الذي لا يجتاز الفحص يُجاب عليه بـ422 مع code: "webhook_url_rejected" وقت الحفظ، ويُوسَم التسليم بـfailed — دون أي إعادة محاولة — إن بدأ يخفق لاحقًا. القواعد:

  • `https://` فقط، والمنفذ 443. يحمل الويب هوك تفاصيل الدفع؛ وعبر http العادي يقرؤها أي أحد على الطريق.
  • اسم نطاق، لا عنوان IP. أنت بحاجة إلى شهادة على أي حال، والشهادات لا تُصدَر لعناوين IP مجرّدة.
  • لا `localhost`، ولا اسم .local أو .internal أو .corp أو .lan أو .test — خوادمنا لا تصل إلى شبكتك، والاسم الذي يُحَل داخل شبكتنا هو بالضبط ما يجب ألا نستدعيه.
  • لا بيانات اعتماد في العنوان (https://user:pass@…). ضع رمزك الخاص في المسار أو في معامل استعلام إن احتجت إلى واحد.
  • كل عنوان يُحَل إليه الاسم يجب أن يكون عامًا — A وAAAA معًا. تُرفض النطاقات الخاصة وloopback وlink-local وCGNAT، ويتكرر الفحص قبل كل تسليم، فتوجيه السجل لاحقًا إلى 127.0.0.1 لن ينفع هو الآخر.
  • إعادة التوجيه إخفاق، لا قفزة. نحن لا نتبعها: العنوان الذي أعطيتنا إياه هو ما فُحص، أما الذي في ترويسة Location فلم يُفحص.
يجري التحقق مرتين عن قصد — مرة عند حفظ العنوان، ليأتي الرد على الخطأ المطبعي فورًا بدل عدم تسليم صامت، ومرة قبل كل إرسال، لأن مالك النطاق يستطيع في أي لحظة توجيهه إلى عنوان داخلي. وإذا انتقل مستقبِلك، فحدّث المفتاح أولًا: العنوان المرفوض لا يُسلّم شيئًا ولا يدخل الطابور.

أجب بسرعة

يكفي أي 2xx، خلال عشر ثوانٍ — تلك هي مهلتنا كاملةً، بما فيها إنشاء الاتصال. أجب أولًا ثم قم بالعمل البطيء بعد ذلك؛ فالمستقبِل الذي ينتظر قاعدة بياناته قبل الرد سيُسجَّل عاجلًا أو آجلًا كمهلة منتهية وتُعاد المحاولة إليه، فتعالج الحدث نفسه مرتين. وأي شيء آخر — 4xx أو 5xx أو إعادة توجيه أو تعليق — يُحتسب محاولة فاشلة ويعود إلى الجدول أعلاه.

عن التسليم، بصراحة

المضمون هو آلية التسليم: سبع محاولات على مدى نحو 31 ساعة، وإعادة إرسال يدوية من لوحة حسابك، ونقطة نهاية للفاتورة تعرف الحالة الحقيقية دائمًا. ابنِ تدفقك بحيث لا يكلّفك ويب هوك لم يصل أبدًا شيئًا — اقرأ الفاتورة في صفحة الشكر، أو طابِق الفواتير المفتوحة مرة كل ساعة. الويب هوك هو المسار السريع، لا المسار الوحيد.

A complete receiver#

Signature, deduplication and a fast answer, end to end.

The snippets above verify one signature. This is the whole endpoint: raw body, signature check, deduplication by event_id, a fast 2xx, and the one condition that is allowed to mark an order paid.

Node.js with Express. express.raw is the part people get wrong: express.json() hands you a parsed object, and bytes you re-serialise from it are not the bytes we signed.

javascript
const express = require("express")
const crypto = require("node:crypto")

const app = express()
const SECRET = process.env.PAYSELL_WEBHOOK_SECRET

function isOurs(body, signature, timestamp) {
  const sentAt = Number(timestamp)
  if (!Number.isFinite(sentAt)) return false
  if (Math.abs(Date.now() / 1000 - sentAt) > 300) return false   // ±5 minutes

  const expected = "sha256=" + crypto
    .createHmac("sha256", SECRET)
    .update(timestamp + ".").update(body)      // raw Buffer, not a parsed object
    .digest("hex")

  const a = Buffer.from(expected), b = Buffer.from(signature ?? "")
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

app.post(
  "/paysell/webhook",
  express.raw({ type: "application/json" }),   // NOT express.json()
  async (req, res) => {
    const signature = req.get("X-Paysell-Signature")
    const timestamp = req.get("X-Paysell-Timestamp")
    if (!isOurs(req.body, signature, timestamp)) return res.sendStatus(401)

    const event = JSON.parse(req.body.toString("utf8"))

    // Answer first: 10 seconds is the whole timeout, connection included.
    res.sendStatus(200)

    // Deduplicate. In real code this is a unique column, not a Set.
    if (await alreadyHandled(event.event_id)) return
    await remember(event.event_id)

    if (event.type !== "payment.credited") return
    const { order_id, status, credited, asset, tx_hash } = event.data

    // The only condition that may release the goods.
    if (status !== "paid" && status !== "overpaid") return
    await markOrderPaid(order_id, { credited, asset, tx_hash })
  }
)

Python with Flask. request.get_data() is the raw body; request.form and request.json are not.

python
import hashlib
import hmac
import json
import os
import time

from flask import Flask, request

app = Flask(__name__)
SECRET = os.environ["PAYSELL_WEBHOOK_SECRET"]


def is_ours(body: bytes, signature: str, timestamp: str) -> bool:
    try:
        sent_at = int(timestamp)
    except (TypeError, ValueError):
        return False
    if abs(time.time() - sent_at) > 300:              # ±5 minutes
        return False

    signed = timestamp.encode() + b"." + body
    expected = hmac.new(SECRET.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest("sha256=" + expected, signature)


@app.post("/paysell/webhook")
def paysell_webhook():
    body = request.get_data()                          # raw bytes, unparsed
    if not is_ours(body, request.headers.get("X-Paysell-Signature", ""),
                   request.headers.get("X-Paysell-Timestamp", "")):
        return "", 401

    event = json.loads(body)

    # Deduplicate. In real code this is a unique column, not a set.
    if already_handled(event["event_id"]):
        return "", 200                                 # a repeat is still a success
    remember(event["event_id"])

    if event["type"] == "payment.credited":
        data = event["data"]
        # The only condition that may release the goods.
        if data["status"] in ("paid", "overpaid"):
            mark_order_paid(data["order_id"], data)

    return "", 200                                     # 2xx within 10 seconds

What the code is doing, and why

  • Verify before anything acts on the body. An unsigned request that reaches your business logic is a paid order for whoever found your URL.
  • Answer 2xx first, work afterwards. Ten seconds is the whole timeout, connection included. A handler that waits for its own database gets recorded as a timeout and retried, and you process the same event twice.
  • Deduplicate on `event_id` in storage that survives a restart. The in-memory set in the examples keeps them short; a real one is a unique column in your database.
  • Mark the order paid only on `status: paid` or `overpaid`. underpaid means part of the money arrived and the invoice is still open, and a deposit in the wrong coin never makes an invoice paid either.
  • Answer 2xx to a duplicate too. A repeat that gets a 4xx looks like a failure to us and comes back again on the schedule.

payment.rejected arrives at the same endpoint. It means a deposit held for an additional check was declined and the money will not be credited: release nothing, and if the invoice was already paid by an earlier transfer, this event is about the extra deposit, not about that payment.

مرجع الحالات#

جميع حالات الفواتير والمدفوعات، موضّحة.

الفاتورة

الحالةالمعنىما ينبغي فعله
pendingبانتظار الدفع.أبقِ الطلب مفتوحًا.
paidمدفوعة بالكامل.سلّم البضاعة.
overpaidوصل أكثر من المطلوب. يُقيَّد الفائض لك بالكامل.سلّم البضاعة؛ ورُدّ الفرق إن رغبت.
underpaidوصل أقل من المطلوب. تبقى الفاتورة مفتوحة وتحتفظ بعنوانها: يستطيع المشتري إتمام الدفع إلى المكان نفسه، ويقول paid_minor كم وصل حتى الآن. وتظل قابلة للدفع بقية عمرها زائد مهلة سماح مدتها 24 ساعة بعد expires_at.انتظر الدفعة المكمّلة، أو اتفق مع العميل. لا تسلّم البضاعة — الفاتورة غير مدفوعة.
expiredأُغلقت النافذة، بما في ذلك مهلة السماح. وقد تحمل مالًا رغم ذلك: كل ما وصل بقي في رصيدك، ويقول paid_minor كم.قدّم فاتورة جديدة. لا تقبل الدفع على العنوان القديم: فبمجرد انتهاء صلاحية الفاتورة يعود العنوان إلى المجمّع، والتحويل المتأخر جدًا حالة تُعالَج عبر الدعم لا قيدًا تلقائيًا. راجع paid_minor قبل أن تخبر العميل بأن شيئًا لم يصل.
cancelledألغيتها أنت. يعود العنوان إلى المجمّع.لا شيء.

الدفعة

مرئية في لوحة حسابك؛ مفيدة عند دعم عميل أثناء الدفع.

الحالةالمعنى
detectedشوهدت على السلسلة، بانتظار التأكيدات.
confirmedأكدتها الشبكة. الإضافة إلى الرصيد تالية.
creditedفي رصيدك. هذه هي اللحظة التي يُطلَق فيها الويب هوك.
reviewمحتجزة لفحص إضافي — كوصول عملات إلى عنوان بلا فاتورة مفتوحة، على سبيل المثال.
rejectedلم تُضَف إلى الرصيد. السبب مُسجَّل.

عندما تذهب الدفعة إلى `review`

بعض الإيداعات تُحتجَز لفحص إضافي بدل تقييدها فورًا: مبلغ كبير على غير العادة، أو عملات تصل إلى عنوان بلا فاتورة مفتوحة، أو اختلاف بين مصدري بيانات البلوكتشين اللذين نستعلمهما حول ما جرى. لا يضيع شيء — المال ينتظر القرار، ويُطلَق الويب هوك فور صدوره، وقد يكون ذلك بعد دقائق أو ساعات. فتعامل مع غياب الاستدعاء في دفعة معروضة بحالة review على أنه أمر طبيعي لا خلل. وإن كان ذلك مهمًا لطلب ما، فاسأل الدعم واذكر tx_hash.

المبالغ#

إلى الخارج وحدات عادية، وفي العودة الوحدات الصغرى.

أرسل المبالغ بالوحدات العادية للعملة، كسلسلة نصية"1.5" تعني واحدًا ونصفًا. لا رقم JSON ولا الوحدة الصغرى.

الأصلالخانات العشريةأنت ترسلamount_minor في الاستجابة
TON9"1.5""1500000000"
USDT_TON6"1.5""1500000"

سلسلة نصية لا رقمًا، لأن أرقام JSON هي أعداد IEEE-754 مزدوجة الدقة، والمبلغ الكبير بالنانوتون لم يعد يُمثَّل فيها بدقة. عدد خانات عشرية أكبر مما تملكه العملة يعني 422، ولا يعني أبدًا تقريب أموالك في صمت. الويب هوك يسير في الاتجاه المعاكس: فحقول amount وfee وcredited هناك أعداد صحيحة بالوحدة الصغرى، لأن ذلك الجانب يقرأه كود لا إنسان.

javascript
// Send amounts in the coin's normal units, as a string:
const amount = "1.5"   // one and a half TON or USDT

// In responses, amount is that same human string; amount_minor is the
// integer in smallest units — use it for exact maths, as a string or BigInt:
BigInt(invoice.amount_minor)  // e.g. 1500000n

الحدود#

الحدود الدنيا والقصوى وحدود المعدل.

الحدالقيمةعند التجاوز
الحد الأدنى للفاتورة0.1 TON · 3 USDT422
الحد الأقصى للفاتورة7000 TON · 10000 USDT422
الفواتير في الساعة لكل متجر60429
الفواتير المفتوحة في آنٍ واحد20، ويزيد مع كل فاتورة مدفوعة حتى 200429
عمر الفاتورةمن دقيقة واحدة إلى 24 ساعة (الافتراضي ساعتان)422
طلبات الواجهة البرمجية لكل مفتاح120 في الدقيقة429 + Retry-After

الحد الأدنى ليس بيروقراطية. عمولتنا نسبة مئوية، لكن تحصيل دفعة يكلّف مبلغًا ثابتًا: سحب USDT من عنوان استلام يعني تزويده بالغاز أولًا، من جيبنا نحن. ودون بضعة دولارات لا تُغطي العمولة تكلفة المعالجة، وقبول مثل هذه الدفعة يعني أن نُقيّد لك مالًا نقله غير مجدٍ اقتصاديًا.

الحد الأقصى ليس موجّهًا ضد المتاجر الكبيرة — إنه فخ لخطأ في الوحدات. أرسل "5000000" وأنت تقصد "5"، ولولاه لصدرت فاتورة بخمسة ملايين دولار: يرى المشتري مبلغًا سخيفًا فينصرف. الطلب الحقيقي لا يبلغ هذا السقف أبدًا، والخاطئ يبلغه دائمًا. وكلا السقفين إعداد (invoice_max_ton، invoice_max_usdt) ويمكن رفعه لمتجرك — اطلب ذلك.

الحد الساعي وحد الفواتير المفتوحة يحميان معًا مجمّع العناوين. تشغل كل فاتورة مفتوحة عنوان استلام، وحلقة خارجة عن السيطرة على موقع واحد كانت ستستنزف المجمّع على الجميع. يستطيع المتجر الجديد إبقاء 20 فاتورة مفتوحة في آنٍ واحد؛ ويزيد المسموح بواحدة عن كل فاتورة حصّلها فعلًا، حتى سقف 200. وتُحتسَب underpaid مفتوحةً — فهي ما تزال تشغل عنوانها بانتظار الباقي. وإلغاء فاتورة متروكة يعيد عنوانها فورًا. أما إعادة المحاولات بنفس idempotency_key فلا تُحتسَب ضمن الحد الساعي.

حد الطلبات هو 120 في الدقيقة لكل مفتاح واجهة برمجية — أي استدعاءان في الثانية، وهو أعلى بكثير من أي تدفق طلبات حقيقي. ويحمل الرد 429 ترويسة Retry-After بالثواني: انتظر تلك المدة بدل إعادة المحاولة في حلقة متلاحقة، فذلك لا يفعل سوى دفع النافذة أبعد.

الأخطاء#

رموز الحالة التي سترها فعليًا.

تعود الأخطاء بصيغة JSON، وفي شكلين اثنين. كل ما نقرره نحن أو نواة المعالجة يضع زوج {code, message} تحت detail. أما جسم الطلب الذي لا يجتاز التحقق فيضع هناك قائمة بأخطاء الحقول بدلًا من ذلك. تحقق أيّ الشكلين وصلك قبل قراءة detail.code — واتخذ قرارك بناءً على `code`، لا على `message` أبدًا: فالصياغة قد تتغير في أي وقت، أما الرمز فلا.

json
{
  "detail": {
    "code": "invalid_input",
    "message": "invoice amount below the minimum: 0.010000 USDT_TON, minimum 3.000000 USDT_TON"
  }
}
json
{
  "detail": [
    {
      "type": "string_type",
      "loc": ["body", "amount"],
      "msg": "Input should be a valid string",
      "input": 5
    }
  ]
}
الحالةمتىما ينبغي فعله
401المفتاح مفقود أو خاطئ أو مُلغى.تحقق من الترويسة. أعِد إصدار المفتاح إذا أُلغي.
404لا توجد هذه الفاتورة، أو تخص متجرًا آخر.تحقق من المعرّف. الحالتان تُجابان بالطريقة نفسها عن قصد، كي لا يمكن استكشاف المعرّفات.
409الفاتورة في حالة تمنع هذا الإجراء.اقرأ حالتها الحالية أولًا.
422الطلب غير صحيح البنية، أو المبلغ خارج حدود الفاتورة.تذكر الرسالة القيمة المرسَلة والحد كليهما.
429فواتير كثيرة جدًا هذه الساعة، أو مفتوحة كثيرة في آنٍ واحد، أو طلبات كثيرة جدًا.انتظر انقضاء Retry-After ثم أعد المحاولة.
502لم نتمكن من الوصول إلى نواة المعالجة.أعد المحاولة بمفتاح التطابق (idempotency) نفسه.

الرموز

الشكل الذي نقرره نحن هو {"detail": {"code": …, "message": …}}. وهذه هي الرموز التي تعيدها واجهة التاجر البرمجية.

الرمزالحالةالمعنى
invalid_api_key401المفتاح مفقود أو تالف أو غير معروف أو مُلغى. والحالات الأربع تُجيب بالطريقة نفسها، فلا يمكن سبر مفتاح.
not_found404لا يوجد هذا الكائن، أو أنه يخص متجرًا آخر.
invalid_input422لم يجتز الطلب التحقق في النواة — مبلغ غير صالح، أو خانات عشرية زائدة، أو مبلغ خارج حدود الفاتورة.
conflict409الإجراء يناقض الحالة الراهنة، كإلغاء فاتورة لم تعد مفتوحة.
too_many_requests429حد معدل: الفواتير في الساعة، أو الفواتير المفتوحة، أو الطلبات في الدقيقة. وتقول Retry-After كم تنتظر.
cbc_unreachable502تعذّر علينا الوصول إلى نواة المعالجة. أعد المحاولة بنفس idempotency_key.
webhook_url_rejected422عند حفظ المفتاح فقط: لم يجتز عنوان الويب هوك الفحوص أعلاه. ويسمّي detail.reason القاعدة المخالَفة — scheme، port، ip_literal، local_hostname، private_address، dns_error، credentials، وهكذا.

لا تعني 502 أن الفاتورة لم تُنشأ — فقد يكون الطلب قد اكتمل مع ضياع الاستجابة في طريق العودة. أعد المحاولة بنفس idempotency_key وستحصل إما على الفاتورة الموجودة أو على فاتورة جديدة، لا فاتورتين أبدًا.

Checkout: what the buyer sees#

The hosted payment page, and when to build your own.

payment_url points at https://paysell.me/pay/{invoice_id}. One page, no account, no login, mobile first, and nothing for you to build.

On the page

  • Your shop's name, the amount and the coin, large, with the invoice's description underneath.
  • A countdown to expires_at — plus the 24-hour grace period when the invoice is underpaid.
  • The invoice's short id with a copy button, so a buyer can quote it to your support.
  • Wallet buttons: Tonkeeper and MyTonWallet open with the address and the amount already filled in. Other reveals a QR code and the address with a copy button.
  • A warning that only this invoice's coin, on the TON network, may be sent — anything else is lost.

How the page reacts

InvoiceWhat the buyer sees
pending“Waiting for payment”, with the wallet choices and the countdown. The page re-reads the invoice every five seconds.
underpaid“Received X of Y”, the exact remainder still owed, and the same address to send it to. The wallet link is prefilled with what is missing, not with the original total — otherwise the buyer would pay twice.
paid · overpaid“Payment received”, and a button back to your shop if the shop has a URL.
expired“Payment window closed”. If money did arrive, the amount is named with a note to contact you — silence here would send the buyer looking for their coins.
cancelled“Payment cancelled”, with a link back to your shop.

If you build your own

You gain your own branding and take on all of the above: the exact address string, the right coin, the countdown with its grace period, the underpayment case, and polling. GET /api/merchant/v1/public/invoices/{invoice_id} is the same unauthenticated read the hosted page uses — rate limited per IP, so poll it no more often than every few seconds. Print the address exactly as returned.

Typical integration mistakes#

The handful that account for most broken integrations.

None of these are exotic. Every one of them has cost somebody a day.

  • Sending the amount as a number

    {"amount": 5} is a 422. It has to be the string "5": JSON numbers are IEEE-754 doubles, and a large sum in nanotons stops being exactly representable in one.

  • Sending the smallest unit

    "5000000" for 5 USDT is a mistake in units, and the upper invoice limit exists to catch it. Smallest units are what comes back in webhooks, not what goes out in requests.

  • Treating the webhook's arrival as payment

    Read data.status. underpaid is not paid, and a deposit in a coin the invoice did not ask for never makes it paid either. Release the goods on paid or overpaid, on nothing else.

  • Not checking the timestamp

    A signature on its own never expires. Without the ±5 minute window on X-Paysell-Timestamp, a delivery captured once can be replayed at any time and will still verify.

  • Verifying the signature over re-serialised JSON

    Parse the body and serialise it again and the bytes change — key order, spacing — and the HMAC no longer matches. Sign the raw bytes exactly as received.

  • No deduplication

    The same event_id will arrive twice sooner or later: we retry until you answer 2xx, and a response lost on the way back looks like a failure from here. The second arrival must do nothing.

  • Using the Idempotency-Key header

    This API reads idempotency_key from the request body; the header is not read at all. A retry without the field opens a second invoice for the same order.

  • Assuming detail is always an object

    It is {code, message} for anything we or the core decide, and a list of field errors when the body itself fails validation. Check which one you got before reading detail.code.

الاسترداد#

كيف تسترد مبلغًا لعميل.

الاسترداد يتم عبر الدعم، لا باستدعاء واجهة برمجية. الاسترداد تحويل جديد إلى عنوان قدّمه شخص ما، ومعالِج مدفوعات يرسل المال عائدًا تلقائيًا عند استدعاء واجهة برمجية هو معالِج يمكن دفعه إلى إرسال المال إلى عنوان مهاجم. لذلك فهو يدوي عن قصد.

لاسترداد مبلغ لمشترٍ، افتح تذكرة دعم من لوحة حسابك تتضمن invoice_id أو tx_hash، والمبلغ، والعنوان الذي تُرسَل إليه الأموال. يتحقق المشغّل من الدفعة، ويسحب المال من رصيدك، ويجيب في التذكرة نفسها. توقّع أن يستغرق ذلك يوم عمل، لا دقيقة.

وهناك نتيجتان يجدر التصميم على أساسهما. الدفع الزائد يُقيَّد لك بالكامل — لا نحتفظ منه بشيء — لذا فإعادة الفرق إلى مشترٍ أرسل أكثر من اللازم قرارك أنت، وتسلك الطريق نفسه. والفاتورة الناقصة الدفع ليست حالة استرداد ما دامت مفتوحة: المال في رصيدك، والعنوان ما يزال مراقَبًا، ويستطيع المشتري ببساطة إتمام الدفع. ولا يظهر قرار يُتخذ إلا بعد مهلة السماح، حين تصير الفاتورة expired ومعها مال.

الاختبار#

كيف تختبر تكاملك قبل الإطلاق.

المفاتيح هنا فعلية: كل مفتاح يُصدَر هو مفتاح sk_live_ يعمل مقابل النواة الإنتاجية وشبكة TON الرئيسية. لا توجد بيئة اختبار منفصلة، وفي ذلك ميزة: أنت تجرّب المسار نفسه الذي ستسلكه طلباتك الحقيقية.

لذا اختبر كما تختبر أي شيء يمس مالًا حقيقيًا: بمبالغ صغيرة. أنشئ فاتورة بالحد الأدنى (0.1 TON أو 3 USDT)، وادفعها من محفظتك أنت، وراقب المسار كاملًا — صفحة الدفع، والويب هوك، والتحقق من التوقيع، وتحوّل طلبك إلى مدفوع. تُطبَّق العمولة، والعملات تتحرك فعلًا.

أما الأجزاء التي يمكنك تجربتها دون إنفاق شيء فهي: إنشاء الفاتورة وقراءتها، وإلغاؤها، و422 على مبلغ غير صحيح البنية، و401 على مفتاح خاطئ، والتحقق من التوقيع لديك — وقّع جسمًا نموذجيًا بسرّك ومرّره إلى معالِجك أنت. والشيء الوحيد الذي يتطلب دفعة حقيقية هو الخطوة الأخيرة فقط: ويب هوك payment.credited فعلي.

خطّط للتكامل بحيث لا يعتمد على بيئة اختبارية أو على محاكاة دفعة: المسار الفعلي يُتحقَّق منه أسرع — وبصورة أصدق.

تعامل مع أول طلب حقيقي لك على أنه الاختبار الفعلي: اختر مبلغًا صغيرًا، وأبقِ الفاتورة مفتوحة في لوحة التحكم، وراجع سطر الدفعة وحالة الويب هوك قبل أن توجّه إليه عملاء حقيقيين.

For AI agents and LLMs#

Machine-readable copies of this page, and a prompt to start from.

Everything on this page also exists in a form a model can read directly. Point your assistant at one of these instead of pasting screenshots of documentation into a chat.

The three files

FileWhat it isUse it for
/llms-full.txtThe whole documentation as one markdown file: endpoints, fields, statuses, limits, errors, webhooks with working verification code, the fee, the checkout page, the checklist.Pasting into a model's context, or letting an agent fetch it. Start here.
/llms.txtA short index in the llms.txt format: what Paysell is, the five rules that decide whether an integration works, and links to everything else.Letting an agent discover the rest on its own.
/openapi.jsonOpenAPI 3.1, generated from the running application's own models, both webhook events included.Generating a client, or loading into anything that speaks OpenAPI.

A prompt to start from

Copy this, replace the stack, and hand it to your assistant. It names the four things that go wrong most often, so the answer does not have to be corrected afterwards.

prompt
Read https://paysell.me/llms-full.txt and implement Paysell payments in my <stack>:
create invoices (POST /api/merchant/v1/invoices, Bearer sk_live_ key, amount as a
decimal string in normal units), redirect the buyer to payment_url, verify webhook
signatures (HMAC-SHA256 over "{timestamp}.{raw_body}", header X-Paysell-Signature,
reject anything whose X-Paysell-Timestamp is more than 300 seconds off), deduplicate
by event_id, answer 2xx within 10 seconds, and mark orders paid only on a
payment.credited event whose data.status is "paid" or "overpaid".

Feeding it to a specific tool

  • Agents with web access — Claude Code, Cursor, Windsurf and the like: give them the /llms-full.txt link. One fetch, no setup.
  • A chat window — ChatGPT, Claude, Gemini: paste the contents of /llms-full.txt into the conversation or attach it as a file. It is written to fit in one message.
  • OpenAPI tooling — client generators, Postman, an agent's tool schema: point it at https://paysell.me/openapi.json. Its servers entry already carries the production base URL, so generated calls go to the right place.

قائمة تحقق ما قبل الإطلاق#

عشرة أمور تُراجَع قبل الإطلاق.

  • المفتاح على جانب الخادم فقط، أبدًا في جافاسكريبت المتصفح.
  • يُتحقَّق من توقيع الويب هوك مقابل "{timestamp}.{raw_body}"، وفي زمن ثابت.
  • تُرفض عمليات التسليم الأقدم من خمس دقائق، وساعة الخادم مضبوطة على NTP.
  • لا يفعل X-Paysell-Event-Id المتكرر شيئًا في المرة الثانية.
  • يجيب الويب هوك بـ2xx خلال عشر ثوانٍ؛ ويحدث العمل البطيء بعد ذلك.
  • عنوان الويب هوك نطاق على https:// وعلى المنفذ 443، دون إعادة توجيه أمامه.
  • الويب هوك الضائع أمر يمكن تجاوزه: تُقرأ نقطة نهاية الفاتورة في صفحة الشكر أو ضمن مسح مطابقة دوري.
  • يُولَّد idempotency_key مرة واحدة لكل طلب ويُعاد استخدامه عند إعادة المحاولة.
  • تخرج المبالغ بالوحدات العادية كسلاسل نصية؛ وتُقرأ أرقام الويب هوك بالوحدة الصغرى.
  • يُعرض العنوان تمامًا كما أُعيد، دون تعديل.
  • تُعالَج overpaid وunderpaid، لا paid فقط؛ وقد تحمل expired قيمة في paid_minor.
  • تُسلَّم البضاعة عند status: paid أو overpaid، ولا تُسلَّم أبدًا لمجرد وصول الاستدعاء.
  • تُعالَج 429 بانتظار انقضاء Retry-After، لا بإعادة المحاولة فورًا.
  • تُقرَأ الأرصدة منّا، ولا تُتبَّع بشكل منفصل باعتبارها الحقيقة.

هل هناك شيء غير واضح؟

إذا لم تُجب هذه الصفحة عن سؤالك، فهذه ثغرة في التوثيق تستحق إخبارنا بها. راسلنا من لوحة حسابك وسنُصلح الصفحة، لا الإجابة فقط.