pay.mntad.com

pay.mntad.com — واجهة API الإصدار 1

مدفوعات USDT على شبكتي TRC20 (ترون) وBEP20 (BNB Smart Chain) للتجّار. تحصل على عناوين عملات رقمية، وتراقب SamaPay الشبكات، وتصلك رسالة webhook موقّعة عن كل إيداع مؤكَّد. الأموال التي تستلمها تصبح رصيدك في البوابة لدى SamaPay.

هذا الملف يُطابَق مع الشيفرة عبر scripts/verify-api-docs.ts: كل مسار وحالة ورمز خطأ ونوع حدث وترويسة وثابت هنا يُقارَن مع src/، فلا يمكن أن يختلفا.


1. المصادقة

Authorization: Bearer sk_live_<40 characters>

الصلاحيات (scopes)

كل مفتاح يحمل مجموعة صلاحيات، وكل مسار يحتاج واحدة منها:

الصلاحيةتسمح بـ
payment_intents.writePOST /v1/payment-intents
payment_intents.readGET /v1/payment-intents وGET /v1/payment-intents/:id
addresses.writePOST /v1/addresses
deposits.readGET /v1/deposits وGET /v1/deposits/:id
events.readGET /v1/events/:id
balance.readGET /v1/balance

الصلاحية الناقصة تعطي 403 insufficient_scope، ويذكر الحقل error.details.required اسمها.


2. الأخطاء

{ "error": { "code": "amount_out_of_range", "message": "…", "request_id": "req_…", "details": { } } }

اعتمد على code. أمّا message فنص إنكليزي للبشر وقد يتغيّر.

HTTPالرمزمتى
400invalid_jsonجسم الطلب ليس JSON.
400idempotency_key_requiredطلب POST بلا Idempotency-Key صالح (من 1 إلى 255 حرف ASCII قابل للطباعة).
400validation_failedالشكل خاطئ. الحقل details.fields يذكر الحقول.
401unauthenticatedالترويسة Authorization غائبة أو مشوّهة.
401invalid_keyمفتاح غير معروف، أو سرّ خاطئ، أو مفتاح اختبار في الإنتاج.
401key_revokedالمفتاح أُلغي. details.successor آخر 4 أحرف من المفتاح البديل إن وُجد.
403insufficient_scopedetails.required يذكر الصلاحية الناقصة.
404not_foundمعرّف غير معروف أو كائن يخصّ حسابًا آخر. الحالتان لهما الجواب نفسه.
409idempotency_payload_mismatchاستُعمل Idempotency-Key نفسه مع جسم مختلف.
409idempotency_in_progressالطلب الأول بهذا المفتاح ما زال يعمل. أعد المحاولة بعد ثانية تقريبًا.
409reference_conflictيوجد طلب دفع بهذا reference بمبلغ أو شبكة مختلفة.
422amount_out_of_rangeالمبلغ ≤ 0، أو أكثر من 6 منازل عشرية، أو خارج الحدّ الأدنى/الأعلى لحسابك.
422unsupported_chainالشبكة ليست TRC20/BEP20، أو غير مفعّلة لحسابك.
422reference_invalidطول المرجع أو أحرفه غير مقبولة (انظر كل مسار).
429rate_limitedطلبات كثيرة. انتظر عدد الثواني المذكور في الترويسة Retry-After.
503derivation_unavailableلا يمكن توليد عنوان الآن. لم يُنشأ شيء، فأعد المحاولة لاحقًا.
503chain_unavailableطبقة الشبكة غير متاحة. لم يُنشأ شيء.
500internalخطأ من طرفنا، مسجَّل تحت request_id.

3. منع التكرار (idempotency) — في كل POST

أرسل Idempotency-Key: <1–255 printable ASCII characters> مع كل طلب POST. المفتاح خاصّ بـ (مفتاحك، Idempotency-Key) ويُحفظ لمدة 24 ساعة.

الإعادة ترجع الجواب كما كان حينها. للحالة الحالية استعمل GET.


