# الثيمات والواجهات المخصصة

تطلق DZBuild عدة ثيمات جاهزة لواجهة المتجر ومخصّصًا بدون كود. لكن إن أردت **التحكم الكامل** — واجهة React/Vue/Next.js/Flutter خاصة، تصميمك المثالي، التوجيه (routing) الخاص بك — فالواجهة البرمجية مبنية لك.

يمشي هذا الدليل عبر بناء واجهة headless من البداية للنهاية:

1. عرض الكتالوج (المنتجات، الفئات، المتغيرات)
2. بناء سلة (من جانب العميل أو الخادم، بحسبك)
3. إرسال الطلب عبر API
4. استقبال webhooks عند تغيّر الحالة

في النهاية ستكون واجهتك المخصصة متوافقة 100% مع لوحة تحكم التاجر في DZBuild — تظهر الطلبات، يخصم المخزون، يتكامل الشحن مع شركة التوصيل، بلا تنازلات.

## البنية[​](#البنية "رابط مباشر إلى البنية")

```
┌──────────────────────┐         ┌──────────────────────────┐

│ واجهة مخصصة          │  HTTPS  │  api.dzbuild.app/v1/*    │

│ (React / Vue / Flutter)│ ─────► │  Authorization: Bearer … │

│                      │         │                          │

│ - تقرأ المنتجات      │ ◄────── │  استجابات JSON           │

│ - تعرض السلة         │         │                          │

│ - تُرسل الطلبات       │         └──────────────────────────┘

└──────────────────────┘                      │

                                              ▼

                                  لوحة تحكم التاجر في DZBuild

                                  - يؤكد الطلبات

                                  - يدير المخزون

                                  - يتكامل مع شركات التوصيل
```

أنت تملك الواجهة. DZBuild تملك البيانات والعمليات. التاجر يدخل `dzbuild.com/dashboard` لإدارة الطلبات المؤكَّدة في واجهتك المخصصة.

## المتطلبات المسبقة[​](#المتطلبات-المسبقة "رابط مباشر إلى المتطلبات المسبقة")

