الثيمات والواجهات المخصصة
تطلق DZBuild 5 ثيمات (Brico, Digital, Prestige, Showcase, Starter) ومخصّصًا بدون كود. لكن إن أردت التحكم الكامل — واجهة React/Vue/Next.js/Flutter خاصة، تصميمك المثالي، التوجيه (routing) الخاص بك — فالواجهة البرمجية مبنية لك.
يمشي هذا الدليل عبر بناء واجهة headless من البداية للنهاية:
- عرض الكتالوج (المنتجات، الفئات، المتغيرات)
- بناء سلة (من جانب العميل أو الخادم، بحسبك)
- إرسال الطلب عبر API
- استقبال 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":
- أرسل الطلب عبر API كالعادة؛ يُنشأ بـ
pendingوpayment_status = pending. - وجّه العميل إلى رابط checkout في SlickPay / Edahabia.
- عند النجاح يستلم خادمك الخلفي 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. - حدود المعدل لكل مفتاح —
pro1500 طلب/دقيقة،pro_plus5000،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.
(كلاهما قالب بداية — تكيّف بحرية.)
الإطلاق
- اختبر جيدًا بمفتاح
pilotعلى متجر اختبار. - أنشئ مفتاحًا منفصلًا للإنتاج (نفس الصلاحيات، اسم مختلف).
- انشر واجهتك بمفتاح prod في متغيرات البيئة.
- ضع طلب اختبار حقيقي؛ تأكد من ظهوره في اللوحة.
- اختبر إرجاعًا إن قدّمت إرجاعات.
- سجّل webhooks تشير إلى رابط الإنتاج.
- راقب
GET /v1/usageيوميًا أول أسبوع.
خارطة الطريق
| الميزة | ETA |
|---|---|
GET /v1/store بأسعار الولايات + مكاتب الاستلام | v1.1 |
GET /v1/products/{id}/combinations للمخزون لكل تركيبة | v1.1 |
POST /v1/orders/{id}/refund لاستردادات عبر API | v1.1 |
| رفع صور المنتج عبر R2 presigned URL | v1.1 |
| حقول متعددة اللغات على استجابات المنتجات | v1.1 |
إن احتجت أيًا منها أبكر، تواصل مع الدعم.