4. حدود المعدّل


5. الكائنات

PaymentIntent (طلب دفع)

{
  "id": "pi_3f9c…",
  "object": "payment_intent",
  "status": "requires_payment",
  "amount": "12.5",
  "amount_received": "0",
  "fee_amount": "0",
  "currency": "USDT",
  "chain": "TRC20",
  "address": "T…",
  "reference": "order-1042",
  "tx_hashes": [],
  "confirmations_required": 19,
  "expires_at": "2026-09-25T13:00:00.000Z",
  "created_at": "2026-09-25T12:00:00.000Z",
  "succeeded_at": null,
  "expired_at": null
}

الحالات:

statusالمعنى
requires_paymentبانتظار تحويل.
processingشوهد تحويل لكنه لم يصل إلى عمق التأكيد بعد.
succeededالإيداعات المؤكَّدة بلغت amount في الوقت. الدفع الزائد مسموح (amount_received > amount).
succeeded_lateاكتمل المبلغ بعد expires_at.
expiredانتهت المهلة ولم يصل شيء.
expired_partialانتهت المهلة وقد وصل أقل من amount.

Deposit (إيداع)

{
  "id": "dep_cm8x…",
  "object": "deposit",
  "status": "confirmed",
  "chain": "TRC20",
  "tx_hash": "a1b2…",
  "amount": "12.5",
  "confirmations": 20,
  "address": "T…",
  "reference": "order-1042",
  "payment_intent_id": "pi_3f9c…",
  "detected_at": "2026-09-25T12:03:10.000Z",
  "confirmed_at": "2026-09-25T12:04:05.000Z"
}

المبالغ والغبار والتأكيدات

الشبكةالتأكيدات
TRC2019
BEP2015

الحقل confirmations_required في طلب الدفع يذكر الرقم نفسه.


6. المسار 1: دفعة واحدة وعنوان واحد (طلبات الدفع)

للدفع عند الشراء: مبلغ ثابت ومهلة، على عنوان جديد لا يُعاد استعماله أبدًا.

POST /v1/payment-intents، الصلاحية payment_intents.write.

الحقلالقاعدة
amountنص عشري، > 0، ≤ 6 منازل عشرية، ضمن الحد الأدنى/الأعلى لحسابك (الافتراضي 1 … 10000).
chainTRC20 أو BEP20.
referenceمعرّفك أنت لهذه الدفعة: من 1 إلى 200 حرف من [A-Za-z0-9:_-].
expires_in_secاختياري، 300 … 604800. الافتراضي 3600.
curl -s https://pay.mntad.com/v1/payment-intents \
  -H "Authorization: Bearer $SAMAPAY_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042" \
  -d '{"amount":"12.5","chain":"TRC20","reference":"order-1042"}'

قراءة طلبات الدفع:

ما يصل عبر webhook:

اقرأ data.object.status لتمييز الحالتين. **أضف الرصيد عند deposit.confirmed**، واستعمل أحداث طلب الدفع للحالة وواجهة المستخدم فقط.


7. المسار 2: عنوان دائم لكل عميل (الشحن)

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

POST /v1/addresses، الصلاحية addresses.write.

الحقلالقاعدة
chainTRC20 أو BEP20 (مفعّلة لحسابك).
referenceclient:tenant:kind:id: أربعة مقاطع غير فارغة، كلٌّ منها 64 حرفًا على الأكثر، بلا : أو مسافات أو أحرف تحكّم داخل المقطع. مثال: acme:shop1:user:42.
curl -s https://pay.mntad.com/v1/addresses \
  -H "Authorization: Bearer $SAMAPAY_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: addr-acme-shop1-user-42-trc20" \
  -d '{"chain":"TRC20","reference":"acme:shop1:user:42"}'
{ "object": "address", "chain": "TRC20", "address": "T…", "reference": "acme:shop1:user:42" }

8. القراءة

