تخط وانتقل إلى المحتوى الرئيسي

الطلبات

إنشاء وقراءة الطلبات والانتقال بين حالاتها وإلغاؤها. دعم كامل للمتغيرات، سلال متعددة الأسطر، تسعير معتمد على الخادم.

بقلم: Support

الطلبات هي قلب المنصة. كل طلب:

  • ينتمي إلى متجر واحد فقط (مقيّد بمفتاحك — لا يمكنك أبدًا الوصول إلى بيانات تاجر آخر بالخطأ).

  • له دورة حياة من 7 حالات.

  • مرتبط بعميل (يُزال التكرار باستخدام الهاتف داخل المتجر).

  • يحتوي على عنصر واحد أو أكثر، كل منها يمكن أن يحمل متغيرات. إضافات المنتج (add-ons، أي الحقول المخصصة المدفوعة) غير مدعومة في واجهة v1 — انظر الإضافات.

  • له حالة دفع (pending, paid, refunded) مستقلّة عن حالة التسليم.

دورة الحياة {#lifecycle}

المسار الرئيسي   pending → confirmed → processing → shipped → delivered
قفزات مسموحة     pending → processing        confirmed → shipped
إلغاء            pending | confirmed | processing → cancelled   (نهائية)
إرجاع            shipped | delivered → returned                 (نهائية)

وهذه خريطة الانتقالات التي تفرضها الواجهة البرمجية بالضبط، لكل حالة:

من

الحالات التالية المسموح بها

pending

confirmed، processing، cancelled

confirmed

processing، shipped، cancelled

processing

shipped، cancelled

shipped

delivered، returned

delivered

returned

cancelled

— (نهائية)

returned

— (نهائية)

الإلغاء مشروع فقط من pending وconfirmed وprocessing — الطلب الذي بلغ shipped أو delivered لم يعد يمكن إلغاؤه.

وتعديل الطلب بـ PATCH إلى الحالة التي هو عليها أصلًا هو عملية 200 بلا أثر.

الحالة

المعنى

المخزون

pending

أُنشئ من الواجهة أو الـ API. ينتظر تأكيد التاجر.

لم يُحجز، إلا إذا كان المتجر يخصم المخزون فور وصول الطلب

confirmed

أكّده التاجر (اتصل بالعميل، راجع السلة).

مُلتزَم (تم خصمه)

processing

يُجهَّز / يُحضَّر للتسليم لشركة التوصيل.

مُلتزَم

shipped

سُلِّم لشركة التوصيل.

مُلتزَم

delivered

استلمه العميل ووقّع.

مُلتزَم

cancelled

أُلغي الطلب — يعود المخزون إن كان مُلتزَمًا.

مُسترَد

returned

أرجع العميل المنتج — يعود المخزون.

مُسترَد

cancelled و returned حالتان نهائيتان — لا يمكن الخروج منهما.

POST /v1/orders — إنشاء طلب

أنشئ طلبًا جديدًا للمتجر المنادي. يستعمله الثيمات المخصصة والتطبيقات المحمولة والموزّعون وأي واجهة headless ترسل الطلبات من خادمها الخاص بدل استعمال checkout واجهة المتجر المدمجة.

المصادقة: مفتاح منصة بصلاحيتَي orders:write وorders:read. يتطلب Idempotency-Key. الرد يعيد قراءة الطلب، لذلك المفتاح الذي يملك orders:write وحدها يُحفَظ طلبه لكنه يتلقى 403 forbidden، وإعادة المحاولة بنفس Idempotency-Key تُعيد نفس 403.

يُنشأ الطلب بحالة pending. في الإعداد الافتراضي لا يُلتزَم المخزون عند الإنشاء: أول انتقال إلى حالة مُلتزِمة (confirmed أو processing أو shipped أو delivered) هو من يخصم المخزون، تمامًا كتدفّق اللوحة. الهدف من ذلك: ترك مساحة لفريق العمليات لتصفية الطلبات الوهمية والمكررة وغير المُجاب عنها قبل لمس المخزون. أما المتجر الذي اختار في الطلبات ← إعدادات العرض ← خصم المخزون الخيار فور وصول الطلب فيُخصم مخزونه لحظة إنشاء الطلب عبر الـ API.

الجسم

{
  "customer": {
    "name":      "Sarra Benali",
    "phone":     "0555000111",
    "email":     "[email protected]",
    "wilaya_id": 16,
    "commune":   "Bab Ezzouar",
    "address":   "12 Rue X, Apt 3"
  },
  "delivery": {
    "type":      "home",
    "desk_id":    null,
    "desk_name":  null
  },
  "items": [
    {
      "product_id": 26,
      "quantity":   2,
      "variants": [
        { "group_name": "Color", "option_name": "Red",  "color_code": "#ff0000", "price_adjustment": 0 },
        { "group_name": "Size",  "option_name": "L",    "color_code": null,      "price_adjustment": 200 }
      ]
    }
  ],
  "discount":       0,
  "payment_method": "cod",
  "notes":          "Please call before delivery"
}

مرجع الحقول

customer (كائن، إلزامي)

الحقل

النوع

إلزامي

ملاحظات

name

string (1–255)

✅

الاسم الكامل

phone

string

✅

^\+?[0-9 ]{6,20}$ — جزائري أو دولي

email

string | null

إن وُجد، يُحفظ في سجل العميل

wilaya_id

int

✅

كود الولاية الجزائرية: من 1 إلى 58، أو من 1 إلى 69 في متجر مضبوط على 69 ولاية (اقرأ wilaya_mode من GET /v1/shipping/rates)

commune

string (1–100)

✅

نص حر، مثل "Bab Ezzouar"

address

string

شارع + شقة؛ يمكن تركه فارغًا للاستلام بالمكتب

العملاء يُزال تكرارهم بالمتجر + رقم الهاتف. إن وُجد عميل بهذا الهاتف في متجرك، يُحدَّث (الاسم، الولاية، البلدية، العنوان، البريد) ويُعاد استخدامه. وإلا يُنشَأ سجل جديد.

delivery (كائن، اختياري)

الحقل

النوع

الافتراضي

ملاحظات

type

home | desk | pickup | digital

home

digital للمنتجات الرقمية فقط. لا تُحتسب أي تكلفة توصيل على pickup وdigital. إذا كان النوع المختار معطّلًا في المتجر لتلك الولاية والنوع الآخر مفعّلًا، يمكن أن ينتقل الطلب إلى النوع الذي يوفّره المتجر؛ ويُظهر delivery.type في الرد النوع النهائي

desk_id

int | null

null

إلزامي إن كان type = desk وأردت مكتبًا محددًا

desk_name

string | null

null

تسمية بشرية اختيارية

items (مصفوفة، إلزامية، 1–50 سطرًا)

الحقل

النوع

إلزامي

ملاحظات

product_id

int

✅

يجب أن ينتمي لمتجرك (الـ IDs من متاجر أخرى تُرفض بـ 400)

quantity

int، من 1 إلى 9999

القيمة 1 إن لم تُرسَل

variants

مصفوفة من كائنات المتغيرات

انظر أدناه

هام — تسعير معتمد على الخادم. أنت لا تُحدّد سعر السطر. يُستعمل دائمًا سعر المنتج الحالي في الكتالوج. إن أرسلت حقل price فهو يُتجاهل.

وprice_adjustment لكل متغيّر معتمد على الخادم أيضًا: لكل زوج (group_name, option_name) يطابق خيارًا حقيقيًا على المنتج، تستبدل DZBuild قيمة price_adjustment الخاصة بالكتالوج. قيمتك تبقى فقط للأزواج غير الموجودة في الكتالوج — وهو تساهُل مقصود للتكاملات القديمة — لذا فأي "خصم" سالب تخترعه يُهمَل بصمت لأي خيار حقيقي. عامل price_adjustment عند الإدخال على أنه معلوماتي فقط: أعِد إرسال القيمة كما وردت من GET /v1/products/{id} كي يطابق إجماليك المحسوب عند العميل إجمالي الخادم.

items[].variants (مصفوفة، اختيارية)

كل كائن متغيّر يصف خيارًا مختارًا لمجموعة متغيرات على المنتج:

الحقل

النوع

ملاحظات

group_name

string

مثل "Color" أو "Size" أو "Material"

option_name

string

مثل "Red" أو "L" أو "Cotton"

color_code

string | null

لون hex (لمتغيرات اللون فقط)

price_adjustment

number

يُضاف للسعر الأساسي. تُستبدل بقيمة الكتالوج كلما وُجد زوج المجموعة/الخيار على المنتج — انظر ملاحظة التسعير أعلاه

أرسل كائن متغيّر واحد لكل مجموعة مختارة على هذا السطر. فـ "تيشيرت أحمر مقاس L" يصبح إدخالين (واحد لـ Color/Red وآخر لـ Size/L). تعرضها DZBuild على صفحة الطلب في اللوحة كما لو اختارها العميل من الواجهة تمامًا.

للمنتجات التي تستخدم متغيرات لكل قطعة (مثل عرض "اشترِ 3 تيشيرتات، اختر لونًا لكل قطعة")، استخدم quantity = 1 لكل سطر وأنشئ سطرًا لكل قطعة — هذا أنظف ربط.

حقول المال على المستوى الأعلى

الحقل

النوع

الافتراضي

ملاحظات

shipping_cost

number

يُتجاهَل إن أُرسل. يحسب الخادم تكلفة التوصيل من سعر المتجر للولاية ونوع التسليم، ومن قواعد الشحن المجاني ورسوم الوزن، بنفس طريقة الطلب المُنشأ من لوحة التحكم. لا تُحتسب أي تكلفة توصيل على طلبات pickup وdigital. المبلغ المحتسب تجده في amounts.shipping_cost

discount

number ≥ 0

0

قيمة كود خصم، خصم يدوي… لا يتجاوز المجموع الفرعي مضافًا إليه الشحن

payment_fee

number

يُتجاهَل إن أُرسل؛ قيمته دائمًا 0

payment_method

cod | free_digital | digital_payment

تلقائي

الافتراضي cod للمادي، free_digital للرقمي

notes

string ≤ 1000

null

ملاحظات العميل، تظهر على صفحة الطلب في اللوحة

الإجمالي يُحسب على الخادم: subtotal + shipping_cost - discount (لا يقل عن 0)، حيث shipping_cost هو ما يحسبه الخادم نفسه. و subtotal نفسه = sum(items[].quantity × (price + Σ variants.price_adjustment)).

الحقل الوحيد الذي يُؤخذ من جسم طلبك هو discount. يجب أن يكون ≥ 0، ولا يتجاوز المجموع الفرعي مضافًا إليه الشحن، ولا يُقارَن بأكواد الخصم المعرَّفة في متجرك، لذلك يجب استدعاء POST /v1/orders من خادم موثوق فقط، ولا يجوز إطلاقًا استدعاؤه من كود متصفح أو تطبيق يمكن للزبون العبث به.

الطلب

curl -X POST 'https://api.dzbuild.app/v1/orders' \
  -H "Authorization: Bearer $DZ_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "customer": {
      "name":      "Sarra Benali",
      "phone":     "0555000111",
      "wilaya_id": 16,
      "commune":   "Bab Ezzouar",
      "address":   "12 Rue X"
    },
    "items": [
      { "product_id": 26, "quantity": 1,
        "variants": [
          { "group_name": "Duration", "option_name": "30 days", "price_adjustment": 0 }
        ]
      }
    ],
    "payment_method": "cod"
  }'