* متجر DZBuild على خطة **Enterprise** سارية. الواجهة البرمجية ميزة من خطة Enterprise: المفتاح الذي يكون متجره على خطة أخرى، أو انتهى اشتراك Enterprise الخاص به، يحصل على `403 forbidden` `"API access requires an active Enterprise plan"` في كل نداء، ويعود للعمل حين يرجع المتجر إلى خطة Enterprise سارية.
* **مفتاح API يُنشئه مالك المتجر** من **الإعدادات ← واجهة API** في لوحة التحكم. المفاتيح المُنشأة هناك مُسجَّلة مسبقًا في البرنامج التجريبي. والمفتاح غير المُسجَّل يحصل على `403 forbidden` `"API is in pilot mode; key not enrolled"`.
* خادم خلفي (أو دالة serverless / edge worker) يحمل المفتاح. **لا تشحن** السر إلى المتصفح — انظر [الأمان](#%D8%A7%D9%84%D8%A3%D9%85%D8%A7%D9%86).

وتحدّد الخطة أيضًا سقفًا شهريًا للطلبات: **المتجر على الخطة Free يتوقف عن قبول الطلبات بعد 30 طلبًا في الشهر الميلادي**، ويُرجع `POST /v1/orders` عندئذ `400 bad_request "Monthly order limit reached for this store plan"`. أما Pro و Unlimited و Enterprise فبلا سقف طلبات. عالج هذا الخطأ في صفحة الدفع لديك.

## الخطوة 0 — احصل على مفتاح[​](#الخطوة-0--احصل-على-مفتاح "رابط مباشر إلى الخطوة 0 — احصل على مفتاح")

يُنشئ مالك المتجر المفاتيح من لوحة تحكم التاجر عبر **الإعدادات ← واجهة API** (`/dashboard/api`)، على خطة Enterprise سارية. يحتفظ المتجر بـ 3 مفاتيح نشطة خاصة به كحد أقصى، ويظهر كل سر مرة واحدة:

1. **أنشئ المفتاح الأول** من الإعدادات ← واجهة API. المفاتيح المُنشأة هناك مُسجَّلة مسبقًا في البرنامج التجريبي.
2. **أنشئ أي مفاتيح إضافية بنفسك** انطلاقًا من ذلك المفتاح، أو من الصفحة نفسها:

```
curl -X POST 'https://api.dzbuild.app/v1/keys' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Content-Type: application/json" \

  -H "Idempotency-Key: $(uuidgen)" \

  -d '{"type":"platform","name":"Custom storefront — production"}'

# HTTP 200 → { "data": { "key_id", "bearer_token", "signing_secret", "note" } }
```

ينسخ المفتاح الجديد فئة تحديد المعدل وعَلَم البرنامج التجريبي من المفتاح المنادي. و**الصلاحيات غير قابلة للاختيار**: يحصل مفتاح المنصة الجديد على المجموعة الافتراضية (`store:read` و `store:write` و `products:read` و `products:write` و `orders:read` و `orders:write` و `customers:read` و `landing_pages:read` و `landing_pages:write` و `promos:read` و `promos:write` و `pixels:read` و `pixels:write` و `shipping:read` و `shipping:write` و `webhooks:read` و `webhooks:write` و `usage:read` و `analytics:read` و `whatsapp:read` و `whatsapp:send`)، في حدود الصلاحيات التي يملكها المفتاح المنادي، لذا افصل البيئات بمفاتيح منفصلة، لا بصلاحيات أضيق.

انسخ `bearer_token` — يظهر **مرة واحدة** عند الإنشاء. فقدته؟ ألغه وأنشئ جديدًا.

اختبر بـ:

```
curl https://api.dzbuild.app/v1/whoami \

  -H "Authorization: Bearer $DZ_KEY"

# المتوقع: { "data": { "key_id": "...", "store_id": 13, "type": "platform", "scopes": [...] } }
```

## الخطوة 1 — عرض الكتالوج[​](#الخطوة-1--عرض-الكتالوج "رابط مباشر إلى الخطوة 1 — عرض الكتالوج")

```
// pages/index.js (Next.js)

export async function getServerSideProps() {

  const res = await fetch('https://api.dzbuild.app/v1/products?limit=50&status=active', {

    headers: { 'Authorization': `Bearer ${process.env.DZ_KEY}` },

  });

  const { data } = await res.json();

  return { props: { products: data.items } };

}



export default function Home({ products }) {

  return (

    <ul>

      {products.map(p => (

        <li key={p.id}>

          {p.primary_image && (

            <img src={p.primary_image} alt={p.name} />

          )}

          <h2>{p.name}</h2>

          <p>{p.price} DZD</p>

          <a href={`/product/${p.slug}`}>عرض</a>

        </li>

      ))}

    </ul>

  );

}
```

أمران عن الصور:

* `primary_image` رابط CDN كامل (مثلًا `https://cdn.dzbuild.app/uploads/products/<store_id>/<file>`). ضعه في وسم `img` كما هو، دون إضافة أي بادئة.
* عنصر القائمة **لا** يتضمّن `store_id`. اقرأه مرة واحدة من `GET /v1/store` أو `GET /v1/whoami` واحتفظ به في إعداداتك.
* `primary_image` يكون `null` حين لا صورة للمنتج. احترس لذلك، كما في المثال أعلاه.

خزّن الاستجابة من جهتك: قائمة المنتجات تُخدَم حديثًا في كل نداء، فالقراءات المتكررة عند الحجم تكلّف رحلة في كل مرة.

## الخطوة 2 — صفحة تفاصيل منتج بالمتغيرات[​](#الخطوة-2--صفحة-تفاصيل-منتج-بالمتغيرات "رابط مباشر إلى الخطوة 2 — صفحة تفاصيل منتج بالمتغيرات")

```
const res = await fetch(`https://api.dzbuild.app/v1/products/${id}`, {

  headers: { 'Authorization': `Bearer ${process.env.DZ_KEY}` },

});

const { data: product } = await res.json();
```

تتضمن الاستجابة `variants[]` — كل إدخال مجموعة متغيرات بخياراتها:

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

    ]

  }

]
```

