الطلبات
الطلبات هي قلب المنصة. كل طلب:
- ينتمي إلى متجر واحد فقط (مقيّد بمفتاحك — لا يمكنك أبدًا الوصول إلى بيانات تاجر آخر بالخطأ).
- له دورة حياة من 7 حالات.
- مرتبط بعميل (يُزال التكرار باستخدام الهاتف داخل المتجر).
- يحتوي على عنصر واحد أو أكثر، كل منها يمكن أن يحمل متغيرات وإضافات.
- له حالة دفع (
pending,paid,refunded) مستقلّة عن حالة التسليم.
دورة الحياة
pending → confirmed → processing → shipped → delivered
↘ returned
↘ cancelled
| الحالة | المعنى | المخزون |
|---|---|---|
pending | أُنشئ من الواجهة أو الـ API. ينتظر تأكيد التاجر. | لم يُحجز |
confirmed | أكّده التاجر (اتصل بالعميل، راجع السلة). | مُلتزَم (تم خصمه) |
processing | يُجهَّز / يُحضَّر للتسليم لشركة التوصيل. | مُلتزَم |
shipped | سُلِّم لشركة التوصيل. | مُلتزَم |
delivered | استلمه العميل ووقّع. | مُلتزَم |
cancelled | أُلغي الطلب — يعود المخزون إن كان مُلتزَمًا. | مُسترَد |
returned | أرجع العميل المنتج — يعود المخزون. | مُسترَد |
cancelled و returned حالتان نهائيتان — لا يمكن الخروج منهما.
POST /v1/orders — إنشاء طلب
أنشئ طلبًا جديدًا للمتجر المنادي. يستعمله الثيمات المخصصة والتطبيقات المحمولة والموزّعون وأي واجهة headless ترغب بتجاوز نقطة /api/orders القديمة المعتمدة على الكوكيز.
المصادقة: مفتاح منصة بصلاحية orders:write. يتطلب Idempotency-Key.
يُنشأ الطلب بحالة pending. لا يُلتزَم المخزون عند الإنشاء — أول انتقال pending → confirmed هو من يخصم المخزون (نفس تدفّق اللوحة). الهدف من ذلك: ترك مساحة لفريق العمليات لتصفية الطلبات الوهمية والمكررة وغير المُجاب عنها قبل لمس المخزون.
الجسم
{
"customer": {
"name": "Sarra Benali",
"phone": "0555000111",
"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 }
]
}
],
"shipping_cost": 600,
"discount": 0,
"payment_fee": 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 | ✅ | كود الولاية الجزائرية |
commune | string (1–100) | ✅ | نص حر، مثل "Bab Ezzouar" |
address | string | شارع + شقة؛ يمكن تركه فارغًا للاستلام بالمكتب |
العملاء يُزال تكرارهم بـ (store_id, phone). إن وُجد عميل بهذا الهاتف في متجرك، يُحدَّث (الاسم، الولاية، البلدية، العنوان، البريد) ويُعاد استخدامه. وإلا يُنشَأ سجل جديد.
delivery (كائن، اختياري)
| الحقل | النوع | الافتراضي | ملاحظات |
|---|---|---|---|
type | home | desk | digital | home | digital للمنتجات الرقمية فقط |
desk_id | int | null | null | إلزامي إن كان type = desk وأردت مكتبًا محددًا |
desk_name | string | null | null | تسمية بشرية اختيارية |
items (مصفوفة، إلزامية، 1–50 سطرًا)
| الحقل | النوع | إلزامي | ملاحظات |
|---|---|---|---|
product_id | int | ✅ | يجب أن ينتمي لمتجرك (الـ IDs من متاجر أخرى تُرفض بـ 400) |
quantity | int 1–9999 | ✅ | |
variants | مصفوفة من كائنات المتغيرات | انظر أدناه |
هام — تسعير معتمد على الخادم. أنت لا تُحدّد سعر السطر. نسحب دائمًا products.price من جانب الخادم ونجمع variants[].price_adjustment. إن أرسلت حقل price فهو يُتجاهل. يمنع ذلك عميلًا مخترَقًا من تمرير طلبات بأسعار وهمية منخفضة.
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 ≥ 0 | 0 | تحسبه من جانب العميل بناء على الولاية + نوع التسليم |
discount | number ≥ 0 | 0 | قيمة كود خصم، خصم يدوي… |
payment_fee | number ≥ 0 | 0 | رسوم بوابة الدفع الإلكتروني |
payment_method | cod | free_digital | digital_payment | تلقائي | الافتراضي cod للمادي، free_digital للرقمي |
notes | string ≤ 1000 | null | ملاحظات العميل، تظهر على صفحة الطلب في اللوحة |
الإجمالي يُحسب على الخادم: subtotal + shipping_cost - discount + payment_fee (لا يقل عن 0). و subtotal نفسه = sum(items[].quantity × (price + Σ variants.price_adjustment)).
الطلب
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 }
]
}
],
"shipping_cost": 600,
"payment_method": "cod"
}'
الاستجابة 201
تردّ بنفس شكل GET /v1/orders/{id} — معبّأة بالكامل بالإجماليات المحسوبة، وكتلة العميل المعيارية، وسطور العناصر التي أنشأتها للتو (مع متغيراتها). id و order_number هما القيم الجديدة.
الأخطاء
| الكود | السبب |
|---|---|
bad_request "customer object is required" | كائن customer مفقود |
bad_request "customer.phone is required (digits, optional leading +)" | الهاتف لم يطابق التعبير النمطي |
bad_request "customer.wilaya_id must be 1-58" | ولاية غير صالحة |
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, or digital" | نوع تسليم غير صالح |
bad_request "payment_method must be cod, free_digital, or digital_payment" | طريقة دفع غير صالحة |
bad_request "Monthly order limit reached for this store plan" | الخطة Free محدودة بـ 30 طلبًا/شهر — ارقَ |
Idempotency
كل POST يجب أن يحمل رأس Idempotency-Key. إن أعدت المحاولة بنفس المفتاح ونفس المتجر خلال 24 ساعة نُعيد لك نفس الاستجابة — يُنشأ الطلب مرة واحدة فقط. انظر 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
# سطر واحد فقط في جدول orders
Webhook الذي يُطلق
إنشاء طلب يُطلق order.created لكل webhooks المشتركة بهذا الحدث. انظر أحداث Webhook.
GET /v1/orders
اسرد الطلبات. ترقيم بالمؤشّر.
المصادقة: مفتاح منصة بصلاحية orders:read.
معاملات الاستعلام
| المعامل | النوع | ملاحظات |
|---|---|---|
limit | int 1–200 | الافتراضي 50 |
cursor | string | غير شفاف |
status | إحدى الحالات السبع | تصفية |
since | سلسلة ISO 8601 | created_at >= since |
customer_phone | string | تطابق تام |
الطلب
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-AD3C",
"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.
الاستجابة 200
{
"data": {
"id": 6894,
"order_number": "ORD-13-20260317-AD3C",
"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 },
"amounts": {
"subtotal": 1000,
"shipping_cost": 0,
"discount": 0,
"payment_fee": 0,
"total": 1000
},
"items": [
{
"id": 8421,
"product_id": 26,
"price": 1000,
"quantity": 1,
"variants": [
{ "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"
}
}
المتغيرات في الاستجابة
مصفوفة variants على كل عنصر هي المرجع لما اختاره العميل. كل إدخال يحمل group_name, option_name, color_code (لمتغيرات اللون)، و price_adjustment (الإضافة لكل قطعة). لعروض القطع المتعددة قد ترى عدة صفوف متغيرات على نفس العنصر بتجميعات مختلفة — انظر ملاحظات القطع المتعددة أدناه.
PATCH /v1/orders/{id} — تغيير الحالة
المصادقة: مفتاح منصة بصلاحية orders:write. يتطلب Idempotency-Key.
الجسم يجب أن يكون {"status": "<one of the 7>"}. نتحقق أن الانتقال مسموح؛ وإلا نُرجع 400 مع الحالات التالية المسموح بها.
الطلب
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 وتفاصيل الطلب المحدّث. الانتقال ملفوف في معاملة بـ SELECT ... FOR UPDATE فتتعامل المحاولات المتزامنة بصراع نظيف بتزامن متفائل.
آثار المخزون الجانبية
عند عبور حدود الالتزام بالمخزون:
- غير مُلتزَم → مُلتزَم (مثل
pending → confirmed) — يُخصم المخزون، يُزاد عدّاد المبيعات، يُبطَل كاش المنتجات. - مُلتزَم → cancelled / returned — يُسترَد المخزون، يُبطَل الكاش.
- الانتقالات الأخرى لا تمسّ المخزون.
تستعمل عمليات المخزون OrderController::handleStockChange القديم بالضبط ليطابق سلوك اللوحة 1:1.
الأخطاء
| الكود | السبب |
|---|---|
bad_request "status must be one of: pending, confirmed, …" | سلسلة غير صالحة |
bad_request "Transition X → Y not allowed. From 'X' you can only go to: …" | غير مسموح بآلة الحالات |
bad_request "order status changed concurrently; retry" | طلب آخر غيّر الحالة أثناء انتقالك. آمن لإعادة المحاولة بنفس مفتاح Idempotency. |
not_found | معرّف الطلب غير معروف أو لمتجر آخر |
POST /v1/orders/{id}/cancel
نقطة مريحة. تكافئ PATCH /v1/orders/{id} مع {"status":"cancelled"} لكن بقاعدة أكثر تساهلًا: أي حالة غير نهائية يمكن أن تنتقل إلى cancelled.
المصادقة: مفتاح منصة بصلاحية orders:write. يتطلب Idempotency-Key.
curl -X POST 'https://api.dzbuild.app/v1/orders/6894/cancel' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: cancel-6894-$(date +%s)"
المتغيرات — مرجع كامل
تجعل المتغيرات منتجًا واحدًا يغطي خيارات متعددة (لون، حجم، خامة، سعة…). على الواجهة يضغط العملاء بطاقات المتغيرات لاختيار ما يريدون؛ على الواجهة البرمجية ترسل أنت الخيارات المختارة كجزء من الطلب.
نموذج المتغيرات
للمنتج 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 — هذا قرار التاجر. التحقق والخصم يتمان فقط عند pending → confirmed.
المخزون لكل تركيبة
إن فُعّل combination_stock_enabled يُتتبع المخزون لكل تركيبة (Red+L = 5، Red+M = 8…). التركيبات لم تُكشف بعد على نقطة المنتجات العامة (ستُكشف في v1.1 كمصفوفة combinations[] على GET /v1/products/{id}/combinations). حاليًا تُجرى فحوص المخزون الفرعي عند التأكيد وتظهر عبر واجهة اللوحة الموجودة.
المتغيرات المتدرجة (Cascading)
تتيح إضافة Cascading Variants للتاجر جعل خيارات المجموعة B تعتمد على اختيار المجموعة A (مثل "علامة → موديل" — اختيار "Apple" للعلامة يُظهر فقط "iPhone 15" / "iPhone 14" للموديل). الواجهة لا تفرض قواعد التدرج؛ يُتوقع من عميلك معرفة القواعد من GET /v1/products/{id} وإرسال تركيبات صحيحة فقط. إن وُجدت قواعد تدرج وأرسلت تركيبة غير صالحة، سيُنشأ الطلب (لا نمنعه) — لكن التاجر سيرفضه عند التأكيد.
متغيرات الصورة-نص
بعض التجار يستخدمون نوع 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 المضمّنة. التدفّق:
- اقرأ
GET /v1/products+GET /v1/products/{id}لعرض الكتالوج. - يضيف المستخدم العناصر إلى سلة من جانب العميل.
- احسب
shipping_costمنwilaya_idللعميل (يمكنك تثبيت الأسعار أو سؤالGET /v1/storeلأسعار شحن المتجر — قادم في v1.1). - POST
/v1/ordersبالسلة + كتلة العميل + سعر الشحن. - أعرض على العميل
order_numberوصفحة "شكرًا". - استمع لـ webhooks
order.confirmed/order.shippedلإطلاق إشعارات.
انظر دليل الثيمات والواجهات المخصصة لخطوات كاملة.
"مزامنة الطلبات الجديدة مع CRM كل دقيقة"
استخدم فلتر since:
curl 'https://api.dzbuild.app/v1/orders?since=2026-04-30T20:00:00Z&limit=200' \
-H "Authorization: Bearer $DZ_KEY"
الأفضل — سجّل webhook لـ order.created وتجنّب الاستعلام المستمر. انظر 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