الاستجابة 200

إنشاء الطلب يُرجع HTTP 200 وليس 201 — لا تتفرّع على رمز الحالة؛ تحقّق من data.id / data.order_number بدلًا من ذلك. الجسم بنفس شكل GET /v1/orders/{id} — معبّأ بالكامل بالإجماليات المحسوبة، وكتلة العميل المعيارية، وسطور العناصر التي أنشأتها للتو (مع متغيراتها).

صيغة order_number هي ORD-{store_id}-{YYYYMMDD}-{8 أحرف hex كبيرة}. وطلبات صفحات الهبوط تستعمل LP-{8 أحرف hex كبيرة}. أما الطلبات المُنشأة قبل 2026-06-02 فتحمل لاحقة قديمة من 4 أحرف hex، لذا يجب أن يقبل المحلّل الطولين.

الأخطاء

الكود

السبب

bad_request "Body must be valid JSON"

JSON تالف، أو جسم عبارة عن قيمة JSON مفردة مثل نص أو رقم بدل كائن (رأس Content-Type لا يُفحَص)

bad_request "customer object is required"

كائن customer مفقود

bad_request "customer.name is required (1-255 chars)"

الاسم مفقود أو طويل جدًا

bad_request "customer.phone is required (digits, optional leading +)"

الهاتف لم يطابق التعبير النمطي