اعرض محدّدًا لكل مجموعة. لـ `type: color` اعرض swatches بالـ `color_code`؛ لـ `type: text` اعرض labels؛ لـ `type: dropdown` اعرض قائمة منسدلة (`select`)؛ لـ `type: image_text` اعرض صورًا مصغرة: فـ `options[].image_id` هو **معرّف رقمي** يطابق `images[].id` لأحد المدخلات في نفس استجابة المنتج. ومجموعة `type: selectable` اختيارية وتسمح للعميل باختيار عدة خيارات. ويحمل كل خيار أيضًا `price_adjustment` (المبلغ المضاف إلى سعر المنتج، ويُستعمل في الخطوة 3) و `is_active`؛ أخفِ الخيارات التي تكون فيها `is_active` بقيمة `false`.

والحقل `images[].url` رابط CDN كامل مثل `primary_image` في الخطوة 1، فضعه في وسم `img` كما هو.

**التعامل مع نفاذ المخزون:** `options[].stock` يكون `null` إن لم يفعّل التاجر المخزون لكل متغيّر. إن كان رقمًا ≤ 0 ظلّل الخيار. لا يرفض الـ API طلبًا لأن خيارًا فيه نفد مخزونه، فتحقّق من المخزون في واجهتك قبل إرسال الطلب. ومخزون كل تركيبة موجود في `combinations[]` من نفس الاستجابة.

## الخطوة 3 — بناء سلة[​](#الخطوة-3--بناء-سلة "رابط مباشر إلى الخطوة 3 — بناء سلة")

السلة **من جانب العميل** (React state, Vuex, Pinia, localStorage…). لا تحتاج نداء API للإضافة. كل سطر:

```
{

  product_id:   26,

  product_name: "T-shirt",

  base_price:   1500,

  quantity:     1,

  selected_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 }

  ]

}
```

احسب إجمالي السطر من جانب العميل: `(base_price + sum(price_adjustment)) × quantity`. أعرض على العميل إجمالي السلة، لكن عامله كعرض فقط:

* **أي سعر ترسله داخل العنصر يُتجاهَل** (حقل الـ API هو `price`). فالخادم يستعمل دائمًا سعر الكتالوج لـ `product_id`.
* **`price_adjustment` يُعاد حلّه من الكتالوج** بحسب الزوج `(group_name, option_name)`. وعند أي عدم تطابق تفوز قيمة الكتالوج. ولا تُستعمل قيمتك أنت إلا حين لا يُحَل الزوج إطلاقًا — وهذا بالضبط ما يحدث بعد أن يُعيد التاجر تسمية خيار متغيّر، فأبقِ سلاسل `group_name`/`option_name` لديك متزامنة مع الكتالوج.
* `group_name` و `option_name` يُبتران بصمت إلى 100 حرف، و `color_code` إلى 7.

## الخطوة 4 — جمع بيانات العميل + احتساب الشحن[​](#الخطوة-4--جمع-بيانات-العميل--احتساب-الشحن "رابط مباشر إلى الخطوة 4 — جمع بيانات العميل + احتساب الشحن")

نموذج checkout قياسي: الاسم، الهاتف، الولاية، البلدية، العنوان. اقرأ قائمة الولايات مرة واحدة من `GET /v1/wilayas` (بأسمائها بالعربية والفرنسية والإنجليزية)، وبلديات كل ولاية من `GET /v1/wilayas/{id}/communes`. تضم القائمة 58 ولاية، أو 69 على متجر مضبوط على نمط 69 ولاية، وطلبات هذا المتجر تقبل معرّفات ولايات حتى 69.

`GET /v1/store` متاحة فعلًا ويستحق نداؤها مرة واحدة عند الإقلاع: تُرجع اسم المتجر و slug واللغة والوصف والشعار والأيقونة واللافتة وألوان الثيم والخط والنطاق الفرعي والنطاق المخصص (وهل هو موثَّق) و `public_url` و `hide_branding` و `created_at`. وتُخدَم حديثًا في كل نداء.

