الرئيسية / الأدلّة

للمطوّرين

تنفيذ UCP على PrestaShop 8/9: الدليل الكامل

واجهات التجارة لوكلاء الذكاء الاصطناعي قادمة. نشرت Google وShopify وWalmart معيار UCP (بروتوكول التجارة الموحّد، رخصة Apache 2.0)؛ وتدفع Anthropic معيار MCP. لم يعد السؤال لمتجر PrestaShop هل بل كيف يصبح مقروءًا من هؤلاء الوكلاء. إليك كيف فعلناها — قرارًا بقرار، بالكود الحقيقيّ من فُندُق.

القاعدة الذهبيّة: نفّذ المواصفة، لا تدوينة مدوّنة

UCP مواصفةٌ بإصدارات مؤرّخة (بتاريخ YYYY-MM-DD) مع JSON Schemas. نستهدف الإصدار المستقرّ 2026-04-08 ونتحقّق من كلّ استجابة مقابل المخطّطات المنشورة — لا نسخةً داخليّة. هذا ما يضمن أن يفهمنا وكيلٌ خارجيّ.

يعيش ملفّ الاكتشاف عند /.well-known/ucp. يقرأه الوكيل، فيجد نقطة الوصول والقدرات، ثم يستعلم المتجر — دون أيّ ترميزٍ يدويّ:

{
  "ucp": {
    "version": "2026-04-08",
    "services": {
      "dev.ucp.shopping": [
        { "version": "2026-04-08", "transport": "rest",
          "endpoint": "https://demo.fondouk.dev/fondouk/ucp",
          "schema": "https://ucp.dev/2026-04-08/services/shopping/rest.openapi.json" },
        { "version": "2026-04-08", "transport": "mcp",
          "endpoint": "https://demo.fondouk.dev/fondouk/ucp/mcp",
          "schema": "https://ucp.dev/2026-04-08/services/shopping/mcp.json" }
      ]
    },
    "capabilities": {
      "dev.ucp.shopping.catalog.search": [{ "version": "2026-04-08",
        "schema": "https://ucp.dev/2026-04-08/capabilities/shopping/catalog/search.json" }],
      "dev.ucp.shopping.catalog.lookup": [{ "version": "2026-04-08",
        "schema": "https://ucp.dev/2026-04-08/capabilities/shopping/catalog/lookup.json" }]
    },
    "payment_handlers": {}
  }
}

القرار رقم 1 — ORM/Presenter، لا الـ webservice

يُرجع الـ webservice /api في PrestaShop بياناتٍ خامًا: سعر أساس، بلا حسابٍ للسعر شامل الضريبة، بلا تطبيقٍ للخصومات، والمخزون في نداءٍ منفصل (N+1). إعادة إنتاج سعر الواجهة تفرض إعادة تنفيذ محرّك الأسعار — مصدرًا للاختلاف.

لذا نستخدم الأصناف الداخليّة (Product::getPriceStatic، StockAvailable، SpecificPrice). والتفصيل الأهمّ على الإطلاق أنّ السعر يُحسَب في سياق زائرٍ مجهول — وهذا هو نموذج الأمان:

// Does the shop display tax-included prices? (group/shop setting)
$displayTax = (Product::getTaxCalculationMethod() === PS_TAX_INC);

$price = Product::getPriceStatic(
    $idProduct,
    $displayTax,                 // tax-incl or tax-excl, per the shop's display setting
    $idProductAttribute,
    $decimals,
    null,
    false,
    /* usereduc */ true,
    1,
    false,
    /* id_customer */ 0,         // anonymous — no customer
    null,
    null,
    $specificPriceOutput,
    true,
    true,
    $context,
    /* use_customer_price */ false  // never a customer-specific price
);
// group = PS_UNIDENTIFIED_GROUP → public specific prices apply,
// B2B / group prices never do. What an anonymous visitor sees, nothing more.

هذا النداء وحده، بـ id_customer = 0 وuse_customer_price = false، هو سبب استحالة تسرّب سعر جملة عبر طلب وكيل.

القرار رقم 2 — توجيه /.well-known/ المتوافق مع PS8 وPS9

الواجهة الأماميّة لم تُهاجَر إلى Symfony لا في PS8 ولا في PS9 (الـ FrontKernel تجريبيّ). الآليّة القابلة للنقل هي الثنائي القديم ModuleFrontController + خطّاف moduleRoutes. وحده الملفّ التعريفي يحتاج مسار well-known؛ أمّا نقاط REST/MCP فتعيش تحت قاعدة الوحدة:

public function hookModuleRoutes()
{
    $mod = ['fc' => 'module', 'module' => 'fondouk'];
    return [
        'module-fondouk-ucp' => [
            'rule' => '.well-known/ucp', 'keywords' => [],
            'controller' => 'ucp', 'params' => $mod,
        ],
        'module-fondouk-catalog-search' => [
            'rule' => 'fondouk/ucp/catalog/search', 'keywords' => [],
            'controller' => 'catalog', 'params' => $mod + ['action' => 'search'],
        ],
        // … lookup, product, mcp, llms.txt
    ];
}