bad_request "customer.wilaya_id must be 1-58"

ولاية خارج نطاق المتجر؛ والمتجر المضبوط على 69 ولاية يرد بـ "customer.wilaya_id must be 1-69"

bad_request "customer.commune is required (1-100 chars)"

البلدية مفقودة أو طويلة جدًا

bad_request "items must be a non-empty array"

سلة فارغة

bad_request "items: max 50 lines per order"

أكثر من 50 سطرًا (قسّمها لطلبات متعددة)

bad_request "items[N].product_id is required"

product_id مفقود

bad_request "Product N does not belong to this store"

منتج من متجر آخر

bad_request "items[N].quantity must be 1-9999"

كمية غير صالحة

bad_request "delivery.type must be home, desk, pickup, or digital"

نوع تسليم غير صالح

bad_request "payment_method must be cod, free_digital, or digital_payment"

طريقة دفع غير صالحة

bad_request "discount must be >= 0"

خصم سالب

bad_request "Monthly order limit reached for this store plan"

سقف الخطة Free — انظر أدناه

سقف الطلبات الشهري. الخطة Free محدودة بـ 30 طلبًا في الشهر التقويمي؛ أما Pro و Unlimited و Enterprise فبلا سقف. واسم خطة غير معروف يعود أيضًا إلى سقف 30/شهر المجاني. العدّاد شهري تقويمي ويشمل كل مصادر الطلبات (واجهة المتجر + صفحة الهبوط + لوحة التحكم + الواجهة البرمجية).

Idempotency