الشحن ليس ضمن `GET /v1/store`، بل له نقاطه الخاصة: `GET /v1/shipping/rates` يُرجع سعر التوصيل إلى المنزل وإلى المكتب لكل ولاية كما ضبطه التاجر، و `GET /v1/shipping/settings` قواعد الشحن المجاني، و `GET /v1/shipping/coverage?wilaya_id=16` مكاتب الاستلام لدى شركة توصيل المتجر في تلك الولاية (انظر اختيار مكتب الاستلام أدناه). استعملها لتعرض على العميل تقديرًا قبل إرسال الطلب.

لا ترسل سعر الشحن: يحسبه `POST /v1/orders` من أسعار التاجر للولاية ونوع التوصيل، ويطبّق الشحن المجاني ورسم الوزن الإضافي، ويتجاهل أي `shipping_cost` في جسم الطلب. وإن كان نوع التوصيل المختار معطّلًا في تلك الولاية والنوع الآخر مفعّلًا، يتحوّل الطلب إلى النوع الذي يقدّمه التاجر. وطلبات `pickup` و `digital` بلا رسوم شحن. اعرض على العميل `amounts.shipping_cost` و `amounts.total` من الرد.

## الخطوة 5 — إرسال الطلب[​](#الخطوة-5--إرسال-الطلب "رابط مباشر إلى الخطوة 5 — إرسال الطلب")

```
// على خادمك الخلفي (Next.js API route, Edge function, server…)

async function placeOrder(req, res) {

  const cart = req.body;



  const order = {

    customer: {

      name:      cart.name,

      phone:     cart.phone,

      email:     cart.email || null,

      wilaya_id: cart.wilaya_id,

      commune:   cart.commune,

      address:   cart.address || ''

    },

    delivery: {

      type:      cart.delivery_type,           // "home" | "desk" | "pickup" | "digital"

      desk_id:   cart.desk_id || null,

      desk_name: cart.desk_name || null

    },

    items: cart.items.map(line => ({

      product_id: line.product_id,

      quantity:   line.quantity,

      variants:   line.selected_variants

    })),

    // لا shipping_cost: الخادم يحسب الشحن من أسعار التاجر

    discount:       0,

    payment_method: 'cod',

    notes:          cart.notes || null

  };



  const idempKey = req.headers['x-checkout-id'] || crypto.randomUUID();



  const apiRes = await fetch('https://api.dzbuild.app/v1/orders', {

    method: 'POST',

    headers: {

      'Authorization':    `Bearer ${process.env.DZ_KEY}`,

      'Content-Type':     'application/json',

      'Idempotency-Key':  idempKey

    },

    body: JSON.stringify(order)

  });



  if (!apiRes.ok) {

    const err = await apiRes.json();

    return res.status(apiRes.status).json(err);

  }

  const { data: createdOrder } = await apiRes.json();   // HTTP 200، وليس 201

  return res.json({

    order_number: createdOrder.order_number,

    total:        createdOrder.amounts.total

  });

}
```

يُجيب `POST /v1/orders` بـ **HTTP 200** عند النجاح، والجسم هو تفصيل الطلب الكامل المعياري، وهو نفس الشكل الذي يُرجعه `GET /v1/orders/{id}`. تفرّع على `apiRes.ok`، لا على `status === 201` أبدًا.

يرى العميل `order_number` في صفحة النجاح. ولوحة التاجر تُظهر الآن الطلب الجديد في قائمة pending جاهزًا للتأكيد.

### حدود التحقق[​](#حدود-التحقق "رابط مباشر إلى حدود التحقق")

كل قاعدة أدناه ترمي `400 bad_request` مع الرسالة مباشرة، فاعرضها في نموذج الدفع لديك بدل اكتشافها في الإنتاج:

