مدار — للمطوّرين API v1
v1

Madar Merchant API

واجهة برمجية لربط نظام متجرك (موقعك، WooCommerce، Shopify، نظام نقاط البيع…) بمنصة مدار مباشرة: أنشئ طلبات التوصيل تلقائياً، واعرف المنطقة والرسوم قبل الإنشاء، وتابع الحالة، واستقبل إشعارات فورية عبر Webhooks عند كل تغيير — دون إدخال يدوي في البوابة.

Base URL
https://wasselni.naseir.site/api/v1
الصيغة
JSON (UTF-8) · أسماء الحقول camelCase
الأوقات
UTC دائماً بصيغة yyyy-MM-ddTHH:mm:ssZ
العملة
شيكل ILS — المبالغ أرقام عشرية برقمين

كل استجابة — نجاحاً أو فشلاً — تأتي بنفس الغلاف، وتحمل ترويسة X-Request-Id (نفس قيمة requestId) أرسلها للدعم عند أي مشكلة:

الـ API مخصّص للاتصال من خادم إلى خادم (server-to-server) عبر HTTPS فقط. يتوفر أيضاً توثيق Swagger التفاعلي وملف OpenAPI على /openapi/v1.json.

البدء السريع

1
سجّل متجرك من تسجيل متجر وانتظر تفعيل الحساب من الإدارة.
2
افتح صفحة «ربط API» من لوحة التاجر: /Merchant/ApiSettings.
3
ولّد مفتاح اختبار mk_test_… واحفظه فوراً في متغير بيئة على خادمك — يُعرض مرة واحدة فقط.
4
أنشئ طلباً تجريبياً عبر POST /orders — لا يصل لأي كابتن ولا يمسّ رصيدك.
5
حاكِ تغيّر الحالة عبر POST /orders/{idOrRef}/simulate واستقبل حدث order.status_changed على رابط الـ Webhook الذي سجّلته في «ربط API».

المصادقة

أرسل مفتاحك في الترويسة X-Api-Key مع كل طلب (ما عدا GET /health). لا تُقبل كوكيز تسجيل الدخول على /api، ولا يوجد تحويل لصفحة الدخول — الأخطاء دائماً JSON.

  • من خادمك فقط: لا تضع المفتاح أبداً في كود المتصفح أو تطبيق الجوال أو مستودع Git — احفظه في متغير بيئة (MADAR_API_KEY).
  • يُعرض مرة واحدة: نخزّن بصمة المفتاح فقط (SHA-256)، فلا يمكن استرجاعه لاحقاً؛ تظهر في البوابة بادئته فقط (مثل mk_live_7Hq2).
  • إعادة التوليد والإلغاء: من «ربط API» — إعادة التوليد تلغي المفتاح القديم فوراً وتُصدر جديداً؛ الإلغاء يوقف المفتاح نهائياً. الإدارة تستطيع إلغاء مفتاحك أيضاً.
  • HTTPS إلزامي: الطلب عبر HTTP يُرفض بـ 400 https_required بلا تحويل.
الحالةHTTPالكود
لا توجد ترويسة X-Api-Key401missing_api_key
مفتاح بشكل خاطئ أو غير موجود أو ملغى401invalid_api_key
حساب المتجر غير مفعّل أو موقوف403merchant_not_active
محاولات مصادقة فاشلة كثيرة من نفس الـ IP429too_many_auth_failures

الوضع التجريبي (Sandbox)

لكل متجر نوعان من المفاتيح، ومفتاح فعّال واحد من كل نوع في الوقت نفسه. نوع المفتاح وحده يحدد الوضع — نفس الـ Base URL ونفس الـ endpoints.

Live
mk_live_…
طلبات حقيقية: تصل للإدارة والكباتن، تدخل رحلات الجمع والتوصيل، وتؤثر على رصيدك. "livemode": true.
Test
mk_test_…
طلبات تجريبية معزولة تماماً للتطوير والاختبار. "livemode": false.