كل POST يجب أن يحمل رأس Idempotency-Key. إن أعدت نفس الطلب (نفس Idempotency-Key ونفس مفتاح API ونفس بايتات الجسم) خلال 24 ساعة نُعيد لك نفس الاستجابة مع Idempotency-Replay: 1، فيُنشأ الطلب مرة واحدة فقط. ردود الخطأ تُعاد هي أيضًا، ما عدا 429 و5xx، ونفس Idempotency-Key مع جسم مختلف يرد بـ 422 idempotency_key_reuse: أرسل الطلب المصحَّح بمفتاح جديد. انظر Idempotency.

# آمن للإعادة بالمفتاح نفسه
KEY="$(uuidgen)"
for i in 1 2 3; do
  curl -X POST 'https://api.dzbuild.app/v1/orders' \
    -H "Authorization: Bearer $DZ_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $KEY" \
    -d @order.json
done
# يُنشأ طلب واحد فقط لا غير

Webhook الذي يُطلق

إنشاء طلب يُطلق order.created لكل اشتراكات /v1/webhooks على هذا الحدث. انظر أحداث Webhook.

⚠️ تنبيه — /v1/webhooks ترى نشاط الواجهة البرمجية فقط

اشتراكات /v1/webhooks تُطلق فقط للتغييرات التي تمت عبر واجهة v1. أما الطلبات الموضوعة على واجهة المتجر أو على صفحة هبوط، وتغييرات الحالة من لوحة التحكم، فلا تُطلق order.created / order.confirmed / order.shipped.

للحصول على أحداث تغطي كل نشاط الطلبات، استعمل إضافة Webhooks (/dashboard/addons ← "Webhooks — Connect n8n, Make & Zapier"، تتطلب خطة Unlimited) وهي تلتقط كل طلب مهما كان مصدره — أو واصل الاستطلاع عبر GET /v1/orders?since=….

ما الذي يتخطّاه مسار الطلب عبر الواجهة البرمجية

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

  • يُنتج أي حدث Pixel أو CAPI لـ Meta أو TikTok.

  • يزيد عدّادي total_orders / total_spent للعميل.

  • يفحص قائمة حظر العملاء — فطلب العميل المحظور عبر الواجهة البرمجية يمرّ. اقرأ is_banned من العملاء وارفضه من جانب عميلك إن احتجت ذلك.

  • يطبّق فحوص مكافحة الإساءة التلقائية في checkout واجهة المتجر، ولا قواعد إضافة الحد الأدنى/الأقصى للكمية.

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

ولاحظ أيضًا أن العميل الذي تُنشئه الواجهة البرمجية تُخزَّن فيه سلسلة customer.name كاملةً في first_name ويُترك last_name فارغًا؛ أما العميل الموجود مسبقًا والمطابَق برقم الهاتف فيُستبدَل فيه first_name فقط، ويُترك أي last_name موجود دون تغيير.


GET /v1/orders

اسرد الطلبات. ترقيم بالمؤشّر.

المصادقة: مفتاح بصلاحية orders:read (ضمن الصلاحيات الافتراضية). المفتاح الذي لا يملكها يتلقى 403 forbidden "Missing scope: orders:read".

معاملات الاستعلام

المعامل

النوع

ملاحظات

limit

int 1–200

الافتراضي 50

cursor

string

غير شفاف

status

إحدى الحالات السبع

تصفية

since

سلسلة ISO 8601

created_at >= since

customer_phone

string

تطابق تام

قيمة status غير الصالحة أو since غير القابلة للتحليل تُتجاهَل بصمت — فتحصل على القائمة غير المصفّاة لا على 400. ولا يوجد فلتر payment_status في v1؛ صفِّ من جانب العميل.

الطلب

curl 'https://api.dzbuild.app/v1/orders?status=pending&limit=20' \
  -H "Authorization: Bearer $DZ_KEY"

الاستجابة 200

{
  "data": {
    "items": [
      {
        "id":              6894,
        "order_number":    "ORD-13-20260317-AD3C91F7",
        "status":          "pending",
        "payment_status":  "pending",
        "payment_method":  "cod",
        "total":           1000,
        "customer_name":   "John Doe",
        "customer_phone":  "0555000000",
        "wilaya_id":       16,
        "commune":         "Bab Ezzouar",
        "delivery_type":   "home",
        "created_at":      "2026-03-17 15:18:13"
      }
    ],
    "next_cursor": null,
    "has_more": false
  }
}

عرض القائمة مضغوط عمدًا (بدون عناصر ولا متغيرات). اطلب GET /v1/orders/{id} للتفاصيل الكاملة.


GET /v1/orders/{id}

تفاصيل كاملة مع سطور العناصر والمتغيرات وكتلة العميل.

المصادقة: مفتاح بصلاحية orders:read. المفتاح الذي لا يملكها يتلقى 403 forbidden.

الاستجابة 200