| الحقل                | القاعدة                                                                                                                                        |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `items`              | من سطر إلى 50 سطرًا، مصفوفة غير فارغة                                                                                                          |
| `items[].product_id` | يجب أن ينتمي لمتجر المفتاح                                                                                                                     |
| `items[].quantity`   | من 1 إلى 9999                                                                                                                                  |
| `customer.name`      | من 1 إلى 255 حرفًا                                                                                                                             |
| `customer.phone`     | يجب أن يطابق `^\+?[0-9 ]{6,20}$` — أرقام ومسافات فقط، مع `+` واحدة في البداية على الأكثر. **الشرطات والأقواس مرفوضة**، فطبّع الرقم قبل الإرسال |
| `customer.wilaya_id` | من 1 إلى 58، أو من 1 إلى 69 على متجر مضبوط على نمط 69 ولاية                                                                                    |
| `customer.commune`   | من 1 إلى 100 حرف                                                                                                                               |
| `delivery.type`      | `home` أو `desk` أو `pickup` أو `digital` (القيمة الافتراضية `home`)                                                                           |
| `payment_method`     | `cod` أو `free_digital` أو `digital_payment` — لا غير (`card` و `paypal` وغيرها مرفوضة)                                                        |
| `discount`           | يجب أن يكون `>= 0`، ويُقلَّص إلى المجموع الفرعي زائد الشحن إن تجاوزه (وليس خطأ). وأي `shipping_cost` أو `payment_fee` ترسله يُتجاهَل           |
| `notes`              | تُبتر إلى 1000 حرف (وليس خطأ)                                                                                                                  |

يُضاف إلى ذلك سقف الخطة: على متجر بالخطة Free، الطلب رقم 31 في الشهر الميلادي يُرجع `400 bad_request "Monthly order limit reached for this store plan"`.

## الخطوة 6 — استقبال webhooks (اختياري لكنه قوي)[​](#الخطوة-6--استقبال-webhooks-اختياري-لكنه-قوي "رابط مباشر إلى الخطوة 6 — استقبال webhooks (اختياري لكنه قوي)")

سجّل webhook ليتفاعل متجرك مع أحداث الطلبات:

```
curl -X POST 'https://api.dzbuild.app/v1/webhooks' \

  -H "Authorization: Bearer $DZ_KEY" \

  -H "Content-Type: application/json" \

  -H "Idempotency-Key: $(uuidgen)" \

  -d '{

    "url":    "https://yourstorefront.com/api/dz-webhook",

    "events": ["order.created", "order.confirmed", "order.shipped", "order.delivered"]

  }'
```

