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

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

تطلق DZBuild 5 ثيمات (Brico, Digital, Prestige, Showcase, Starter) ومخصّصًا بدون كود. لكن إن أردت التحكم الكامل — واجهة 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 على خطة Pro أو أعلى (الخطة Free لا تشمل الواجهة البرمجية — انظر الخطط).
  • مفتاح بصلاحيات صحيحة:
    • products:read — لعرض الكتالوج
    • orders:read — لقراءة الطلبات (لصفحات الحالة/التتبع)
    • orders:write — لإنشاء الطلبات من الواجهة
  • خادم خلفي (أو دالة serverless / edge worker) يحمل المفتاح. لا تشحن السر إلى المتصفح — انظر الأمان.

الخطوة 0 — احصل على مفتاح

في لوحة التاجر:

لوحة التحكم → Developer → API Keys → "+ إنشاء مفتاح منصة"
• الاسم: "Custom storefront — production"
• الصلاحيات: products:read, orders:read, orders:write
• حفظ

انسخ السر (dzpk_live_…) — يظهر مرة واحدة. فقدته؟ ألغه وأنشئ جديدًا.

اختبر بـ:

curl https://api.dzbuild.app/v1/whoami \
-H "Authorization: Bearer dzpk_live_..."
# المتوقع: { "data": { "key_id": "...", "store_id": 13, "type": "platform", "scopes": [...] } }

الخطوة 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}>
<img src={`https://cdn.dzbuild.app/${p.store_id}/products/${p.primary_image}`} />
<h2>{p.name}</h2>
<p>{p.price} DZD</p>
<a href={`/product/${p.slug}`}>عرض</a>
</li>
))}
</ul>
);
}

خزّن الاستجابة — قائمة المنتجات مخزَّنة في الحافة لمدة 30 ثانية فالقراءات المتكررة عند الحجم رخيصة.

الخطوة 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: image_text اعرض صورًا مصغرة (اسم الملف موجود على options[].image_id — اجلب images[].url المطابق من نفس استجابة المنتج).

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

الخطوة 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. أعرض على العميل إجمالي السلة. (الخادم يُعيد الحساب عند الإرسال — لا تثق بحساب العميل للقيمة النهائية.)

الخطوة 4 — جمع بيانات العميل + احتساب الشحن

نموذج checkout قياسي: الاسم، الهاتف، الولاية، البلدية، العنوان. استخدم قائمة الولايات من GET /v1/store (قادم) أو ثبّت 58 ولاية في الواجهة — قائمة ثابتة.

لتكلفة الشحن خياران:

  • ثبّت الأسعار حسب الولاية + نوع التسليم في واجهتك (الأبسط).
  • اقرأ من إعدادات متجر التاجر عبر GET /v1/store (مخطط لـ v1.1).

مرّر تكلفة الشحن المحسوبة كـ shipping_cost. الخادم لا يُعيد حساب الشحن لك حاليًا.

الخطوة 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,
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: cart.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();
return res.status(201).json({
order_number: createdOrder.order_number,
total: createdOrder.amounts.total
});
}

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

الخطوة 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 في الاستجابة — احفظه. كل تسليم webhook يُرفق رأس X-DZ-Signature يجب أن تتحقق منه. انظر التحقق من التواقيع.

استخدم webhooks لـ:

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

أنماط شائعة

واجهة متعددة اللغات

احفظ ترجماتك في الواجهة. الـ API يُعيد أسماء المنتجات والأوصاف كما أدخلها التاجر تمامًا. إن استعمل التاجر إضافة Multi-language، سيُعيد GET /v1/products/{id} (في v1.1) name_ar و name_fr و description_ar و description_fr إلى جانب الحقول الأساسية. حتى ذلك الحين، الكنوني فقط مكشوف — اختر ما يريده عميلك من جانب العميل.

اختيار مكتب الاستلام (Stop-desk)