{
  "data": {
    "id":             6894,
    "order_number":   "ORD-13-20260317-AD3C91F7",
    "store_seq":      644,
    "status":         "pending",
    "payment_status": "pending",
    "payment_method": "cod",
    "customer": {
      "id":         5578,
      "name":       "John Doe",
      "phone":      "0555000000",
      "email":      null,
      "wilaya_id":  16,
      "commune":    "Bab Ezzouar",
      "address":    "12 Rue X"
    },
    "delivery": { "type": "home", "desk_id": null, "desk_name": null },
    "shipment": {
      "sent_to_delivery":    true,
      "sent_to_delivery_at": "2026-09-06 16:04:30",
      "delivery_company":    "colivraison",
      "delivery_tracking":   "TRK-0000000000",
      "last_send_failure":   null
    },
    "amounts": {
      "subtotal":      1000,
      "shipping_cost": 0,
      "discount":      0,
      "payment_fee":   0,
      "total":         1000
    },
    "items": [
      {
        "id":         8421,
        "product_id": 26,
        "price":      1000,
        "quantity":   1,
        "variants": [
          { "order_item_id": 8421, "group_name": "Duration", "option_name": "30 days",
            "color_code": null, "price_adjustment": "0.00" }
        ]
      }
    ],
    "created_at": "2026-03-17 15:18:13",
    "updated_at": "2026-03-17 15:18:13"
  }
}

حقول الشحن

