# الطلبات

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

* ينتمي إلى متجر واحد فقط (مقيّد بمفتاحك — لا يمكنك أبدًا الوصول إلى بيانات تاجر آخر بالخطأ).
* له دورة حياة من 7 حالات.
* مرتبط بعميل (يُزال التكرار باستخدام الهاتف داخل المتجر).
* يحتوي على عنصر واحد أو أكثر، كل منها يمكن أن يحمل متغيرات. **إضافات المنتج (add-ons، أي الحقول المخصصة المدفوعة) غير مدعومة في واجهة v1** — انظر [الإضافات](#add-ons-are-not-exposed).
* له حالة دفع (`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` — إنشاء طلب[​](#post-v1orders--إنشاء-طلب "رابط مباشر إلى post-v1orders--إنشاء-طلب")

أنشئ طلبًا جديدًا للمتجر المنادي. يستعمله **الثيمات المخصصة** و**التطبيقات المحمولة** و**الموزّعون** وأي **واجهة 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":     "sarra@example.com",

    "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` (كائن، إلزامي)[​](#customer-كائن-إلزامي "رابط مباشر إلى 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` (كائن، اختياري)[​](#delivery-كائن-اختياري "رابط مباشر إلى 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 سطرًا)[​](#items-مصفوفة-إلزامية-150-سطرًا "رابط مباشر إلى items-مصفوفة-إلزامية-150-سطرًا")

| الحقل        | النوع                      | إلزامي | ملاحظات                                                    |
| ------------ | -------------------------- | ------ | ---------------------------------------------------------- |
| `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` (مصفوفة، اختيارية)[​](#itemsvariants-مصفوفة-اختيارية "رابط مباشر إلى itemsvariants-مصفوفة-اختيارية")

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

| الحقل              | النوع          | ملاحظات                                                                                                          |
| ------------------ | -------------- | ---------------------------------------------------------------------------------------------------------------- |
| `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[​](#الاستجابة-200 "رابط مباشر إلى الاستجابة 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[​](#idempotency "رابط مباشر إلى Idempotency")

كل POST يجب أن يحمل رأس `Idempotency-Key`. إن أعدت نفس الطلب (نفس `Idempotency-Key` ونفس مفتاح API ونفس بايتات الجسم) خلال 24 ساعة نُعيد لك **نفس** الاستجابة مع `Idempotency-Replay: 1`، فيُنشأ الطلب مرة واحدة فقط. ردود الخطأ تُعاد هي أيضًا، ما عدا `429` و`5xx`، ونفس `Idempotency-Key` مع جسم مختلف يرد بـ `422 idempotency_key_reuse`: أرسل الطلب المصحَّح بمفتاح جديد. انظر [Idempotency](https://dzbuild.com/ar/ar/api-docs/idempotency.md).

```
# آمن للإعادة بالمفتاح نفسه

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 الذي يُطلق[​](#webhook-الذي-يُطلق "رابط مباشر إلى Webhook الذي يُطلق")

إنشاء طلب يُطلق `order.created` لكل اشتراكات `/v1/webhooks` على هذا الحدث. انظر [أحداث Webhook](https://dzbuild.com/ar/ar/api-docs/webhooks/events.md).

`/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` من [العملاء](https://dzbuild.com/ar/ar/api-docs/resources/customers.md) وارفضه من جانب عميلك إن احتجت ذلك.
* يطبّق فحوص مكافحة الإساءة التلقائية في checkout واجهة المتجر، ولا قواعد إضافة الحد الأدنى/الأقصى للكمية.

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

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

***

## `GET /v1/orders`[​](#get-v1orders "رابط مباشر إلى get-v1orders")

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

**المصادقة:** مفتاح بصلاحية `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؛ صفِّ من جانب العميل.

### الطلب[​](#الطلب-1 "رابط مباشر إلى الطلب")

```
curl 'https://api.dzbuild.app/v1/orders?status=pending&limit=20' \

  -H "Authorization: Bearer $DZ_KEY"
```

### الاستجابة 200[​](#الاستجابة-200-1 "رابط مباشر إلى الاستجابة 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}`[​](#get-v1ordersid "رابط مباشر إلى get-v1ordersid")

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

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

### الاستجابة 200[​](#الاستجابة-200-2 "رابط مباشر إلى الاستجابة 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 "رابط مباشر إلى الإضافات (add-ons) غير مكشوفة")

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

***

## `PATCH /v1/orders/{id}` — تغيير الحالة[​](#patch-v1ordersid--تغيير-الحالة "رابط مباشر إلى patch-v1ordersid--تغيير-الحالة")

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

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

### الطلب[​](#الطلب-2 "رابط مباشر إلى الطلب")

```
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** — يُسترَد المخزون.
* الانتقالات الأخرى لا تمسّ المخزون.

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

### الأخطاء[​](#الأخطاء-1 "رابط مباشر إلى الأخطاء")

| الكود                                                                         | السبب                                                                                                                                                                           |
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `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`[​](#post-v1ordersidcancel "رابط مباشر إلى post-v1ordersidcancel")

نقطة مريحة — تكافئ **تمامًا** `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`[​](#post-v1ordersidsend-to-delivery "رابط مباشر إلى post-v1ordersidsend-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)         | لنداءات شركات التوصيل ميزانية خاصة بكل متجر فوق [حدود المعدل](https://dzbuild.com/ar/ar/api-docs/rate-limits.md) |

```
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`[​](#كيفية-إرسال-المتغيرات-في-post-v1orders "رابط مباشر إلى كيفية-إرسال-المتغيرات-في-post-v1orders")

حوّل اختيار العميل إلى إدخال متغيّر واحد لكل مجموعة مختارة. لـ "تيشيرت أحمر مقاس 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` إن قُطعت. انظر [المنتجات](https://dzbuild.com/ar/ar/api-docs/resources/products.md). ويتحرك مخزون التركيبات في نفس اللحظة التي يتحرك فيها باقي مخزون الطلب.

### المتغيرات المتدرجة (Cascading)[​](#المتغيرات-المتدرجة-cascading "رابط مباشر إلى المتغيرات المتدرجة (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-مخصصة "رابط مباشر إلى \"إرسال طلب من واجهة 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` لا تُطلق إلا عندما تتغيّر الحالة **عبر الواجهة البرمجية** — وتأكيد التاجر من لوحة التحكم لا يُنتج أي حدث.

انظر دليل [الثيمات والواجهات المخصصة](https://dzbuild.com/ar/ar/api-docs/guides/custom-storefronts.md) لخطوات كاملة.

### "مزامنة الطلبات الجديدة مع CRM كل دقيقة"[​](#مزامنة-الطلبات-الجديدة-مع-crm-كل-دقيقة "رابط مباشر إلى \"مزامنة الطلبات الجديدة مع 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](https://dzbuild.com/ar/ar/api-docs/webhooks/overview.md).

### "تأكيد كل الطلبات المعلّقة لعميل واحد"[​](#تأكيد-كل-الطلبات-المعلّقة-لعميل-واحد "رابط مباشر إلى \"تأكيد كل الطلبات المعلّقة لعميل واحد\"")

```
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
```