ستحصل على `secret` في الاستجابة (HTTP 200): احفظه. كل تسليم يُوقَّع به، فتحقّق من `X-DZ-Signature` قبل أن تتصرّف بناءً على أي تسليم. انظر [التحقق من التواقيع](https://dzbuild.com/ar/ar/api-docs/webhooks/verifying-signatures.md) للوصفة والشيفرة.

وهناك حدّان يستحقان المعرفة قبل أن تبني على هذا: `order.created` في API v1 يُطلق **فقط** للطلبات التي تُنشئها واجهتك عبر الـ API (لا لأي طلب يُوضع في واجهة DZBuild الخاصة بالتاجر)، وأحداث الحالة تُطلق فقط لتغييرات الحالة التي تتم عبر الـ API. انظر [كتالوج الأحداث](https://dzbuild.com/ar/ar/api-docs/webhooks/events.md).

استخدم webhooks لـ:

* إرسال SMS للعميل عند تأكيد طلبه.
* تحديث CRM / Google Sheet / تحليلاتك.
* إبطال صفحة "thank you" بعد تأكيد التاجر.
* إطلاق تسليم منتج رقمي بعد `order.delivered`.

## أنماط شائعة[​](#أنماط-شائعة "رابط مباشر إلى أنماط شائعة")

### واجهة متعددة اللغات[​](#واجهة-متعددة-اللغات "رابط مباشر إلى واجهة متعددة اللغات")

احفظ ترجماتك في الواجهة. الـ API يُعيد أسماء المنتجات والأوصاف كما أدخلها التاجر تمامًا. إن استعمل التاجر [إضافة Multi-language](https://dzbuild.com/ar/ar/docs/addons/multi-language.md)، يبقى `GET /v1/products/{id}` يُعيد الاسم والوصف الأساسيين فقط؛ فترجمات الإضافة ليست جزءًا من استجابة الـ API، لذا اختر ما يريده عميلك من جانب العميل.

### اختيار مكتب الاستلام (Stop-desk)[​](#اختيار-مكتب-الاستلام-stop-desk "رابط مباشر إلى اختيار مكتب الاستلام (Stop-desk)")

لـ `delivery.type = "desk"`:

```
// 1. اقرأ مكاتب الاستلام لدى شركة توصيل المتجر للولاية المختارة:

//    GET /v1/shipping/coverage?wilaya_id=16  (scope shipping:read)

//    تحمل desks[] الحقول desk_id و name و address و phone و commune_id؛

//    والرد 422 no_courier_linked يعني أن المتجر بلا شركة توصيل مربوطة



// 2. أعرض القائمة، يختار العميل:

selectedDesk = { id: 7842, name: "Yalidine Bab Ezzouar" };



// 3. مرّره للطلب:

order.delivery = {

  type:      "desk",

  desk_id:    selectedDesk.id,

  desk_name:  selectedDesk.name

};
```

### الدفع الإلكتروني (SlickPay / Edahabia)[​](#الدفع-الإلكتروني-slickpay--edahabia "رابط مباشر إلى الدفع الإلكتروني (SlickPay / Edahabia)")

لـ `payment_method = "digital_payment"`:

1. أرسل الطلب عبر API كالعادة؛ يُنشأ بـ `pending` و `payment_status = pending`.
2. وجّه العميل إلى رابط checkout في SlickPay / Edahabia.
3. عند النجاح يستلم خادمك الخلفي تأكيد مزوّد الدفع. ويمكنك عندها تأكيد الطلب بـ `PATCH /v1/orders/{id}` مع `{"status": "confirmed"}`. لا يمكن ضبط `payment_status` عبر الـ API، وإعداد SlickPay الخاص بالمتجر لا يعلّم كمدفوعة إلا الطلبات التي دُفعت في صفحة الدفع الخاصة بالمتجر، فلا يعلّم طلبًا أُنشئ عبر الـ API كمدفوع.

### الإرجاع والاسترداد[​](#الإرجاع-والاسترداد "رابط مباشر إلى الإرجاع والاسترداد")

تُدار حاليًا من اللوحة. وعبر الـ API يمكنك تعليم طلب كمُرجَع بـ `PATCH /v1/orders/{id}` مع `{"status": "returned"}`، وذلك من الحالة `shipped` أو `delivered`. ولا توجد نقطة للاسترداد.

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

* **لا تضع أبدًا** مفتاح API في كود يصل إليه المتصفح. المفاتيح تنتمي لخادمك / serverless / edge worker. المتصفح ينادي **خادمك** وخادمك ينادي DZBuild.
* **HTTPS فقط** بين واجهتك و `api.dzbuild.app`. طلبات HTTP العادية مرفوضة.
* **Idempotency-Key مطلوب** على كل `POST` و `PATCH` و `DELETE` (واختياري على `PUT`). بدونه تحصل على `400` بالكود `bad_request` والرسالة `"Idempotency-Key header is required for write requests"`؛ فالكود هو `bad_request`، لا `idempotency_key_required`. ويجب ألا تتجاوز القيمة 64 حرفًا من `[A-Za-z0-9_-:.]`؛ فـ `crypto.randomUUID()` يمرّ، أما base64 ومعظم ترميزات التجزئة فلا (`+` و `/` و `=` مرفوضة).
* **حدود المعدل** تُحتسب لكل **متجر**: كل مفاتيح المتجر تتقاسم ميزانية واحدة قدرها 600 نداء في الدقيقة، فالمفاتيح الإضافية لا ترفعها. والواجهة البرمجية متاحة على خطة Enterprise وحدها، والمتجر الذي يغادرها يحصل على `403` إلى أن يعود إلى خطة Enterprise سارية. ولاحظ أن هذه الحدود تخصّ نداءات الواجهة البرمجية وحدها؛ أما واجهة المتجر التي تبنيها فتخدم متسوّقيك دون أن تستهلك منها شيئًا. انظر [حدود المعدل](https://dzbuild.com/ar/ar/api-docs/rate-limits.md) للسقوف الحالية.
* **مفتاح لكل بيئة.** لا تشارك مفاتيح dev/prod. أنشئ مفتاحًا منفصلًا للمرحلة (staging) وألغه عند انتهائها. (لا يمكنك اختيار الصلاحيات، ويحتفظ المفتاح بالصلاحيات التي أُنشئ بها، فالفصل يأتي من المفتاح نفسه.)

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

| المشكلة                                                             | السبب المرجَّح                                                            | الإصلاح                                                                |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `401 unauthorized "Missing Authorization header"`                   | نسيت رأس `Authorization: Bearer …`                                        | أضفه                                                                   |
| `401 unauthorized "Invalid bearer format"`                          | الرمز بلا نقطة — غالبًا نسخ مبتور احتفظ بمعرّف المفتاح `dzpk_live_…` وحده | استعمل رمز bearer الكامل بمحارفه الـ 73                                |
| `401 unauthorized "Invalid or revoked API key"`                     | خطأ كتابي، مفتاح ملغى أو منتهي الصلاحية، أو متغير بيئة خاطئ               | أنشئ مفتاحًا جديدًا من **الإعدادات ← واجهة API**                       |
| `403 forbidden "API is in pilot mode; key not enrolled"`            | المفتاح موجود لكن DZBuild لم تُسجّله في البرنامج التجريبي                 | راسل الدعم                                                             |
| `403 forbidden "API access requires an active Enterprise plan"`     | المتجر ليس على خطة Enterprise، أو انتهى اشتراك Enterprise الخاص به        | يجدّد التاجر اشتراكه أو يرقّي إلى Enterprise؛ فيعود المفتاح نفسه للعمل |
| `400 bad_request "Product N does not belong to this store"`         | معرّف من متجر آخر                                                         | استعمل المفتاح الصحيح                                                  |
| `400 bad_request "items must be a non-empty array"`                 | سلة فارغة                                                                 | لا تُرسل سلال فارغة                                                    |
| `400 bad_request "Monthly order limit reached for this store plan"` | متجر بالخطة Free بلغ سقف 30 طلبًا في الشهر                                | يرقّي التاجر إلى Pro أو أعلى                                           |
| `429 rate_limited`                                                  | استعلام مفرط                                                              | استبدل بـ webhooks؛ وتراجع باستعمال ترويسة `Retry-After`               |

## أمثلة الشيفرة[​](#أمثلة-الشيفرة "رابط مباشر إلى أمثلة الشيفرة")

كل ما تحتاجه موجود في هذه الصفحة — عرض الكتالوج، وعرض المتغيرات، وشكل السلة، وإرسال الطلب، وتسجيل webhook. انسخ من الأقسام أعلاه؛ فلا توجد مستودعات بداية منفصلة لاستنساخها.

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

1. اختبر جيدًا بمفتاح `pilot` على متجر اختبار.
2. أنشئ مفتاحًا منفصلًا للإنتاج (نفس الصلاحيات، اسم مختلف).
3. انشر واجهتك بمفتاح prod في متغيرات البيئة.
4. ضع طلب اختبار حقيقي؛ تأكد من ظهوره في اللوحة.
5. اختبر استردادًا إن كنت تقدّمه.
6. سجّل webhooks تشير إلى رابط الإنتاج.
7. راقب `GET /v1/usage` يوميًا أول أسبوع.

## خارطة الطريق[​](#خارطة-الطريق "رابط مباشر إلى خارطة الطريق")

| الميزة                                           | الحالة                                                                            |
| ------------------------------------------------ | --------------------------------------------------------------------------------- |
| أسعار الشحن حسب الولاية ومكاتب الاستلام          | متاحة: `GET /v1/shipping/rates` و `GET /v1/shipping/coverage?wilaya_id=`          |
| المخزون لكل تركيبة                               | متاح: `combinations[]` في `GET /v1/products/{id}` و `GET /v1/products/{id}/stock` |
| `POST /v1/orders/{id}/refund` لاستردادات عبر API | غير متاح                                                                          |
| رفع صور المنتج                                   | متاح: `POST /v1/products/{id}/images` يقبل رابط صورة https عامًا                  |
| حقول متعددة اللغات على استجابات المنتجات         | غير متاحة                                                                         |

إن احتجت أيًا منها أبكر، [تواصل مع الدعم](https://dzbuild.com/ar/ar/docs/operations/faq.md).