store_seq هو رقم الطلب الظاهر في لوحة التحكم (#644)؛ إنشاء الطلب عبر الـ API يرقّمه قبل الرد، وفي الحالة النادرة التي يفشل فيها الترقيم يبقى null إلى أن يُملأ بعد قليل. يصبح shipment.sent_to_delivery صحيحًا (true) عندما تقبل شركة التوصيل الطرد، وdelivery_company هي الشركة (معرّفها النصي) وdelivery_tracking رقم التتبع. shipment.last_send_failure هو آخر إرسال مرفوض لهذا الطلب (at وprovider وmessage كما أرسلتها الشركة) أو null إن لم يفشل أي إرسال. ويكون دائمًا null متى صار sent_to_delivery صحيحًا، فالفشل السابق لا يظهر بعد إرسال ناجح. تُرجع نداءات الإنشاء والتحديث والإلغاء الكائن نفسه.

المتغيرات في الاستجابة

مصفوفة variants على كل عنصر هي المرجع لما اختاره العميل. كل إدخال يحمل order_item_id وgroup_name وoption_name وcolor_code (لمتغيرات اللون) وprice_adjustment (الإضافة لكل قطعة، وتُرجَع كسلسلة string في JSON لا كعدد). لعروض القطع المتعددة قد ترى عدة إدخالات متغيرات على نفس العنصر بتجميعات مختلفة — انظر ملاحظات القطع المتعددة أدناه.

ما لا تُرجعه استجابة التفاصيل

  • notes حقل للكتابة فقط في v1 — يُخزَّن على الطلب ويظهر في لوحة التحكم، لكن GET /v1/orders/{id} لا يُرجعه أبدًا.

  • كما أن product_name وsku للعنصر وtotal السطر تُلتقط عند الإنشاء لكنها لا تُرجَع أيضًا. ارجع إلى GET /v1/products/{id} إن احتجت الأسماء.

الإضافات (add-ons) غير مكشوفة {#add-ons-are-not-exposed}

إضافات المنتج (الحقول المخصصة المدفوعة التي يضبطها التاجر على منتج) ليس لها أي سطح في v1: لا يمكنك إرسالها في POST /v1/orders، وGET /v1/orders/{id} لا يُرجع أي إدخالات إضافات. أما الطلب الموضوع على واجهة المتجر مع إضافات فيُقرأ عبر الواجهة البرمجية وقيمة الإضافات مدمجة أصلًا في items[].price (وبالتالي في amounts.subtotal) — لكن بدون أي سطر إضافة ولا عنوان ولا نسبة ملف.


PATCH /v1/orders/{id} — تغيير الحالة

المصادقة: مفتاح منصة بصلاحيتَي orders:write وorders:read. يتطلب Idempotency-Key. بدون orders:read تتغير الحالة فعلًا، لكن الرد يكون 403 forbidden.

الجسم يجب أن يكون {"status": "<one of the 7>"}. نتحقق من الانتقال مقابل الجدول في دورة الحياة؛ وإلا نُرجع 400 مع الحالات التالية المسموح بها. وتعديل الطلب إلى حالته الحالية هو عملية 200 بلا أثر.

الطلب

curl -X PATCH 'https://api.dzbuild.app/v1/orders/6894' \
  -H "Authorization: Bearer $DZ_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: confirm-6894-$(date +%s)" \
  -d '{"status": "confirmed"}'

تُرجع 200 وتفاصيل الطلب المحدّث. الانتقالات المتزامنة على نفس الطلب تُنفَّذ بالتسلسل: إن غيّر طلبٌ آخر الحالة قبلك، يفشل طلبك بنظافة بـ bad_request ("order status changed concurrently; retry") بدل أن يُطبَّق مرتين.

آثار المخزون الجانبية

حالات الالتزام بالمخزون هي confirmed وprocessing وshipped وdelivered.

  • غير مُلتزَم → مُلتزَم — يُخصم المخزون أول مرة يدخل فيها الطلب أيًا من تلك الحالات، بما في ذلك القفزة pending → processing. ويرتفع كذلك عدّاد مبيعات المنتج.

  • في متجر اختار في خصم المخزون الخيار فور وصول الطلب، خُصم المخزون عند إنشاء الطلب، فلا يخصم هذا الانتقال شيئًا إضافيًا.

  • مُلتزَم → cancelled / returned — يُسترَد المخزون.

  • الانتقالات الأخرى لا تمسّ المخزون.

المخزون لا يُتحقَّق منه: الخصم يتوقف عند الصفر، فلا يُرفض البيع الزائد أبدًا، وأي مشكلة في المخزون لا تُفشل تغيير الحالة ولا تتراجع عنه. ويتصرّف المخزون تمامًا كما يتصرّف مع الطلبات المُدارة من لوحة التحكم.

الأخطاء

الكود

السبب

bad_request "Field \"status\" is required"

الجسم لا يحوي status

bad_request "status must be one of: pending, confirmed, …"

سلسلة غير صالحة

bad_request "Transition X -> Y not allowed. From 'X' you can only go to: …"

غير مسموح بآلة الحالات (الواجهة تُصدر -> بأحرف ASCII عادية)

bad_request "order status changed concurrently; retry"

طلب آخر غيّر الحالة أثناء انتقالك. أعد المحاولة بمفتاح Idempotency-Key جديد: هذا الرد 400 محفوظ تحت المفتاح الذي أرسلته، وإعادة المحاولة به تُعيد نفس الخطأ طوال 24 ساعة.

not_found

معرّف الطلب غير معروف أو لمتجر آخر


POST /v1/orders/{id}/cancel

نقطة مريحة — تكافئ تمامًا PATCH /v1/orders/{id} مع {"status":"cancelled"}. نفس جدول الانتقالات، بلا أي صلاحية إضافية. الإلغاء مشروع فقط من pending وconfirmed وprocessing؛ ومن shipped أو delivered ستحصل على 400 bad_request (فتلك الحالتان لا تنتقلان إلا إلى delivered / returned).

المصادقة: مفتاح منصة بصلاحيتَي orders:write وorders:read. يتطلب Idempotency-Key. بدون orders:read يُلغى الطلب فعلًا، لكن الرد يكون 403 forbidden.

curl -X POST 'https://api.dzbuild.app/v1/orders/6894/cancel' \
  -H "Authorization: Bearer $DZ_KEY" \
  -H "Idempotency-Key: cancel-6894-$(date +%s)"


POST /v1/orders/{id}/send-to-delivery

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

المصادقة: مفتاح بصلاحية delivery:send. يتطلب Idempotency-Key. المفاتيح المُنشأة من الإعدادات ← واجهة API لا تحمل هذه الصلاحية وتتلقى 403 forbidden؛ أما التطبيق فيحصل عليها عندما يوافق التاجر عليها أثناء التثبيت.

الحقل

النوع

ملاحظات

provider

string، اختياري

المعرّف النصي لشركة توصيل مرتبطة بالمتجر. اتركه لاستعمال شركة التوصيل الافتراضية للمتجر. الشركة غير المرتبطة تُرفض.

confirm_token

string

الرمز الذي أعاده النداء الأول، يُرسَل بعد موافقة التاجر

  1. نادِ بدون confirm_token. الرد يكون 422 confirmation_required ومعه confirm_token (يُستعمل مرة واحدة، صالح 600 ثانية) وaction وwill_change: العميل والهاتف والوجهة ونوع التسليم والإجمالي وشركة التوصيل. اعرض هذا الملخص على التاجر.

  2. بعد موافقته، أعد النداء بنفس provider ومعه confirm_token وبمفتاح Idempotency-Key جديد (المفتاح الأول مرتبط بالجسم الذي لا يحوي الرمز). الرمز المنتهي أو المستعمل أو الذي لم يعد يطابق الطلب يرد بـ 422 confirmation_stale مع رمز جديد. الحقل confirm: true غير مقبول هنا.

عادةً يجري الإرسال في الخلفية ويرد بـ 202 مع queued: true. اقرأ GET /v1/orders/{id} إلى أن يصبح shipment.sent_to_delivery صحيحًا ومعه delivery_tracking، أو إلى أن يُظهر shipment.last_send_failure رفض الشركة. وإن لم يكن التشغيل في الخلفية متاحًا يجري الإرسال داخل النداء نفسه: 200 مع sent وprovider وtracking، أو 422 courier_refused مع رسالة الشركة. بعد أن تقبل الشركة الطرد، ينتقل الطلب من pending أو confirmed إلى processing، وهذا يخصم المخزون إن لم يكن قد خُصم. الطلبات من نوع التسليم pickup لا تُسلَّم أبدًا لشركة توصيل. والإرسال في الخلفية الذي يتوقف قبل الاتصال بالشركة، مثل إرسال طلب pickup أو شركة غير مرتبطة، يرد مع ذلك بـ 202 ولا يغيّر أيًا من حقلَي shipment.

الكود

السبب

forbidden (403)

المفتاح لا يملك delivery:send

not_found (404)

طلب غير معروف، أو طلب متجر آخر

already_sent (409)

الطلب عند شركة توصيل أصلًا؛ وerror.tracking يحمل رقم تتبعه

confirmation_required، confirmation_stale (422)

انظر الخطوتين أعلاه

courier_refused (422)

رفضت الشركة الطرد أثناء إرسال داخل النداء

rate_limited، too_many_concurrent (429)

لنداءات شركات التوصيل ميزانية خاصة بكل متجر فوق حدود المعدل

curl -X POST 'https://api.dzbuild.app/v1/orders/6894/send-to-delivery' \
  -H "Authorization: Bearer $DZ_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: send-6894-approved" \
  -d '{"confirm_token": "cft_REPLACE_WITH_TOKEN"}'


المتغيرات — مرجع كامل

تجعل المتغيرات منتجًا واحدًا يغطي خيارات متعددة (لون، حجم، خامة، سعة…). على الواجهة يضغط العملاء بطاقات المتغيرات لاختيار ما يريدون؛ على الواجهة البرمجية ترسل أنت الخيارات المختارة كجزء من الطلب.

نموذج المتغيرات

للمنتج 0 أو أكثر من مجموعات المتغيرات. للمجموعة 0 أو أكثر من الخيارات. كل خيار يمكن أن يحمل:

  • value (الاسم المعروض)

  • color_code (لون hex، فقط لمجموعات اللون)

  • price_adjustment (يُضاف للسعر الأساسي)

  • image_id (صورة منتج مرتبطة كـ swatch)

اقرأ GET /v1/products/{id} لرؤية كل المجموعات والخيارات لمنتج:

{
  "variants": [
    {
      "id":   11,
      "name": "Color",
      "type": "color",
      "options": [
        { "id": 14, "value": "Red",  "color_code": "#ff0000", "image_id": 28, "stock": 12 },
        { "id": 15, "value": "Blue", "color_code": "#0000ff", "image_id": 29, "stock": 5  }
      ]
    },
    {
      "id":   12,
      "name": "Size",
      "type": "text",
      "options": [
        { "id": 16, "value": "S", "stock": 10 },
        { "id": 17, "value": "M", "stock": 10 },
        { "id": 18, "value": "L", "stock": 5  }
      ]
    }
  ]
}

منتج بـ 2 ألوان × 3 مقاسات لديه 6 تركيبات.

كيفية إرسال المتغيرات في POST /v1/orders

حوّل اختيار العميل إلى إدخال متغيّر واحد لكل مجموعة مختارة. لـ "تيشيرت أحمر مقاس L":

"variants": [
  { "group_name": "Color", "option_name": "Red", "color_code": "#ff0000", "price_adjustment": 0 },
  { "group_name": "Size",  "option_name": "L",   "color_code": null,      "price_adjustment": 0 }
]

الأسماء التي ترسلها تُحفظ كما هي على الطلب — يجب أن تطابق ما أعاده GET /v1/products/{id}. price_adjustment يُضاف لسعر السطر (فيكون final_price = product.price + Σ price_adjustment)، لكن لأي زوج موجود في الكتالوج يستبدل الخادم قيمته الخاصة، فأرسل رقم الكتالوج.

المخزون لكل متغيّر

إن فعّل التاجر variant_stock_enabled على منتج، يحمل كل خيار متغيّر عداد مخزونه. اقرأه من options[].stock. الواجهة لا تمنعك من إنشاء طلب بـ quantity > stock؛ هذا قرار التاجر. ويُخصم المخزون عند أول انتقال إلى حالة مُلتزِمة (confirmed، processing، shipped، delivered)، أو عند الإنشاء في متجر يخصم المخزون فور وصول الطلب، ولا يُتحقَّق منه أبدًا: الخصم يتوقف عند الصفر، فيُسجَّل البيع الزائد بدل أن يُرفض.

المخزون لكل تركيبة

إن فُعّل combination_stock_enabled يُتتبع المخزون لكل تركيبة (Red+L = 5، Red+M = 8…). يُرجعها GET /v1/products/{id} في combinations[] (لكل واحدة id وsku وstock وis_active وخياراتها options) مع combination_count؛ وتتوقف القائمة عند 300 عنصر، ويُخبرك combinations_truncated إن قُطعت. انظر المنتجات. ويتحرك مخزون التركيبات في نفس اللحظة التي يتحرك فيها باقي مخزون الطلب.

المتغيرات المتدرجة (Cascading)

تتيح إضافة Cascading Variants للتاجر جعل خيارات المجموعة B تعتمد على اختيار المجموعة A (مثل "علامة → موديل" — اختيار "Apple" للعلامة يُظهر فقط "iPhone 15" / "iPhone 14" للموديل). وهذه الإضافة تتطلب خطة Pro.

لا يكشف GET /v1/products/{id} أي معلومات عن التدرّج — فخريطة الأب/الابن لا تُطبَّق إلا في واجهة المتجر وفي لوحة التحكم. لا يستطيع عميلك اكتشاف قواعد التدرّج عبر الواجهة البرمجية في v1: ثبّتها في الكود أو اقرأها من لوحة التحكم. وإرسال تركيبة غير صالحة يُنشئ الطلب رغم ذلك (لا نمنعه) — لكن التاجر سيرفضه عند التأكيد.

متغيرات الصورة-نص

بعض التجار يستخدمون نوع image_text للمتغيرات (صورة مصغرة بجانب اسم الخيار). على الواجهة البرمجية ما زلت ترسل group_name + option_name — الصورة شأن واجهة فقط ولا تكون جزءًا من حمولة الطلب.

عروض القطع المتعددة

إن أطلق التاجر عرض "اشترِ 3، اختر ألوانًا"، يختار العميل متغيرًا مختلفًا لكل قطعة. على الواجهة البرمجية:

"items": [
  { "product_id": 26, "quantity": 1,
    "variants": [{ "group_name": "Color", "option_name": "Red" }] },
  { "product_id": 26, "quantity": 1,
    "variants": [{ "group_name": "Color", "option_name": "Blue" }] },
  { "product_id": 26, "quantity": 1,
    "variants": [{ "group_name": "Color", "option_name": "Green" }] }
]

ثلاثة سطور منفصلة، كل واحد quantity = 1. هكذا تعرض صفحة الطلب لون كل قطعة بنظافة.


أنماط شائعة

"إرسال طلب من واجهة React مخصصة"

تبني واجهة React/Vue/Next.js تتحدث مع الواجهة البرمجية بدلًا من ثيمات DZBuild المضمّنة. التدفّق:

  1. اقرأ GET /v1/products + GET /v1/products/{id} لعرض الكتالوج.

  2. يضيف المستخدم العناصر إلى سلة من جانب العميل.

  3. لعرض سعر التوصيل قبل إتمام الطلب، اقرأ أسعار المتجر عبر GET /v1/shipping/rates (صلاحية shipping:read). أما الطلب نفسه فيُحتسب عليه دائمًا ما يحسبه الخادم.

  4. POST /v1/orders بالسلة وكتلة العميل؛ وamounts.shipping_cost في الرد هو تكلفة التوصيل المحتسبة.

  5. أعرض على العميل order_number وصفحة "شكرًا".

  6. استطلع GET /v1/orders?since=… لمتابعة تقدّم التنفيذ. فأحداث order.confirmed / order.shipped في /v1/webhooks لا تُطلق إلا عندما تتغيّر الحالة عبر الواجهة البرمجية — وتأكيد التاجر من لوحة التحكم لا يُنتج أي حدث.

انظر دليل الثيمات والواجهات المخصصة لخطوات كاملة.

"مزامنة الطلبات الجديدة مع CRM كل دقيقة"

استخدم فلتر since:

curl 'https://api.dzbuild.app/v1/orders?since=2026-04-30T20:00:00Z&limit=200' \
  -H "Authorization: Bearer $DZ_KEY"

الاستطلاع هو الأداة الصحيحة هنا. فاشتراك /v1/webhooks على order.created لا يغطي إلا الطلبات التي أنشأها تكاملك أنت عبر الواجهة البرمجية — أما طلبات واجهة المتجر وصفحات الهبوط فلا تُطلقه أبدًا، ولذلك لا يمكنه أن يحلّ محل مسح since. انظر Webhooks.

"تأكيد كل الطلبات المعلّقة لعميل واحد"

PHONE="0555000000"
curl -sS "https://api.dzbuild.app/v1/orders?status=pending&customer_phone=$PHONE" \
  -H "Authorization: Bearer $DZ_KEY" \
| jq -r '.data.items[].id' \
| while read OID; do
    curl -sS -X PATCH "https://api.dzbuild.app/v1/orders/$OID" \
      -H "Authorization: Bearer $DZ_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: confirm-$OID" \
      -d '{"status":"confirmed"}'
  done
هل أجاب هذا عن سؤالك؟