عزل الطلبات التجريبية

  • لا تصل أبداً إلى الإدارة أو الكباتن، ولا تُنشئ رحلات جمع أو توصيل.
  • لا تؤثر على الرصيد إطلاقاً: GET /me/balance بمفتاح اختبار يُرجع رصيد 0 وبلا حركات.
  • كل استجابة طلب وكل payload لـ Webhook تحمل الحقل "livemode": false — تحقّق منه في نظامك قبل أي إجراء فعلي (شحن، فوترة…).
  • نفس externalRef يمكن أن يوجد مرة واحدة في كل وضع — طلب تجريبي SHOP-10482 لا يتعارض مع طلب حقيقي بنفس الرقم.
  • مفتاح الاختبار يرى الطلبات التجريبية فقط، ومفتاح الإنتاج يرى الحقيقية فقط.

محاكي الحالات POST /orders/{idOrRef}/simulate

المعاملات

الاسمالنوعإلزاميالوصف
idOrRefstringنعممعرّف الطلب التجريبي أو رقمه في المسار.
statusstringنعمالحالة المطلوبة من القيم المبيّنة أدناه.

بما أن الطلب التجريبي لا يمر بكابتن، يمكنك تحريك حالته يدوياً لاختبار تكاملك وتلقّي Webhooks. المحاكي متاح أيضاً كأزرار في بوابة التاجر.

  • لمفاتيح الاختبار فقط — استدعاؤه بمفتاح mk_live_ يُرجع 403 test_mode_only.
  • الانتقال للأمام فقط ومن حالة غير نهائية — وإلا 409 invalid_transition (مثلاً محاكاة processing بعد out_for_delivery، أو أي محاكاة بعد delivered/returned/cancelled).
  • كل محاكاة ناجحة تُطلق حدث order.status_changed على الـ Webhook كأي تغيير حقيقي.
status المُرسَلما يراه التاجر merchantStatusالتسمية
processingprocessingقيد التجهيز
out_for_deliveryout_for_deliveryفي الطريق
delivereddeliveredتم التسليم
returnedreturnedمرتجع
cancelledcancelledملغي

الاستجابة

بيانات الطلب التجريبي بعد الانتقال إلى الحالة المطلوبة.

الأخطاء

  • 403 test_mode_only
  • 409 invalid_transition
  • 404 order_not_found

الطلبات

الطريقةالمسارالوصف
POST/ordersإنشاء طلب
POST/orders/bulkإنشاء حتى 100 طلب دفعة واحدة
GET/orders/{idOrRef}طلب واحد مع سجل الحالات
GET/ordersقائمة بفلاتر وتقسيم صفحات
POST/orders/{idOrRef}/cancelإلغاء طلب في حالة الانتظار
POST/orders/{idOrRef}/simulateمحاكاة الحالة — مفاتيح الاختبار فقط (التفاصيل)

إنشاء طلب POST /orders

يُرجع 201 مع ترويسة Location: /api/v1/orders/{id} لطلب جديد، أو 200 عند إعادة إرسال نفس externalRef (منع التكرار). حد حجم الجسم 64KB.

المعاملات

