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

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

ابنِ واجهة متجر مخصصة بالكامل بـ React أو Vue أو Next.js أو Flutter — مدعومة بواجهة DZBuild البرمجية.

بقلم: Support

تطلق 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) يحمل المفتاح. لا تشحن السر إلى المتصفح — انظر الأمان.

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

الخطوة 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 — عرض الكتالوج

// 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 — صفحة تفاصيل منتج بالمتغيرات

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 — بناء سلة

السلة من جانب العميل (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 — جمع بيانات العميل + احتساب الشحن

نموذج 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 — إرسال الطلب

// على خادمك الخلفي (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 (اختياري لكنه قوي)

سجّل 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 قبل أن تتصرّف بناءً على أي تسليم. انظر التحقق من التواقيع للوصفة والشيفرة.

وهناك حدّان يستحقان المعرفة قبل أن تبني على هذا: order.created في API v1 يُطلق فقط للطلبات التي تُنشئها واجهتك عبر الـ API (لا لأي طلب يُوضع في واجهة DZBuild الخاصة بالتاجر)، وأحداث الحالة تُطلق فقط لتغييرات الحالة التي تتم عبر الـ API. انظر كتالوج الأحداث.

استخدم webhooks لـ:

  • إرسال SMS للعميل عند تأكيد طلبه.

  • تحديث CRM / Google Sheet / تحليلاتك.

  • إبطال صفحة "thank you" بعد تأكيد التاجر.

  • إطلاق تسليم منتج رقمي بعد order.delivered.

أنماط شائعة

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

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

اختيار مكتب الاستلام (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)

لـ 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 سارية. ولاحظ أن هذه الحدود تخصّ نداءات الواجهة البرمجية وحدها؛ أما واجهة المتجر التي تبنيها فتخدم متسوّقيك دون أن تستهلك منها شيئًا. انظر حدود المعدل للسقوف الحالية.

  • مفتاح لكل بيئة. لا تشارك مفاتيح 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 عامًا

حقول متعددة اللغات على استجابات المنتجات

غير متاحة

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

هل أجاب هذا عن سؤالك؟