لـ delivery.type = "desk":

// 1. اقرأ مكاتب التاجر للولاية المختارة
// (مخطط v1.1: GET /v1/store/desks?wilaya=16)
// حتى ذلك الحين استعلم شركة التوصيل مباشرة (Yalidine, ZR…)

// 2. أعرض القائمة، يختار العميل:
selectedDesk = { id: 7842, name: "Yalidine Bab Ezzouar" };

// 3. مرّره للطلب:
order.delivery = {
type: "desk",
desk_id: selectedDesk.id,
desk_name: selectedDesk.name
};

الدفع الإلكتروني (SlickPay / Edahabia)

لـ payment_method = "digital_payment":

  1. أرسل الطلب عبر API كالعادة؛ يُنشأ بـ pending و payment_status = pending.
  2. وجّه العميل إلى رابط checkout في SlickPay / Edahabia.
  3. عند النجاح يستلم خادمك الخلفي webhook من SlickPay → نادِ PATCH /v1/orders/{id} لتحويل payment_status إلى paid (مخطط v1.1؛ حتى ذلك الحين يُحدَّث الدفع عبر مستقبل webhook لـ SlickPay الموجود في اللوحة).

الإرجاع والاسترداد

تُدار حاليًا من اللوحة. دعم API لـ POST /v1/orders/{id}/refund ضمن خارطة الطريق.

الأمان

  • لا تضع أبدًا مفتاح API في كود يصل إليه المتصفح. المفاتيح تنتمي لخادمك / serverless / edge worker. المتصفح ينادي خادمك وخادمك ينادي DZBuild.
  • HTTPS فقط بين واجهتك و api.dzbuild.app. الـ Worker يرفض HTTP.
  • Idempotency-Key مطلوب على كل الكتابات. POST بدونه = 400 idempotency_key_required.
  • حدود المعدل لكل مفتاح — pro 1500 طلب/دقيقة، pro_plus 5000، enterprise بلا حدود. انظر حدود المعدل.
  • مفتاح لكل بيئة. لا تشارك مفاتيح dev/prod. أنشئ مفتاحًا منفصلًا للمرحلة (staging) وألغه عند انتهائها.

استكشاف الأخطاء

المشكلةالسبب المرجَّحالإصلاح
401 unauthorized "Missing Authorization header"نسيت رأس Authorization: Bearer …أضفه
401 unauthorized "Invalid API key"خطأ كتابي، مفتاح ملغى، أو متغير بيئة خاطئأعد إنشاءه من اللوحة
403 forbidden "Missing scope: orders:write"المفتاح يفتقر للصلاحيةأعد إنشاءه بالصلاحية
400 bad_request "Product N does not belong to this store"معرّف من متجر آخراستعمل المفتاح الصحيح
400 bad_request "items must be a non-empty array"سلة فارغةلا تُرسل سلال فارغة
429 rate_limitedاستعلام مفرطاستبدل بـ webhooks؛ أو ارقَ الخطة

تطبيقات مرجعية

نُحافظ على أمثلة جاهزة للنسخ:

  • dzbuild-storefront-nextjs — Next.js 14 + App Router + Tailwind. عرض المنتجات، تفاصيل بمتغيرات، سلة، checkout.
  • dzbuild-storefront-flutter — تطبيق Flutter يسحب من نفس الـ API.

(كلاهما قالب بداية — تكيّف بحرية.)

الإطلاق

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

خارطة الطريق

الميزةETA
GET /v1/store بأسعار الولايات + مكاتب الاستلامv1.1
GET /v1/products/{id}/combinations للمخزون لكل تركيبةv1.1
POST /v1/orders/{id}/refund لاستردادات عبر APIv1.1
رفع صور المنتج عبر R2 presigned URLv1.1
حقول متعددة اللغات على استجابات المنتجاتv1.1

إن احتجت أيًا منها أبكر، تواصل مع الدعم.