الحقلالنوعإلزاميالوصف
externalRefstringموصى بهرقم الطلب في نظامك — مفتاح منع التكرار
≤64، [A-Za-z0-9_\-#./]
recipientNamestringنعماسم المستلم
2–100 حرف
recipientPhonestringنعمجوال المستلم، مثال 0599123456
يُطبَّع (مسافات، +970/+972…) ثم ^05[69]\d{7}$
addressstringنعمالعنوان الوصفي
≤300
zonestringلاالمنطقة؛ إن غابت تُحسب من الموقع
اسم منطقة موجودة (مطابقة تامة)
latitudenumberلاخط العرض
−90..90، مع longitude معاً
longitudenumberلاخط الطول
−180..180
mapUrlstringلارابط واتساب / Google Maps
≤500؛ تُستخرج منه الإحداثيات إن غابت
paymentTypestringنعمنوع التحصيل: الدفع عند الاستلام / مدفوع مسبقاً
cod | prepaid
codAmountnumberنعمCOD: قيمة الطلب التي تُحصَّل؛ Prepaid: قيمة إشارية لا تدخل الحسابات
0.01–99999
deliveryFeeTargetstringلامن يدفع رسوم التوصيل
merchant (افتراضي) | customer
codCollectionTargetstringلامن يستلم مبلغ COD — لـ COD فقط
wasselni_direct (افتراضي) | captain | merchant_pre_collected
notesstringلاملاحظات للكابتن
≤500

الاستجابة 201 Created:

totalCollect هو ما يجمعه الكابتن من الزبون: cod + رسوم على التاجر = codAmount؛ cod + رسوم على الزبون = codAmount + deliveryFee؛ prepaid + رسوم على الزبون = deliveryFee؛ prepaid + رسوم على التاجر = 0. تحذيرات غير قاتلة تظهر في meta.warnings: zone_mismatch، cod_collection_target_ignored، phone_normalized.

الاستجابة

طلب جديد مع معرّف ورقم تتبّع ورسوم التوصيل. عند التكرار تعاد بيانات الطلب الأصلي.

الأخطاء

  • 422 validation_failed
  • 422 zone_required
  • 409 external_ref_conflict

خيارات الدفع وأثرها المالي

cod + wasselni_direct الافتراضي
الزبون يحوّل قيمة الطلب مباشرة لحساب مدار. الطلب لا يُسلَّم حتى تؤكد الإدارة وصول التحويل (يظهر awaitingDirectTransfer: true حتى التأكيد). عند التسليم: رصيدك += codAmount، ثم −= deliveryFee إن كان deliveryFeeTarget=merchant.
cod + captain
الكابتن يقبض نقداً أو بتحويل لحسابه. عند التسليم: رصيدك += codAmount − deliveryFee (إن كانت عليك).
cod + merchant_pre_collected
قبضت المبلغ مسبقاً خارج المنصة. لا يُضاف codAmount لرصيدك؛ يُخصم deliveryFee فقط إن كانت عليك.
prepaid
codAmount قيمة إشارية فقط (الكابتن يُخبر الزبون بها) ولا تدخل أي حساب؛ عند التسليم يُخصم deliveryFee إن كانت عليك. codCollectionTarget يُتجاهل (تحذير cod_collection_target_ignored).
المرتجع (كل الأنواع)
يُخصم deliveryFee دائماً (بغض النظر عن deliveryFeeTarget) ويُخصم returnFee إن كان أكبر من صفر.

تحديد المنطقة

المنطقة تحدد رسوم التوصيل والمرتجع. ثلاث طرق، بالأولوية:

  1. zone — اسم المنطقة كما يظهر في GET /zones (مطابقة تامة)؛ اسم غير موجود → 422 zone_not_found. إن أرسلت معه إحداثيات لمنطقة أخرى يُعتمد zone مع تحذير zone_mismatch.
  2. latitude + longitude — تُكتشف المنطقة من حدودها المرسومة.
  3. mapUrl — رابط خريطة (واتساب / Google Maps) تُستخرج منه الإحداثيات؛ إن تعذّر → 422 invalid_map_url.

لا منطقة ولا موقع → 422 zone_required. موقع خارج كل المناطق المرسومة → 422 zone_not_detected (أرسل zone صراحة). المناطق التي hasBoundary=false لا تُكتشف من الإحداثيات.

منع التكرار بـ externalRef

  • أرسل رقم طلبك في externalRef. إعادة إرسال نفس القيمة (بعد انقطاع شبكة أو timeout) تُرجع الطلب الموجود بـ 200 وmeta.replayed=true وترويسة Idempotent-Replayed: true — دون إنشاء طلب ثانٍ.
  • المحتوى لا يُقارَن: الإعادة تُرجع الطلب الأصلي كما هو حتى لو اختلفت الحقول.
  • إن كان externalRef مستخدماً لطلب محذوف → 409 external_ref_conflict.
  • الفرادة لكل متجر ولكل وضع (live / test) على حدة.

جلب طلب GET /orders/{idOrRef}

المعاملات

الاسمالنوعإلزاميالوصف
idOrRefstringنعممعرّف UUID أو رقم الطلب في المسار.

idOrRef = المعرّف (uuid) أو refNumber (WP-…). يتضمن timeline من سجل الحالات تصاعدياً. طلب لمتجر آخر → 404 order_not_found. للبحث برقم طلبك استخدم GET /orders?externalRef=SHOP-10482.

الاستجابة

بيانات الطلب وسجل تغيّر الحالات في timeline.

الأخطاء

  • 404 order_not_found

قائمة الطلبات GET /orders

Queryالنوعالوصف
statusstringمبسّطة، عدة قيم بفاصلة: pending,processing,out_for_delivery,delivered,returned,cancelled
from, toISO date / datetime UTCعلى createdAt؛ to بتاريخ فقط يشمل اليوم كاملاً
externalRefstringمطابقة تامة
sourcestringapi | portal | app
pageint ≥ 1افتراضي 1
pageSizeint 1..100افتراضي 20

ترتيب ثابت createdAt تنازلياً، بدون timeline. meta: page، pageSize، total، totalPages. قيمة غير صالحة (حالة غير معروفة، from > to، pageSize خارج النطاق) → 400 validation_failed.

الاستجابة

قائمة طلبات مع معلومات الصفحات في meta.

الأخطاء

  • 400 validation_failed

إلغاء طلب POST /orders/{idOrRef}/cancel

المعاملات

الاسمالنوعإلزاميالوصف
idOrRefstringنعممعرّف الطلب أو رقمه في المسار.
reasonstringلاسبب الإلغاء، حتى 200 حرف.
  • مسموح للطلب في حالة pending فقط (قبل بدء رحلة الجمع).
  • جسم اختياري: { "reason": "الزبون ألغى" } (≤200 حرف).
  • ملغى مسبقاً → 200 مع meta.alreadyCancelled=true (آمن للإعادة).
  • أي حالة أخرى → 409 order_not_cancellable مع details.status؛ غير موجود → 404 order_not_found.

الاستجابة

بيانات الطلب بعد الإلغاء، مع meta.alreadyCancelled لتوضيح الإعادة.

الأخطاء

  • 404 order_not_found
  • 409 order_not_cancellable

إنشاء جماعي POST /orders/bulk

المعاملات

الاسمالنوعإلزاميالوصف
ordersarrayنعممن 1 إلى 100 عنصر، لكل عنصر معاملات إنشاء طلب.

الجسم { "orders": [ ... ] } من 1 إلى 100 طلب (وإلا 400 bulk_empty / 400 bulk_limit_exceeded)، وحد الحجم 1MB. كل عنصر يمر بنفس تحقق POST /orders ويُحفظ مستقلاً — نجاح جزئي ممكن: الرد 200 وsuccess=true ما دامت البنية صالحة حتى لو فشلت كل العناصر؛ المرجع هو summary.failed وresults[].error. مع externalRef تصبح إعادة إرسال الدفعة كاملة آمنة.

الاستجابة

ملخّص النتائج ونتيجة مستقلة لكل عنصر؛ قد تنجح بعض العناصر فقط.

الأخطاء

  • 400 bulk_empty
  • 400 bulk_limit_exceeded
  • 413 payload_too_large

المناطق والرسوم

GET /zones

name هو القيمة التي تُرسل في حقل zone. أضف ?includeBoundary=true لإرفاق حدود GeoJSON Polygon ([lng, lat]) للمناطق المرسومة.

المعاملات

الاسمالنوعإلزاميالوصف
includeBoundarybooleanلاإرفاق حدود المناطق بصيغة GeoJSON عند true؛ الافتراضي false.

الاستجابة

قائمة المناطق مع رسوم التوصيل والمرتجع ووجود الحدود.

الأخطاء

  • 401 invalid_api_key

GET /zones/detect?lat=…&lng=…

نفس خوارزمية الإنشاء — لعرض الرسوم في صفحة الدفع عندك قبل إنشاء الطلب. إحداثيات غير صالحة → 400 validation_failed؛ خارج كل المناطق → 422 zone_not_detected.

المعاملات

الاسمالنوعإلزاميالوصف
latnumberنعمخط العرض بين −90 و90.
lngnumberنعمخط الطول بين −180 و180.

الاستجابة

المنطقة المطابقة ورسوم التوصيل والمرتجع.

الأخطاء

  • 400 validation_failed
  • 422 zone_not_detected

الحساب

GET /me

ملف المتجر + المفتاح المستخدم (البادئة فقط) + حالة الـ Webhook. مناسب كأول طلب لاختبار المفتاح. بمفتاح اختبار: livemode=false وbalance = 0.

المعاملات

لا توجد معاملات؛ أرسل ترويسة المصادقة فقط.

الاستجابة

ملف المتجر والمفتاح المستخدم وحالة ربط Webhook والوضع الفعّال.

الأخطاء

  • 401 invalid_api_key
  • 403 merchant_not_active

GET /me/balance?limit=20

الرصيد وآخر الحركات (limit من 1 إلى 100، افتراضي 20). type: cod_credit، delivery_fee، return_fee، withdrawal، adjustment؛ amount بإشارته (الخصومات سالبة). بمفتاح اختبار: رصيد 0 وبلا حركات.

المعاملات

الاسمالنوعإلزاميالوصف
limitintegerلاعدد الحركات، من 1 إلى 100؛ الافتراضي 20.

الاستجابة

الرصيد والعملة وآخر الحركات؛ الوضع التجريبي يعيد صفراً بلا حركات.

الأخطاء

  • 400 validation_failed
  • 401 invalid_api_key

Webhooks

سجّل رابط HTTPS واحداً من «ربط API» واحفظ السر whsec_… (يُعرض مرة واحدة). نرسل POST بجسم JSON موقّع عند كل حدث، ويمكنك إرسال حدث تجريبي من زر «اختبار» أو عبر POST /webhooks/test (بلا جسم، حد 5 طلبات/دقيقة؛ 422 webhook_not_configured / webhook_disabled).

الحدثمتىdata
order.createdإنشاء طلب (من الـ API أو البوابة)order مع change: null
order.status_changedكل تغيير حالة (حقيقي أو عبر المحاكي)order + change
webhook.testزر «اختبار» أو POST /webhooks/test{ "message": "اختبار من مدار" }

الترويسات والـ Payload

الترويسةالمعنى
X-Madar-Signaturet=<unix>,v1=<hex> — HMAC-SHA256 للنص "{t}.{rawBody}" بسر الـ Webhook
X-Madar-Eventنوع الحدث
X-Madar-Event-Idمعرّف الحدث (= id في الجسم) — ثابت عبر إعادات المحاولة: استخدمه لتجاهل التكرار
X-Madar-Deliveryمعرّف محاولة التسليم هذه

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

احسب HMAC على الجسم الخام كما وصل (قبل أي JSON parse)، وقارن بمقارنة ثابتة الزمن، وارفض أي t أبعد من 5 دقائق عن وقتك (حماية من إعادة الإرسال). الترويسة قد تحمل أكثر من v1 عند تدوير السر — يكفي تطابق واحد.

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

  • ردّ بـ 2xx خلال 10 ثوانٍ — أي كود آخر أو انقطاع أو تجاوز المهلة يُعدّ فشلاً. استلم وأكّد سريعاً ثم عالج في الخلفية.
  • جدول إعادة المحاولة بعد الفشل: 1 دقيقة 5 دقائق 30 دقيقة 2 ساعة 12 ساعة ← متوقف (6 محاولات كحد أقصى). يمكنك «إعادة الإرسال» يدوياً من سجل التسليم في البوابة.
  • إيقاف تلقائي: بعد 25 فشلاً متتالياً يُعطَّل الـ Webhook — أصلح الرابط ثم فعّله من «ربط API».
  • قد يصل الحدث أكثر من مرة (إعادة محاولة أو إعادة إرسال يدوية) وقد لا يصل بالترتيب — تجاهل التكرار بـ X-Madar-Event-Id، واعتمد occurredAt أو GET /orders/{idOrRef} للحالة النهائية.
  • الرابط يجب أن يكون HTTPS عاماً — العناوين الخاصة/الداخلية مرفوضة ولا تُتبع التحويلات (redirects).

الحالات

كل طلب يحمل status (الحالة التشغيلية الداخلية) وmerchantStatus (الحالة المبسّطة التي يراها التاجر) وmerchantStatusLabel (التسمية العربية). ننصح ببناء منطقك على merchantStatus — قيمه ثابتة ولن تتغير. المسار: pending ← collecting ← at_warehouse ← out_for_delivery ← delivered / returned.

الحالة الداخلية statusmerchantStatusما يراه المتجر
pendingpendingانتظار
collecting / at_warehouse / assignedprocessingقيد التجهيز
out_for_deliveryout_for_deliveryفي الطريق
delivereddeliveredتم التسليم
returnedreturnedمرتجع
cancelledcancelledملغي

الحالات delivered وreturned وcancelled نهائية لا تتغير. القيمة الاحتياطية settled تُعرض للتاجر كـ delivered.

الأخطاء

عند الفشل: success=false وdata=null وerror يحمل code (ثابت بصيغة snake_case — ابنِ منطقك عليه) وmessage (عربي للعرض، قد يتغير) وdetails اختيارية. أخطاء التحقق تُفصّل في details.fields[]. لا تظهر أي تفاصيل داخلية — أرسل requestId للدعم.

HTTPcodeالرسالة
400 https_required يجب استخدام HTTPS
400 invalid_json جسم الطلب ليس JSON صالحاً
400 validation_failed بيانات غير صالحة
400 bulk_empty قائمة الطلبات فارغة
400 bulk_limit_exceeded الحد الأقصى 100 طلب في الدفعة
401 missing_api_key مفتاح API مفقود — أرسل ترويسة X-Api-Key
401 invalid_api_key مفتاح API غير صالح أو ملغى
403 merchant_not_active حساب المتجر غير مفعّل أو موقوف
403 test_mode_only هذه العملية متاحة في الوضع التجريبي فقط
404 order_not_found الطلب غير موجود
404 not_found المسار غير موجود
405 method_not_allowed طريقة HTTP غير مسموحة لهذا المسار
409 order_not_cancellable لا يمكن إلغاء الطلب بعد بدء رحلة الجمع
409 external_ref_conflict رقم الطلب الخارجي مستخدم لطلب محذوف
409 invalid_transition لا يمكن نقل الطلب إلى هذه الحالة
413 payload_too_large حجم الطلب أكبر من المسموح
422 zone_required حدّد المنطقة أو أرسل الموقع
422 zone_not_found المنطقة غير موجودة — راجع GET /zones
422 zone_not_detected الموقع خارج كل مناطق التوصيل المرسومة — أرسل حقل zone صراحة
422 invalid_map_url تعذّر استخراج الإحداثيات من رابط الخريطة
422 invalid_phone رقم الجوال غير صالح — مثال: 0599123456
422 webhook_not_configured لم يُضبط رابط Webhook بعد
422 webhook_disabled الـ Webhook معطّل — فعّله من البوابة
429 too_many_auth_failures محاولات مصادقة فاشلة كثيرة — حاول لاحقاً
429 rate_limited تجاوزت الحد المسموح من الطلبات — حاول بعد قليل
500 internal_error خطأ داخلي — أرسل requestId للدعم
503 service_unavailable الخدمة غير متاحة مؤقتاً

الحدود

الحدالقيمةعند التجاوز
لكل مفتاح API120 طلب / دقيقة429 rate_limited
لكل عنوان IP (كل /api)300 طلب / دقيقة429 rate_limited
محاولات مصادقة فاشلة لكل IP20 خلال 5 دقائق429 too_many_auth_failures
POST /webhooks/test5 طلبات / دقيقة429 rate_limited
حجم الجسم64KB413 payload_too_large
حجم جسم POST /orders/bulk1MB، حتى 100 طلب413 payload_too_large / 400 bulk_limit_exceeded
pageSize في القوائم1..100400 validation_failed

ردود 429 تحمل ترويسة Retry-After بالثواني — انتظرها قبل الإعادة، ويُفضّل استخدام تراجع أُسّي (exponential backoff) مع الأخطاء 5xx.

سجل التغييرات

v1
الإصدار الأول 2026-10-24
إنشاء الطلبات (فردي وجماعي حتى 100) مع منع التكرار بـ externalRef واكتشاف المنطقة من الموقع، جلب/قائمة/إلغاء الطلبات، المناطق والرسوم، الحساب والرصيد، Webhooks موقّعة مع إعادة المحاولة، والوضع التجريبي بمفاتيح mk_test_ ومحاكي الحالات.
© مدار — منصة التوصيل المجمّع · Swagger · OpenAPI · تتبع طلب