شرطٌ مسبق: إعادة كتابة الروابط. فخٌّ معروف: مجلّد .well-known/ فعليّ (يُنشئه Let's Encrypt) يختصر Apache عبر قاعدة -d في .htaccess. نكشفه ونُبلغ عنه؛ والارتداد إلى ملفٍّ ثابت اختياريّ.

المحور: نواةٌ مفصولة

القطعة الأهمّ ليست الوحدة، بل الـ Capability Graph: صيغةٌ محوريّة محايدة (JSON Schema بإصدارات مُرقَّمة)، بلا أيّ اعتمادٍ على PrestaShop. تغذّيها الوحدة بالاستبطان؛ وتُسقِطها المُحوّلات إلى UCP (REST، MCP). إليك شكل variant في الرسم — لاحظ أنّ prices مصفوفة (متعدّدة العملات) وأنّ quantity مُعلَّمة داخليّة، لا تُسلسَل أبدًا نحو وكيل:

"variant": {
  "required": ["id", "prices", "availability"],
  "properties": {
    "id":    { "type": "string" },
    "prices": { "type": "array", "items": { "required": ["price"],
      "properties": { "price": { "$ref": "#/$defs/money" }, "list_price": { "$ref": "#/$defs/money" } } } },
    "availability": { "required": ["available"], "properties": {
      "available": { "type": "boolean" }, "status": { "type": "string" },
      "quantity": { "type": "integer", "description": "Internal — NEVER exposed by adapters." } } }
  }
}

أيّ منصّة تعرف إنتاج هذا الرسم ترث مجّانًا كلّ المُحوّلات. هذا هو كلّ جوهر المحور.

ما الذي يحصل عليه الوكيل فعلًا

اكتشِف، ثم استعلِم — نقطة الوصول تأتي من الملفّ التعريفي:

curl -s -X POST https://demo.fondouk.dev/fondouk/ucp/catalog/search \
  -H 'Content-Type: application/json' \
  -d '{"query":"t-shirt","pagination":{"limit":1}}'
{
  "ucp": { "version": "2026-04-08",
    "capabilities": { "dev.ucp.shopping.catalog.search": [{ "version": "2026-04-08" }] } },
  "products": [{
    "id": "gid://demo.fondouk.dev/1/product/1",
    "title": "Hummingbird printed t-shirt",
    "price_range": { "min": { "amount": 2294, "currency": "EUR" },
                     "max": { "amount": 2294, "currency": "EUR" } },
    "variants": [{
      "id": "gid://demo.fondouk.dev/1/variant/1-1", "title": "S / White",
      "price": { "amount": 2294, "currency": "EUR" },
      "availability": { "available": true, "status": "in_stock" }
    }]
  }],
  "pagination": { "has_next_page": true, "cursor": "eyJvIjoxfQ", "total_count": 6 }
}

amount بوحداتٍ صغرى (2294 = 22.94 €)، والعملة صريحة — كما يفرض مخطّط UCP المنشور. الرسم نفسه يغذّي خادم MCP، فـ tools/call search_catalog يُرجع بيانات الكتالوج ذاتها في structuredContent الخاصّ به.

تسهيلٌ عبر GET للمتصفّحات الوكيلة. النقل القانونيّ هو POST، لكنّ وكيلًا من نوع المتصفّح لا يُصدر سوى طلبات GET يبلغ النتيجة ذاتها بوضع الاستعلام في الرابط — غير معياريّ، مُعلَنٌ باسم x-fondouk.get_convenience في الملفّ التعريفي:

curl -s 'https://demo.fondouk.dev/fondouk/ucp?capability=dev.ucp.shopping.catalog.search&query=t-shirt&limit=1'

الملفّ التعريفي وعدٌ

التفصيل الذي يفصل خادمًا صالحًا للاستخدام عن آخر مجرّد مطابق: لا تُعلن إلا عمّا يستجيب فعلًا. ما دامت نقطة وصولٍ قيد الإنشاء، لا تظهر قدرتها — services: {}، capabilities: {}. ملفٌّ فارغ خيرٌ من ملفٍّ كاذب: يوم اختبار وكيلٍ حقيقيّ، خطأ 501 على قدرةٍ مُعلَنة يكسر الثقة.

التحصين، لأنّ «الصمود أمام 50 وكيلًا» يُختبَر لا يُعلَن

تحديد معدّلٍ بأسلوب token-bucket‏ (60/دقيقة، دفعة 120) مع Retry-After، وتخزينٌ مؤقّت يُبطَل عبر خطافات المنتج، وحدودٌ لحجم جسم الطلب والدُّفعات (413/400). مُثبَتٌ تحت الزحف على PS8 وPS9: يبقى المتجر متجاوبًا.

باختصار

للقراءة فقط، بلا إعداد، العامّ يبقى عامًّا. الرؤية مجانيّة إلى الأبد؛ والباقي — الدفع (طبقة Fondouk Pro المدفوعة)، ومنصّاتٌ أخرى — في قائمة الانتظار. الكود مفتوح (MIT): github.com/fondouk-dev/fondouk.

المجانيّ والـ Pro يتعايشان. ثبّت Fondouk Pro إلى جانب الوحدة المجانيّة فتدخل هذه الأخيرة في وضع السكون بأدب — تتنازل عن مساراتها فلا يكون هناك ملفٌّ تعريفيّ مكرّر — بينما يتولّى الـ Pro الخدمة ويضيف لوحة التحكّم في الواجهة الخلفيّة. أزِل الـ Pro فتستأنف الوحدة المجانيّة الخدمة وحدها، دون إعادة تثبيت.

فريق فُندُق — Synapsea

→ كلّ الأدلّة

Fondouk Pro

الوحدة في هذا الدليل مجانية وستبقى مجانية. تضيف Fondouk Pro فوقها لوحة تحليلات للوكلاء — أيّ وكلاء الذكاء الاصطناعي يزورون متجرك، وأيّ المنتجات يشاهدون، والتحكّم لكلّ وكيل (سماح / تقييد / حظر)، وأدوات GDPR — على متجر PrestaShop Addons الرسمي.

احصل على Fondouk Pro من Addons   شاهد لوحة التحكّم ←