المسارالصلاحيةيعيد
GET /v1/depositsdeposits.read{ "object": "list", "data": [Deposit], "has_more": bool }، الأقدم أولًا. الاستعلام: reference وpayment_intent_id وsince (ISO-8601) وlimit (1–100، الافتراضي 100).
GET /v1/deposits/:iddeposits.readإيداع واحد (dep_…).
GET /v1/events/:idevents.readحدث واحد، كما أُرسل بالضبط.
GET /v1/balancebalance.readرصيدك في البوابة (أدناه).
GET /v1/healthnoneحالة الخدمة.

GET /v1/balance:

{
  "object": "balance",
  "currency": "USDT",
  "chains": { "TRC20": { "available": "15.75", "pending": "0" }, "BEP20": { "available": "0", "pending": "0" } },
  "fee_bps": 0
}

9. Webhooks

ترسل SamaPay كل حدث بطلب POST إلى رابط الـ webhook الخاص بك (https، يُحدَّد عند إصدار مفتاحك).

الحدث

{
  "id": "evt_…",
  "object": "event",
  "api_version": "2026-09-24",
  "type": "deposit.confirmed",
  "created_at": "2026-09-25T12:04:05.000Z",
  "data": { "object": { "id": "dep_…", "object": "deposit" } }
}

الترويسات

التوقيع هو v1 = HMAC-SHA256(secret, "<t>.<raw body>") بصيغة hex. secret هو نصّ whsec_… كاملًا. أثناء تدوير السرّ قد تظهر عدة قيم v1، فاقبل الطلب إذا طابقت أيٌّ منها.

التحقّق (Node)

import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the EXACT bytes you received (not re-serialised JSON). secrets: your current whsec_… (and the previous one during a rotation).
export function verifySamaPay(rawBody, header, secrets, now = Math.floor(Date.now() / 1000)) {
  const parts = String(header ?? "").split(",").map((p) => p.trim());
  const ts = parts.filter((p) => p.startsWith("t="));
  if (ts.length !== 1 || !/^t=\d{1,12}$/.test(ts[0])) return false;
  const t = Number(ts[0].slice(2));
  if (Math.abs(now - t) > 300) return false;                       // replay window: 300 s
  const given = parts.filter((p) => /^v1=[0-9a-f]{64}$/.test(p)).map((p) => Buffer.from(p.slice(3), "hex"));
  let ok = false;
  for (const s of secrets) {
    const expected = createHmac("sha256", s).update(`${t}.${rawBody}`).digest();
    for (const g of given) if (g.length === expected.length && timingSafeEqual(g, expected)) ok = true;
  }
  return ok;
}

ما يجب أن يفعله مستقبِلك

  1. تحقّق من التوقيع على الجسم الخام. ارفض إذا كان |now − t| > 300 ثانية. قارن بزمن ثابت.
  2. لا تثق بالجسم. أعد جلب الكائن بمفتاحك أنت واعمل فقط بما جلبته:
    • deposit.confirmed: GET /v1/deposits/:id، حيث :id هو data.object.id. أضف الرصيد فقط إذا كانت status المجلوبة confirmed.
    • payment_intent.*: GET /v1/payment-intents/:id.
    • أو أعد جلب الحدث كاملًا عبر GET /v1/events/:id.
  3. أضف رصيد كل إيداع مرة واحدة فقط. اجعل مفتاح الإضافة هو الإيداع، <CHAIN>:<tx_hash>، مع توحيد صيغة الهاش: BEP20 بالشكل 0x + 64 حرف hex صغير، وTRC20 بـ 64 حرف hex صغير بلا 0x. المعرّف dep_… فريد بالقدر نفسه. ضع قيد تفرّد على ذلك المفتاح في قاعدة بياناتك. الإرسال مرة واحدة على الأقل، فقد يصل الحدث نفسه أكثر من مرة.
  4. أضف المبلغ amount المجلوب. إذا كان في دفترك منازل عشرية أقل، قرّب إلى الأسفل.
  5. أجب بـ 2xx فقط بعد أن يُحفظ ما كتبته نهائيًا.

الإرسال وإعادة المحاولة