الانتقال إلى المحتوى
Micro Frontend

تضمين متجر التذاكر بشكل أصلي

يعرض Micro-Frontend متجر تذاكر ADITUS مباشرة داخل صفحتك، دون iframe. المواقع التي لا تملك backend تحمّل السكربت وتضبط public-key مع cart-scope="tab". أما المواقع التي تملك backend فتنشئ الجلسة على خادمها وتمرّر session token، ويحدد خادمك عندها هوية المشتري. بدون جلسة يبقى Micro-Frontend غير نشط.

جديد هنا؟ تعرض النظرة العامة ما يقدمه Micro Frontend دون تفاصيل تقنية.

البدء السريع

هناك طريقتان للوصول إلى متجر يعمل. تستخدم كلتاهما السكربت نفسه والعنصر نفسه، والفرق هو من ينشئ الجلسة.

بدون واجهة خلفية

ينشئ العنصر بنفسه جلسة متصفح لمفتاح publicKey الخاص بك، مقصورة على النطاقات المسموح بها. اضبط cart-scope="tab": بدون هوية خادم تنتهي القيمة الافتراضية "shared" بخطأ صريح. يحصل كل تبويب عندها على سلة خاصة به. الجلسة مجهولة الهوية: يتصفح الزوار ويملؤون السلة. قبل التسجيل يجب أن يعيد element.config.onUserRequired رمز جلسة مرتبطًا بمستخدم صادرًا من خادم؛ وبدونه يتوقف المتجر عند السلة مع إشعار.

index.html
<!-- Site without a backend: load the script once, place the element. -->
<script src="https://developers.aditus.com/api/embed/v1/aditus-shop.js" defer></script>

<aditus-shop
  public-key="pk_your_site"
  cart-scope="tab"
></aditus-shop>

مع واجهة خلفية

ينشئ خادمك الجلسة باستخدام mint secret ويحدد من هو المشتري. مرّر الرمز المُعاد إلى العنصر. مع البريد الإلكتروني للمشتري تحافظ القيمة الافتراضية cart-scope "shared" على سلة واحدة في جميع التبويبات.

server.js
// 1. On your server: create a session. The mint secret never leaves the server.
const res = await fetch("https://developers.aditus.com/api/shop/session", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ADITUS_MINT_SECRET}`,
    "Content-Type": "application/json",
  },
  // email comes from your own login session, never from the browser request.
  body: JSON.stringify({ publicKey: "pk_your_site", email: user.email }),
});
const { sessionToken } = await res.json();
index.html
<!-- 2. In your page: pass the token from your server to the element. -->
<script src="https://developers.aditus.com/api/embed/v1/aditus-shop.js" defer></script>

<aditus-shop
  public-key="pk_your_site"
  session-token="SESSION_TOKEN_FROM_YOUR_SERVER"
></aditus-shop>

استبدل pk_your_site بمفتاحك القابل للنشر من لوحة إدارة ADITUS. العنوان developers.aditus.com هو عنوان الإنتاج للسكربت وواجهة API. الطريقتان موصوفتان بالتفصيل أدناه.

التكنولوجيا وراء الواجهة الصغيرة

يعرض Micro-Frontend عملية الشراء كاملة (الفعاليات، المنتجات، السلة، التسجيل، الدفع، الإتمام) داخل DOM صفحتك. يستخدم تخطيط صفحتك، ويمكن لـ CSS الخاص بك تنسيق كل جزء منه.

React 19، متوافق مع React 18

يعمل المسار الكامل كتدفق React واحد آمن الأنواع. يُبنى ويُختبر باستخدام React 19 ويستخدم ميزات React 18 فقط (useState وuseEffect وuseMemo وما شابهها)، لذا تعمل حزمة المصدر مع React 18 أو أحدث (peerDependency react >=18). إلى جانب React لديها تبعية تشغيل واحدة: libphonenumber-js للتحقق من أرقام الهاتف. لا توجد مكتبة واجهة ولا إطار لجلب البيانات ولا أداة لإدارة الحالة. يتضمن السكربت الذي تستضيفه ADITUS مكتبة React مسبقًا، لذا لا تحتاج صفحتك إلى React خاص بها.

Light DOM بدلاً من iframe

لا iframe ولا Shadow DOM. يُعرض المتجر مباشرة في DOM صفحتك، ويندمج في تخطيطك، وهو متجاوب وسهل الوصول. تأتي الخطوط والألوان من رموز --aditus-*؛ والخط الافتراضي هو system-ui. اضبط --aditus-font لاستخدام خط صفحتك.

إطار حيادي: حامل واحد () في كل مكان (mount())

استدعاء واحد (mount(container, config)) يربط المتجر بأي عنصر DOM. سواء React أو Vue أو Angular أو HTML عادي: المحوّل مستقل عن الإطار ويعمل بالطريقة نفسها في كل مكان.

التصميم لك

يحمل كل عنصر خطاف CSS ثابتًا (فئات BEM aditus-shop__* إضافة إلى data-state). تنسّقه بـ CSS الخاص بك أو تضبط ألوان العلامة والزوايا والمسافات عبر بعض رموز التصميم --aditus-*. توجد الأنماط الأساسية الاختيارية في CSS @layer aditus-shop، لذا تتغلب قواعدك غير الموضوعة في طبقة دائمًا. ينطبق هذا أيضًا على القواعد العامة في موقعك مثل button {} أو input {} أو a {} من قالب WordPress أو Bootstrap: فهي تغيّر المتجر أيضًا. اقصر هذه القواعد على مناطق صفحتك الخاصة إذا كان المتجر لا ينبغي أن يتبناها.

متعدد اللغات، مع تبديل فوري

تم دمج DE/EN. يتم دفع مفتاح اللغة مباشرة إلى المثيل الجاري تشغيله عبر Handle.update() - دون إعادة تعيين رحلة المستخدم. يتم الحفاظ على العربة والتقدم.

آمن حسب التصميم

بدون جلسة صالحة يبقى المتجر غير نشط ولا يرسل أي طلبات إلى المتجر. تأتي الجلسة إما من خادمك (تُنشأ باستخدام mint secret الذي لا يصل إلى المتصفح أبدًا) أو، في المواقع التي لا تملك backend، من جلسة المتصفح المقيّدة بنطاق publicKey الخاص بك. تمر جميع استدعاءات المتجر عبر ADITUS API التي تضيف بيانات اعتماد المتجر على الخادم.

التنسيق على ثلاثة مستويات

استخدم المستوى الأكثر عمومية الذي يناسب الحالة: تنشئ رموز التصميم العلامة التجارية، وتجعل خطافات BEM الدلالية العامة الأدوار المتكررة متسقة، وتُحجز خطافات المكونات الحالية للتجاوزات المتخصصة.

الخطافات الدلالية العامة

.aditus-shop__section, .aditus-shop__panel, .aditus-shop__list, .aditus-shop__list-item, .aditus-shop__actions و .aditus-shop__action. تصف هذه الخطافات الإضافية الأدوار مع احتفاظ كل عنصر بخطافه المتخصص. يقترح تحليل الرابط ولقطة الشاشة في Styling Studio قواعد على هذا المستوى تحديدًا (إطار أم ظل، سُمك الأزرار، تباعد الأقسام)، وليس أبدًا على الخطافات الخاصة بالمكوّنات.

الإجراءات والاستثناءات

حيثما تكون مطبقة، توثق data-variant="primary" أو data-variant="secondary" دور الإجراء. لا تستنتج الأدوار الدلالية من تداخل DOM: لا يعد reg-form لوحة موثقة. استهدف خطافه المتخصص فقط عندما يحتاج هذا النموذج إلى استثناء مقصود.

shop-micro-frontend.css
/* 1. Tokens: the broad brand foundation. */
.onsite-shop .aditus-shop {
  --aditus-accent: #c2410c;
  --aditus-surface: #ffffff;
  --aditus-radius: 12px;
}

/* 2. General hooks: consistent roles throughout the journey. */
.onsite-shop .aditus-shop__section { margin-block: 2rem; }
.onsite-shop .aditus-shop__panel {
  background: var(--aditus-surface);
  border: 1px solid var(--aditus-border);
  border-radius: var(--aditus-radius);
  box-shadow: var(--aditus-shadow);
}
.onsite-shop .aditus-shop__list { gap: 0.75rem; }
.onsite-shop .aditus-shop__list-item { padding-block: 0.75rem; }
.onsite-shop .aditus-shop__actions { gap: 0.5rem; }
.onsite-shop .aditus-shop__action[data-variant="primary"] { font-weight: 700; }
.onsite-shop .aditus-shop__action[data-variant="secondary"] { box-shadow: none; }

/* 3. Expert override: only the named component changes. */
.onsite-shop .aditus-shop__article-card {
  border-inline-start: 4px solid var(--aditus-accent);
}

متطلبات موقعك

تعتمد المتطلبات على طريقة التسليم. يحتاج السكربت الذي تستضيفه ADITUS إلى متصفح حديث فقط؛ وتحتاج حزمة المصدر أيضًا إلى React 18+ في تطبيقك. ويحتاج كلاهما إلى جلسة لـ publicKey تتضمن قائمته البيضاء نطاقك.

ما هو مطلوب

  • لحزمة المصدر فقط: React 18+ وreact-dom 18+ في حزمة تطبيقك (peerDependency react >=18)؛ يستخدم mount() الدالة createRoot من react-dom/client. يجلب السكربت المستضاف React الخاص به.
  • متصفح مزود بـ DOM (جانب العميل). يتم تركيب المحول في عنصر DOM حقيقي (Light DOM). تعمل تطبيقات SSR جيدًا طالما أن mount() يعمل على العميل.
  • متصفح دائم الخضرة حاليًا - يستخدم واجهات برمجة تطبيقات الويب القياسية (fetch، AbortController، ResizeObserver، CSS@layer)، خط الأساس منذ عام 2022 تقريبًا. لا آي إي. (API)
  • جلسة لـ publicKey الخاص بك. المواقع التي لا تملك backend تضبط public-key مع cart-scope="tab"؛ ثم ينشئ العنصر جلسة المتصفح بنفسه عبر /shop/embed-session المقيّد بالنطاق. أما المواقع التي تملك backend فتنشئ الجلسة على خادمها باستخدام mint secret وتمرّر الرمز. بدون جلسة يبقى Micro-Frontend غير نشط. يجب أن يكون نطاقك ضمن القائمة البيضاء لـ publicKey.

ما ليس مطلوبًا

  • لا حاجة إلى إطار معين. يعمل mount(container, config) مع React أو Vue أو Angular أو HTML عادي.
  • لا يوجد إطار iframe، ولا يوجد ظل DOM - يتم عرضه أصلاً في صفحتك.
  • لا حاجة إلى CSS معين على موقعك. توجد الأنماط الأساسية في @layer aditus-shop بخصوصية فئة واحدة، لذا تتغلب أي قاعدة غير موضوعة في طبقة على موقعك. ويشمل ذلك قواعد العناصر العامة مثل button أو input أو a: فهي تصل أيضًا إلى عناصر التحكم في المتجر؛ قيّدها بمحتواك إن كان يجب ألا تنطبق هناك. كما أن box-sizing والحشوات والمسافات محمية إضافيًا من إعادة تعيين شاملة مثل * { margin:0; padding:0 }. يعطّل baseStyles: false الأنماط الأساسية.
  • لا حاجة إلى مكتبات أخرى على موقعك. تجلب حزمة المصدر، إلى جانب peerDependencies الخاصة بـ React، تبعية تشغيل واحدة (libphonenumber-js للتحقق من أرقام الهاتف)؛ ويحتوي السكربت المستضاف على كل شيء.
  • لا حاجة إلى إعداد بناء للسكربت المستضاف؛ تحتاج حزمة المصدر إلى استيراد ESM فقط.

موقعك موثوق: يصادق خادمك على المستخدم قبل إصدار جلسة معرَّفة. تُعرض الواجهة المضمّنة عمدًا في Light DOM لصفحتك، من دون عزل iframe. يمكن لبرامج صفحتك الوصول إلى DOM وتخزين المتصفح؛ لذا أمّن موقعك وبرامج الأطراف الثالثة فيه. تفويض المصدر بالمفتاح العام ليس مصادقة للمستخدم. تُدخل بيانات الدفع فقط في الصفحة الخارجية لمزوّد الدفع.

أمان الدفع، PCI وCSP

تُعرض خطوات التحضير للشراء في صفحتك. أما الدفع نفسه فيتم على صفحة مزوّد الدفع، لذا لا تمر أي بيانات دفع عبر صفحتك.

لا بيانات دفع في صفحتك

يتم عرض الإعداد الكامل للشراء - الحدث، والتذاكر، والإضافات، وعربة التسوق، والتسجيل - محليًا في صفحتك. في اللحظة التي يبدأ فيها المستخدم إعادة توجيه الدفع، يغادر التدفق الحساس صفحتك بالكامل: ينتقل التنقل العادي عالي المستوى إلى صفحة الدفع الخارجية المخصصة لموفر الدفع، وتستأنف رحلة العودة الرحلة وتكمل الطلب. يتم إدخال بيانات البطاقة أو البنك هناك فقط - لا تعرض الواجهة الأمامية الدقيقة بيانات اعتماد الدفع أو تنقلها أو تخزنها في صفحتك.

الحد الأدنى من نطاق PCI

نظرًا لعدم معالجة أي بيانات خاصة ببطاقة الائتمان أو البنك أو نقلها أو تخزينها في موقعك، تظل بيئتك في الحد الأدنى من نطاق PCI-DSS لنموذج إعادة التوجيه الكلاسيكي (عادةً SAQ A - يتم دائمًا إجراء التصنيف الملزم بواسطة الجهة المستحوذة أو QSA). طرق الدفع بدون إعادة توجيه، مثل الفاتورة، كاملة من جانب الخادم - مع عدم وجود بيانات دفع في المتصفح على الإطلاق.

Content Security Policy

إذا كان موقعك يضبط Content Security Policy، فأضف: script-src لأصل ADITUS (السكربت المستضاف)، وconnect-src لأصل ADITUS API (تمر عبره كل استدعاءات المتجر)، وimg-src لأصل ADITUS API (الأعلام وشعارات طرق الدفع وشارات المحفظة) ولمضيفي صور الفعاليات والمنتجات، وstyle-src 'unsafe-inline' لأن الأنماط الأساسية تُحقن كعنصر <style> (غير لازم مع baseStyles: false وورقة أنماط خاصة بك). لا حاجة إلى إدخالات script-src أو frame-src لمزوّدي الدفع، لأن خطوة الدفع إعادة توجيه على المستوى الأعلى.

مقارنة مع iframe وShadow DOM

iframe

الـ iframe مستند منفصل: لا تنطبق أنماط صفحتك داخله، ويجب مزامنة ارتفاعه، وله ترتيب تركيز خاص به. أما Micro-Frontend فيعرض التحضير للشراء كـ HTML في DOM صفحتك. لا يغادر صفحتك سوى الدفع، عبر إعادة توجيه على المستوى الأعلى إلى صفحة مزوّد الدفع.

Shadow DOM

تغلّف كثير من Web Components نفسها في Shadow DOM. عندها لا تصل إليها أنماط صفحتك إلا عبر الخصائص المخصصة و::part. أما Micro-Frontend فيستخدم تتالي CSS العادي: توجد أنماطه الأساسية في @layer aditus-shop، لذا تتغلب كل قاعدة غير موضوعة في طبقة في صفحتك دون خصوصية أعلى أو !important.

مقارنة النهج

معيارإطار iframe الكلاسيكيأداة Shadow-DOMالواجهة الأمامية الصغيرة لـ ADITUS
إمكانية الوصولمستند منفصل له ترتيب تركيز وبنية صفحة خاصة به.مراجع ARIA المعتمدة على المعرّف (aria-labelledby وaria-describedby) لا تعبر حدود Shadow DOM.HTML في DOM صفحتك: ترتيب تركيز واحد وبنية صفحة واحدة مع ترميزك الخاص.
التصميم والعلامات التجاريةجامد: لا يوجد وراثة CSS، والتخصيص فقط عبر واجهات برمجة تطبيقات postMessage. (API)الأنماط العالمية لا تصل إلى الداخل؛ السمات فقط عبر الخصائص والأجزاء التي تم تمريرها.يتغلب CSS غير الموضوع في طبقة على الأنماط الأساسية في @layer؛ ويمكن تجاوز الأنماط الأساسية أو إيقافها.
الحزمة والأداءيقوم عادةً بتحميل تطبيق ثانٍ كامل بما في ذلك وقت التشغيل الخاص به داخل الإطار.غالبًا ما يتم شحن وقت التشغيل الخاص به والتبعيات المكررة.السكربت المستضاف: حزمة واحدة تتضمن React. حزمة المصدر: تستخدم React الخاص بتطبيقك إضافة إلى تبعية تشغيل واحدة (libphonenumber-js).
أمان الدفع ونطاق PCIمعزولة بشدة - ولكن على حساب تجربة المستخدم والتصميم والاستجابة.يعتمد على الأداة: حقول البطاقة داخل الأداة جزء من صفحتك.الاختيار في صفحتك، والدفع على صفحة مزوّد الدفع.

توصيتنا: تحميل JavaScript مباشرة من رابط ADITUS

حمّل حزمة JavaScript مباشرة من ADITUS. هذا هو الخيار الذي نوصي به لجميع المواقع، بما فيها React وأطر العمل الأخرى، وليس فقط مواقع HTML أو CMS. تطوّر ADITUS منطق المتجر الداخلي مركزيًا، فتصل التحسينات المتوافقة إلى التكامل دون الحاجة إلى تنزيل حزمة جديدة أو إعادة بناء موقعك أو نشره.

عرض طريقة التكامل الموصى بها عبر السكربت /api/embed/v1/aditus-shop.js

كيف تصلك التحديثات

يوفّر رابط v1 إصدارات متوافقة بواجهات ثابتة. تُحمّل الإصدارات الجديدة عند فتح الصفحة لاحقًا بعد انتهاء ذاكرة HTTP المؤقتة، ومدتها حاليًا ساعة، أو إعادة التحقق منها؛ وليس بالضرورة عند كل إعادة تحميل. يحتفظ مسار الدفع المفتوح بالكود الذي حمّله.

البديل: إدارة التوزيع والتحديثات بنفسك

اختر حزمة المصدر أو تثبيت ملف tarball عبر npm أو استضافة نسخة JavaScript بنفسك فقط إذا أردت إدارة التوزيع بنفسك. لا تستطيع ADITUS تحديث نسختك. عليك متابعة الإصدارات وتنزيلها أو تثبيتها واختبارها وإعادة البناء عند الحاجة ثم نشرها. الحزمة غير منشورة في سجل npm العام.

بديل: حزمة المصدر (توزيع ذاتي)

بديلًا من ذلك، نزّل حزمة المصدر من مثيل ADITUS وادمجها في تطبيقك (Vite أو webpack أو Next.js أو Nuxt). تتولى توزيعها بنفسك: تتطلب التحديثات إعادة التثبيت والبناء والنشر. ليست على npm العامة.

ما يحتويه التسليم

المحول الكامل كمصدر TypeScript قابل للقراءة مع إعلانات النوع لكل خيار تكوين ورد اتصال وحدث. React وreact-dom يظلان تبعيات نظيرة؛ صفحتك توفرهما، لذلك لا شيء يتعارض في حزمتك ولا يوجد React آخر.

الحصول على الحزمة

الحزمة متطابقة لكل موقع ويتم تنزيلها مباشرة من هذه الحالة، دون الحاجة إلى بيانات اعتماد:aditus-shop-embed.tgz. ثبّتها من هذا الرابط مباشرةً (يدعم npm install روابط tarball، المسار /api/embed/v1/aditus-shop-embed.tgz) أو فكّها في مستودعك. يوفّر الإعداد publicKey وقائمة النطاقات المسموحة؛ تبقى الواجهة المضمّنة غير نشطة دون publicKey. التواصل: صفحة الاتصال. استخدم هذه الحزمة فقط إذا كنت توزّع البناء الخاص بك وتحدّثه بنفسك.

Stay current: the v1 track receives compatible updates in place. Check manifest.json regularly (before every release of your app, ideally as a scheduled job) and compare package.sourceHash with aditus.sourceHash in the installed package.json. If they differ, reinstall the tarball and run your build and tests; hooks, tokens and events stay stable within v1. The Web Component bundle updates itself through the one-hour HTTP cache.

التكامل عبر السكربت من رابط ADITUS

تعمل الحزمة المبنية مسبقًا على https://developers.aditus.com/api/embed/v1/aditus-shop.js في كل المواقع، بما فيها التطبيقات التي تحتاج إلى خطوة بناء وWordPress وTypo3 وHTML العادي. وسم script واحد يسجّل <aditus-shop> مع React مضمّنًا: لا حاجة إلى npm أو بناء خاص بك. لا يتطلب هذا الرابط العام ذو الإصدار أي بيانات اعتماد؛ ويظل المتجر يتطلب publicKey مسموحًا به وجلسة. لا يُستبدل الدفع الجاري أثناء التنفيذ أبدًا؛ وقد يؤخر تخزين المتصفح المؤقت الحزمة الجديدة حتى ساعة واحدة.

index.html
<!-- 1. Include the bundle hosted by ADITUS (once per page). -->
<script src="https://developers.aditus.com/api/embed/v1/aditus-shop.js" defer></script>

<!-- 2. Explicit tab isolation for anonymous sessions created automatically in the browser (not the default).
     For shared: session-token from your backend; anonymous ones also need a server-side sharedCartKey. -->
<aditus-shop
  public-key="pk_live_yourkey"
  api-base="https://developers.aditus.com/api"
  cart-scope="tab"
  culture="en-GB"
></aditus-shop>

<!-- 3. Optional: consume journey events as plain DOM events. -->
<script>
  document.querySelector("aditus-shop").addEventListener("cart:update", (e) => {
    console.log("Items in cart:", e.detail.itemCount);
  });
</script>

يسجّل رابط JS المستضاف نفسه <aditus-shop> تلقائيًا ويعرض window.AditusShop: mount وthemeToCss وclearJourney وmintEmbedSession وonEmbedSessionRenewed بتوقيعات حزمة المصدر نفسها. لا يحتاج موقعك إلى React. تقبل mount المستضافة وelement.config وحزمة المصدر AditusShopConfig نفسها بما فيها السمات وeventTheme: false وarticleContent والاستدعاءات. وحدها سمات HTML لا تنقل الدوال أو الكائنات. تبقى حزمة المصدر مناسبة لأدوات البناء ومشاركة بيئة React الحالية.

نطاق السلة والثقة والتخزين

JavaScript عادي: دورة رمز جلسة صريح

mount(container, config) هو mount المصدر نفسه: يبقى غير نشط دون sessionToken ولا ينشئ جلسة تلقائيًا. اضبط apiBase صراحة على رابط /api/shop الكامل. بعد تسجيل الدخول غير المتزامن ينشئ خادمك الجلسة بين الخوادم ويرسل الرمز فقط. host.renewSession وhost.revokeSession دوال ومسارات خاصة بك يجب تنفيذها في خادمك المصادَق عليه، وليست نقاط ADITUS جاهزة للمتصفح. لا ترسل الأسرار إلى المتصفح. يبقى onUserRequired تسجيل دخول غير متزامن مع إصدار S2S يعيد رمزًا أو null في كل خيارات JavaScript.

index.html / your own login
<div id="ticketshop-slot"></div>
<script src="https://developers.aditus.com/api/embed/v1/aditus-shop.js"></script>
<script>
  // Called after your async login and server-side session creation, with the returned sessionToken.
  // host.renewSession(staleToken): Promise<string | null>
  // host.revokeSession(token): Promise<void>
  // These are HOST-SPECIFIC backend functions, not built-in ADITUS routes.
  // Your backend authenticates the user; mint secrets never enter the browser.
  window.mountKnownSessionShop = function (sessionToken, host) {
    if (!sessionToken) throw new Error("A sessionToken from your backend is required");
    const slot = document.getElementById("ticketshop-slot");
    if (!slot) throw new Error("Missing mount container");
    let disposed = false;
    let config = {
      apiBase: "https://developers.aditus.com/api/shop",
      publicKey: "pk_your_site",
      sessionToken,
      cartScope: "shared", // default; trusted S2S identity required; immutable after mount
      culture: "en-GB",
      refreshSession: async (staleToken) => {
        const token = await host.renewSession(staleToken);
        if (disposed) return null;
        if (token) config = { ...config, sessionToken: token };
        return token; // Adapter swaps token and retries the failed request once.
      },
      onError: (error) => console.error("Ticketshop:", error.message),
    };
    const handle = window.AditusShop.mount(slot, config);
    return {
      updateCulture(culture) {
        if (disposed) return;
        config = { ...config, culture };
        handle.update(config); // full replacement, not a patch
      },
      async logout() {
        disposed = true;
        handle();
        window.AditusShop.clearJourney(); // local resume pointer only
        await host.revokeSession(config.sessionToken); // HOST-SPECIFIC S2S revoke
      },
      teardown() {
        disposed = true;
        handle(); // SPA route cleanup, not server-side revocation
      },
    };
  };
  // Your site supplies the real token and implementations:
  // const shop = window.mountKnownSessionShop(sessionToken, host);
  // Logout: await shop.logout(); route change: shop.teardown();
</script>

JavaScript عادي: إصدار المتصفح وتجديد الجلسة

لمفتاح publicKey مسموح، يعيد mintEmbedSession(publicKey, apiRoot = "/api", { fresh?, staleToken? }) القيمة Promise<string | null> دون سر. تستخدم الدوال وapi-base جذر /api، بينما تستخدم config.apiBase قاعدة /api/shop. يمرر onEmbedSessionRenewed(publicKey, apiRoot, listener) رمزًا نصيًا ويعيد دالة إلغاء اشتراك. يعالج المثال فشل الإصدار والانتهاء والتجديد الاستباقي والتنظيف. يلغي الاشتراك هذا المستمع فقط، لا الذاكرة المشتركة أو مؤقتات التجديد أو جلسات الخادم؛ تحفظ الدوال الاستمرارية عبر sessionStorage.

index.html / browser session
<div id="ticketshop-slot"></div>
<script src="https://developers.aditus.com/api/embed/v1/aditus-shop.js"></script>
<script>
  window.mountBrowserSessionShop = async function () {
    const { mount, mintEmbedSession, onEmbedSessionRenewed } = window.AditusShop;
    const publicKey = "pk_your_site"; // your whitelisted key, not a secret
    const apiRoot = "https://developers.aditus.com/api"; // no /shop for helpers
    const slot = document.getElementById("ticketshop-slot");
    if (!slot) throw new Error("Missing mount container");
    const sessionToken = await mintEmbedSession(publicKey, apiRoot);
    if (!sessionToken) throw new Error("Browser session failed: check key/domain");
    let disposed = false;
    let config = {
      apiBase: apiRoot + "/shop", // mount uses the shop base
      publicKey,
      sessionToken,
      cartScope: "tab", // explicit isolation opt-in: browser-created sessions cannot safely share
      culture: "en-GB",
      refreshSession: async (staleToken) => {
        const token = await mintEmbedSession(publicKey, apiRoot, {
          fresh: true, staleToken,
        });
        if (disposed) return null;
        if (token) config = { ...config, sessionToken: token };
        return token;
      },
      onError: (error) => console.error("Ticketshop:", error.message),
    };
    const handle = mount(slot, config);
    const offRenewed = onEmbedSessionRenewed(publicKey, apiRoot, (token) => {
      if (disposed) return;
      config = { ...config, sessionToken: token };
      handle.update(config); // preserve callbacks and all other options
    });
    return function teardown() {
      disposed = true;
      offRenewed();
      handle();
    };
  };
  // const teardown = await window.mountBrowserSessionShop();
  // Start now; your SPA calls window.teardownTicketshop() on route change.
  window.mountBrowserSessionShop().then((teardown) => {
    window.teardownTicketshop = teardown;
  }).catch((error) => {
    document.getElementById("ticketshop-slot").textContent = error.message;
  });
</script>

السمات وإعداد JavaScript الكامل

تقبل element.config كامل AditusShopConfig بما فيها الدوال. تتقدم القيم المحددة على سمات HTML حتى false؛ وتبقي المفاتيح المحذوفة أو undefined السمات فعالة. تستبدل كل عملية إسناد جميع التجاوزات وليست رقعة: element.config = {} تزيلها. يدعم الإسناد قبل ترقية العنصر. api-base هو جذر /api، أما config.apiBase فينتهي بـ /api/shop. تعيد themeToCss(theme, selector = ".aditus-shop") CSS للرموز بديلًا لكائن السمة.

index.html / element.config
<aditus-shop id="shop" public-key="pk_your_site"
  api-base="https://developers.aditus.com/api" cart-scope="tab" culture="de-DE"></aditus-shop>
<script>
  const element = document.getElementById("shop");
  // Supported before upgrade by the following hosted script, too.
  element.config = {
    apiBase: "https://developers.aditus.com/api/shop",
    culture: "en-GB", // overrides the HTML culture attribute
    cartScope: "tab", // explicit isolation opt-in for this anonymous browser-created example
    theme: { accent: "#c2410c", fontSize: "16px" },
    eventTheme: false,
    onComplete: (order) => console.log("Order completed:", order),
    onError: (error) => console.error("Ticketshop:", error.message),
  };
  // No sessionToken: effective publicKey enables browser session creation.
  // Optional refresh by your site: refreshSession: (staleToken) => host.renewSession(staleToken)
  // host.renewSession is HOST-SPECIFIC; your refresh always takes precedence.
  // Later: element.config = {}; removes overrides, attributes apply again.
  // Cleanup: element.remove();
</script>
<script src="https://developers.aditus.com/api/embed/v1/aditus-shop.js" defer></script>
يصفمعنى
cart-scopeالخيار AditusShopConfig.cartScope?: 'tab' | 'shared' افتراضيًا 'shared' حتى عند حذفه. 'tab' اختيار صريح لعزل الرحلات. تقبل window.AditusShop.mount() المستضافة مركزيًا وelement.config وmount() في حزمة المصدر/إطار العمل الخيار نفسه؛ وفي HTML استخدم cart-scope="tab" أو cart-scope="shared". اختر قبل التركيب. النطاق ثابت لنسخة checkout المركّبة: يرفض handle.update() تغييره بدل ترحيل سلة نشطة. احتفظ به عند استبدال التهيئة بالكامل.
public-keyمفتاحك القابل للنشر (تنطبق القائمة البيضاء للنطاقات). بدون session-token ينشئ العنصر جلسة المتصفح بنفسه. تستخدم المواقع التي لا تملك واجهة خلفية هذا الوضع وتضبط أيضًا cart-scope="tab"؛ مع القيمة الافتراضية "shared" تنتهي جلسة المتصفح بخطأ صريح لعدم وجود هوية خادم موثوقة.
session-tokenرمز جلسة أنشأته الواجهة الخلفية لديك باستخدام mint secret (انظر أدناه). عند تعيينه لا ينشئ العنصر جلسة بنفسه أبدًا، وتدير صفحتك دورة حياة الجلسة.
api-baseعنوان URL الأساسي لواجهة برمجة تطبيقات ADITUS. الإعدادات الافتراضية للأصل الذي تم تحميل البرنامج النصي للحزمة منه - عادةً لا تقوم بتعيين هذا مطلقًا. (API)
cultureتُمرَّر culture/locale المتجر إلى ADITUS لاختيار الموارد، وتُمرَّر القيم الصالحة مثل it-IT. يبقى نص موارد voucher وupsell كما تقدمه ADITUS؛ وتستخدم بقية الواجهة موارد ألمانية/إنجليزية مع الرجوع بينهما. هذا لا يعني توفر واجهة كاملة بالإيطالية. يؤدي تغييرها إلى تحديث اللغة دون إعادة ضبط الرحلة.
article-layout / article-group-layout / event-layout / article-select-mode / show-headerخيارات العرض المسطحة، نفس قيم تكوين mount() (البطاقات، الكمية، ...). نقطة الإدخال (منتقي الأحداث مقابل الحدث المثبت) ليست سمة: فهي ثابتة من جانب الخادم عند إنشاء الجلسة.
include-shop-assortmentsدمج اختياري للتشكيلات؛ يقابله includeShopAssortments في mount(). الافتراضي false: تحميل التشكيلة الافتراضية فقط. عند true تُضاف التشكيلات ذات showInShop=true. لتشكيلة البداية المحددة صراحةً الأولوية دائمًا. عند غياب الخاصية يُستخدم إعداد العميل؛ وتتجاوزه القيمة false الصريحة.
survey-columnsأعمدة خيارات الإجابة (1–4) لأسئلة استطلاع RadioButtonList/CheckBoxList. تقابل surveyColumns في تكوين mount(). يؤدي عدم التعيين إلى الاحتفاظ بالعمود الفردي الافتراضي (أو جانب الخادم الافتراضي المخزن للعميل)؛ تنهار إطارات العرض الضيقة مرة أخرى إلى عمود واحد.
hide-house-numberيخفي حقل رقم المنزل في كتلة العنوان في الاستبيانات لكل نسخة بلد؛ يقابله hideHouseNumber في إعداد mount(). يُدخل الشارع ورقم المنزل معًا في حقل واحد باسم "الشارع ورقم المنزل" يأخذ العرض المتاح؛ تكتب اقتراحات العناوين الشارع ورقم المنزل فيه بالترتيب المعتاد في البلد (مثل "12 Rue Cler" في فرنسا)، ويُفرَّغ رقم المنزل المعبأ مسبقًا، وتُرسل إجابة رقم المنزل المنفصلة دائمًا كمسافة غير مرئية بعرض صفري (U+200B) دون خطأ تحقق حتى إذا جعلها الاستبيان إلزامية. يتبع نموذج عنوان الفوترة القاعدة نفسها. معطّل افتراضيًا؛ عند حذف السمة تُطبق القيمة الافتراضية للعميل، و"false" الصريح يتجاوزها.
show-jump-buttonزر انتقال في خطوة التخصيص؛ يقابله showJumpButton في إعداد mount(). مفعّل افتراضيًا: بعد فشل الإرسال يظهر زر صغير مثبّت أسفل الواجهة يعرض عدد البيانات المتبقية وينتقل إلى التالية؛ وعند اكتمال كل شيء يعيد إلى الزر دون أن يرسله أبدًا. يختفي ما دام ذلك الزر أو الهدف ظاهرًا وأثناء الكتابة. القيمة "false" توقفه؛ وعند غياب السمة يُطبَّق الإعداد الافتراضي للعميل.
voucher-code-paramاسم معامل استعلام في عنوان URL لصفحتك يحمل رمز قسيمة/تذكرة (مثل "code" لروابط مثل …/tickets?code=ED24). يقابل voucherCodeParam في تكوين mount()‎. يقرأه العنصر مرة واحدة عند التركيب، ويزيله من شريط العنوان ويسترد الرمز تلقائيًا (نموذج القسيمة أو أول سلة). فقط للجلسات المثبّتة على فعالية.
base-stylesاضبط على "خطأ" لإسقاط المظهر الافتراضي المحايد وتصميم كل خطاف بنفسك.
autoScroll / articleIconVisible / classPrefixلا توجد سمة: اضبط هذه الخيارات عبر element.config (بالأسماء نفسها كما في إعداد mount()). يعيد autoScroll (الافتراضي true) بداية المتجر إلى مجال الرؤية بعد تغيير الخطوة؛ اضبطه على false إذا كانت صفحتك تدير التمرير بنفسها. يعرض articleIconVisible (الافتراضي false) صورة أو حروفًا أولى لكل مقالة في عرض القائمة. يغيّر classPrefix (الافتراضي "aditus-shop") بادئة جميع خطافات CSS.

الجلسة والشفاء الذاتي

يوقف sessionToken الفعلي من الإعداد أو السمات إصدار المتصفح التلقائي. مع publicKey فعلي دون رمز، يبقى الإصدار التلقائي. تتقدم refreshSession التي يوفرها موقعك دائمًا على التجديد المدمج. بدونها تستخدم الجلسات التلقائية الدالة المشتركة للتجديد والاستباق. يبقى Light DOM كما هو دون iframe أو shadow DOM.

الأحداث والإصدارات

تتصاعد DOM CustomEvents من العنصر بحمولة event.detail: cart:update وphase:change وcheckout:complete وanalytics بالأسماء والحمولات نفسها في handle.on(). تتوفر استدعاءات الإعداد أيضًا.

إعادة ضبط الرحلة عمدا

تزيل element.clearJourney() وwindow.AditusShop.clearJourney() مؤشر الاستئناف المحلي لهذه الصفحة فقط. استدعها قبل إعادة إدراج العنصر أو تركيبه لبداية جديدة. لا تحذف سلال الخادم ولا تلغي الجلسات؛ إلغاء الجلسة عند الخروج من مسؤولية الخادم.

التحديثات دون إعادة تحميل

إعدادات mount/update بدائل كاملة لا رقع: احتفظ بالإعداد الكامل ومرر { ...config, culture } إلى handle.update(). تستبدل element.config جميع التجاوزات أيضًا وتحدّث السمات غير المحجوبة مباشرة. يحفظ العرض الرحلة ويعيد تغيير الهوية إنشاء الجلسة. تفك handle() التركيب ويزيل حذف العنصر مستمعيه الداخليين والتجديد. أزل مستمعي DOM الذين سجلتهم بنفسك إذا احتفظت بالعنصر.

تطبيقات SSR: Next.js وNuxt

الواجهة الأمامية الصغيرة مخصصة للعميل فقط. محتواه مباشر ومقيد بالجلسة - التوفر والأسعار وسلة تسوق المستخدم موجودة فقط لجلسة مصادق عليها في وقت الطلب، لذلك لا يوجد شيء ذو معنى يمكن لخادمك عرضه مسبقًا. ينشئ mount() جذر React جديد من جانب العميل (createRoot)؛ لا يتم ترطيب العلامات المقدمة من الخادم داخل الحاوية. وهذا يجعل تكامل SSR بسيطًا ويمكن التنبؤ به: قم بعرض عنصر نائب على الخادم، وتثبيته على العميل، وحجز المساحة حتى لا يقفز أي شيء.

ticketshop-section.tsx
// Next.js (App Router): the micro-frontend is deliberately client-only.
"use client";
import { useEffect, useRef } from "react";
import { mount, type ShopHandle } from "@aditus/shop-embed";

export function TicketshopSection({ sessionToken }: { sessionToken: string }) {
  const el = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!el.current) return;
    const handle: ShopHandle = mount(el.current, {
      publicKey: "pk_your_site",
      sessionToken, // issued server-side, passed to the client component as a prop
      cartScope: "shared", // the default in frameworks too; trusted server identity
    });
    return () => handle(); // unmount on route change
  }, [sessionToken]);

  // Server and client render the SAME placeholder and so avoid a hydration mismatch.
  // min-height reserves the space so the page does not jump on mount (CLS).
  return <div id="aditus-shop" ref={el} style={{ minHeight: 560 }} />;
}

تجنب تغيير التخطيط (CLS)

امنح حاوية التثبيت الحد الأدنى من الارتفاع الذي يتطابق تقريبًا مع عرض المتجر الأول، وقم بشكل اختياري بعرض الهيكل العظمي الخاص بك داخلها - ينتج الخادم والعميل نفس العنصر النائب، لذلك لا يوجد عدم تطابق في الترطيب. يتم عرض المحول مباشرة بعد التثبيت ويستبدل العنصر النائب في طلاء واحد. لا تحاول استخدام hydroot على الحاوية: لا توجد شجرة متجر مقدمة من الخادم للإرفاق بها.

الجلسة في تطبيق SSR

تتمتع تطبيقات SSR بميزة طبيعية: يمكن لوقت تشغيل الخادم الذي يعرض الصفحة أيضًا إنشاء الجلسة - من خادم إلى خادم مع السر، تمامًا مثل تدفق إنشاء الجلسة أدناه (Next.js: Route Handler أو Server Component؛ Nuxt: مسار الخادم). قم بتسليم الرمز غير الشفاف الناتج إلى مكون العميل كدعم أو حمولة. لا يظهر السر مطلقًا في رمز العميل، ويظل الرمز المميز في الذاكرة.

تفاصيل Next.js

قم بتمييز مكون التثبيت "استخدام العميل" وتثبيته في useEffect (انظر المقتطف). باستخدام جهاز توجيه الصفحات، يحقق التالي/الديناميكي مع ssr: false نفس الشيء. قم بإرجاع المقبض من التأثير كتنظيف بحيث يتم إلغاء تحميل المسار من جانب العميل بشكل نظيف. يمر مفتاح الثقافة الموجود على المثيل قيد التشغيل عبر Handle.update() دون إعادة تعيين الرحلة؛ تتم إعادة تحميل رمز جلسة جديد (كما في المقتطف) - وتنجو عربة التسوق من ذلك أيضًا، لأن الواجهة الأمامية الصغيرة تستعيد الرحلة على الحامل.

تفاصيل Nuxt / Vue

قم بلف هدف التثبيت في <ClientOnly> أو قم بتثبيته في onMounted. يحتاج المحول إلى عنصر DOM حقيقي في المتصفح. تذكر متطلبات الحزمة: يجب تثبيت React و React-dom 18+ كتبعيات لتطبيق Nuxt الخاص بك؛ يتم عرض المتجر في حاويته الخاصة ولا يتداخل مع DOM الافتراضي لـ Vue.

نطاق السلة والثقة والتخزين

يحدد cartScope ما إذا كانت علامات تبويب الزائر تتشارك سلة واحدة. القيمة الافتراضية هي 'shared'؛ أما 'tab' فيفصل علامات التبويب عمدًا. اختره قبل التركيب، إذ لا يمكن تغييره بعد ذلك.

الجانب'shared' (افتراضي)'tab'
السلةسلة واحدة لكل علامات التبويب ذات الأصل نفسه وpublicKey نفسه والهوية نفسها والفعالية نفسهالكل علامة تبويب سلتها الخاصة
الجلسةتصدرها الواجهة الخلفية لديك: مستخدم معرّف، أو مجهول مع sharedCartKey من جلسة الخادم الخاصة بك. لا تأخذ الهوية أو المفتاح من المتصفح أبدًا.أي جلسة، بما في ذلك الإصدار من المتصفح
عدة علامات تبويبتحرر علامة تبويب واحدة، والبقية للقراءة فقط وتعرض «متابعة التحرير هنا». تتولى علامة التبويب المفتوحة أو المُعاد تحميلها تلقائيًا، ولا يحدث ذلك أثناء الدفع أبدًا.مستقلة
عند غياب الشرطخطأ صريح، حتى عند حذف cartScope؛ ولا تحويل صامت إلى 'tab'. لذلك تضبط الصفحات التي لا تملك واجهة خلفية القيمة 'tab'.–
  • الإعداد: cartScope في mount() وelement.config، وcart-scope في HTML. يرفض handle.update() أي تغيير، لذا احتفظ بالقيمة في كل إعداد بديل. السمات · حقول الإصدار
  • التخزين: تُحفظ مسودات النماذج لكل علامة تبويب في sessionStorage وتنتهي صلاحيتها بعد ساعتين من آخر تعديل. لا تُكتب الرموز في localStorage أبدًا. لا تتأثر الطلبات والتسجيلات المؤكدة.
  • تسجيل الخروج: عند تسجيل الخروج أو تغيير المستخدم، تلغي الواجهة الخلفية لديك الجلسة القديمة؛ ثم حدّث الرمز أو أزل التضمين واستدعِ clearJourney(). لا يرث المستخدم الجديد سلة المستخدم السابق أبدًا.
  • الأمان: يُعرض التضمين مباشرة في صفحتك دون iframe. يمكن للبرامج النصية في صفحتك، بما فيها برامج الجهات الخارجية، الوصول إليه؛ لذا لا تُدرج إلا برامج موثوقة. تُدخل بيانات الدفع لدى مزود الدفع فقط.
shared-cart.js
// Load the recommended centrally hosted aditus-shop.js first.
// sessionToken comes from your trusted backend's server-side session, not a browser-created one.
// Identified user: backend authenticates user and derives email/externalUserId.
// Anonymous visitor: backend omits email and MUST derive sharedCartKey
// from its own trusted visitor session. Neither identity nor key is browser input.
const slot = document.getElementById("ticketshop-slot");
if (!slot) throw new Error("Missing mount container");
const config = {
  apiBase: "https://developers.aditus.com/api/shop",
  publicKey: "pk_your_site",
  sessionToken,
  cartScope: "shared",
};
const handle = window.AditusShop.mount(slot, config);
// Full replacement config retains the chosen scope:
handle.update({ ...config, culture: "en-GB" });
// Do not change cartScope on this handle. Route teardown: handle();

تحتاج الواجهة الصغيرة إلى جلسة

لا رمز

لا يوجد رمز مميز للجلسة: تظل الواجهة الأمامية الصغيرة غير نشطة

بدون رمز مميز للجلسة، تعرض الواجهة الأمامية الصغيرة إشعارًا محايدًا ولا تبدأ أي استدعاءات لواجهة برمجة التطبيقات (API) على الإطلاق. لا يمكن ملء عربة التسوق، ولا تسجيل، ولا الخروج. تكون الجلسة إلزامية قبل تنشيط المتجر - ولكن ليس من الضروري أن تكون مرتبطة بمستخدم معروف: جلسة الضيف كافية لتصفح عربة التسوق وملؤها، ويمكن تحديد المستخدم الحقيقي لاحقًا عند الخروج (انظر تدفق المستخدم الاختياري أدناه).

mount.ts
import { mount } from "@aditus/shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount target #aditus-shop not found");

// Without a sessionToken the micro-frontend stays inactive: it only shows a
// neutral notice and starts no API calls: no cart, no registration,
// no checkout. Only a session activates the shop.
mount(el, {
  publicKey: "pk_your_site",
  // sessionToken missing -> the shop is not activated.
});
مستخدم موقعك

رمز الجلسة - مستخدم محدد

تقوم الواجهة الخلفية الخاصة بك بإصدار رمز مميز قصير العمر مرتبط بمستخدم واحد. تعمل الواجهة الأمامية المصغرة بعد ذلك كهذا المستخدم: عربة التسوق الخاصة به وبياناته. الرمز غير شفاف، ومنتهي الصلاحية، ومستخدم واحد ومرتبط بمفتاحك العام - ولا يصل سرك إلى المتصفح أبدًا. (publicKey)

mount.ts
import {
  mount,
  type ShopHandle,
  type AditusShopConfig,
} from "@aditus/shop-embed";

// getElementById can return null. Check it properly in TypeScript instead of using "!".
const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount target #aditus-shop not found");

// AditusShopConfig types every option and gives you autocompletion.
const config: AditusShopConfig = {
  apiBase: "https://developers.aditus.com/api/shop",
  publicKey: "pk_your_site",   // required as soon as a session is involved
  sessionToken,                // issued by your backend, keep it in memory only
  cartScope: "shared",          // default; requires a trusted server identity
};

const handle: ShopHandle = mount(el, config);

// The micro-frontend sends the X-Aditus-Session header with every shop call.
// On logout, revoke the session on the server and remount/update.

عند إعادة التحميل يتيح مؤشر رحلة صالح طلب السلة من الخادم ضمن النطاق المفوض والهوية الموثوقة نفسيهما. ليس تفويضًا ولا ضمانًا للاستعادة بعد الانتهاء أو مسح التخزين أو تغيير المستخدم. لا يدمج tab رحلات التبويبات الأخرى؛ ويحافظ shared على قفل checkout مع كاتب واحد.

تستخدم عودة الدفع aditus-return (رمز عودة مبهم وقصير العمر مرتبط بالخادم) مع aditus-pay. يمكن العودة في التبويب نفسه أو تبويب جديد، لكن الخادم يتطلب عميلًا/أصلًا مفوضًا والهوية الموثوقة المطابقة. معاملات العودة ليست دليل شراء. يتحقق الخادم من حالة الدفع وينهي المحاولة المرتبطة بالرمز؛ تبقى النتيجة المجهولة محظورة للتسوية، دون إعادة عمياء كشراء جديد. العودة المنتهية أو غير المفوضة خطأ صريح، وليست إذنًا بإنشاء طلب بديل.

تعيين مزود الدفع يجهّز الدفع فقط: يبقى الملخص قابلًا للتعديل. يبدأ القفل عند إعادة التوجيه الفعلية بعد الموافقات المطلوبة. معامل العودة cancel/error ليس دليل فشل: يعيد الخادم فحص السلة؛ تؤكد Completed مع order.id اكتمالها. لا تُسمح إعادة المحاولة إلا بعد تأكيد إزالة تعيين المزود بنجاح للسلة نفسها؛ تبقى النتائج المبهمة محظورة ويمكن إعادة فحصها. لا يضمن ذلك استرداد المال أو يستبعد تسوية متأخرة.

الدفع بإعادة التوجيه: ما الذي يجب أن تفعله صفحتك للجلسة

  • قدّم رابط العودة المُهيأ مع تركيب المكوّن. احتفظ بـ aditus-return وaditus-pay عبر التوجيه أو إعادة توجيه تسجيل الدخول اللازمة؛ لا تعتبر aditus-pay=success تفويضًا أبدًا. وفّر جلسة صالحة للهوية الموثوقة وpublicKey نفسيهما. احتفظ بـ cartScope ولا تمسح الرحلة أو تبدأ checkout ثانيًا لتجاوز عودة معلقة أو منتهية أو غير مؤكدة.

هل تريد بداية جديدة متعمدة بدلاً من ذلك - زر حقيقي "للبدء من جديد"؟ تقوم الحزمة بتصدير ClearJourney(): اتصل بها قبل التثبيت (أو إعادة التثبيت) وسيتم إسقاط سجل الرحلة المستمر محليًا للصفحة الحالية، لذلك يبدأ التثبيت التالي من جديد عند نقطة الإدخال. تتم إزالة مؤشر السيرة الذاتية المحلي فقط؛ لم يتم تغيير أي حالة عربة تسوق من جانب الخادم.

نقطة البداية: الصفحة الرئيسية للفعالية أو التذاكر أو القسيمة

يحدد خادمك نقطة بداية الرحلة عند إنشاء الجلسة، وليس المتصفح. بدون eventSlug تظهر قائمة الفعاليات. ومع eventSlug اختر قائمة المقالات (الافتراضية) أو الصفحة الرئيسية للفعالية أو نموذج القسيمة عبر startMode؛ ويقيّد assortment قائمة المقالات.

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

قد يكون لكل مجموعة تذاكر ظاهرة في المتجر مدخل مباشر. إذا حُدّدت مجموعة واحدة في الجلسة، تعرض الصفحة الرئيسية إجراء التذاكر الخاص بها فقط، لا القائمة الكاملة ولا خيار القسيمة.

يفتح startMode: "home" الصفحة الرئيسية للفعالية. يختار الزائر شراء التذاكر أو استرداد القسيمة؛ ولا يؤدي فتح الصفحة وحده إلى إنشاء سلة أو استرداد رمز. يفتح شراء التذاكر قائمة المقالات (وفق قيد assortment إن وُجد)، ويفتح استرداد القسيمة نموذج الرمز. بدون eventSlug تظهر قائمة الفعاليات أولاً، ثم صفحة الفعالية المختارة. تتقدم السلة المُعدّة لقسيمة أو الرحلة القابلة للاستئناف على البداية الجديدة.

Default

Article start

eventSlug alone: the micro-frontend opens the article selection of that event with all assortments shown in the shop.

assortment

One assortment only

assortment restricts the article start to one assortment of the event: pass its id or exact name (case-insensitive). Hidden assortments are allowed, which makes it the way to expose e.g. free press tickets on a dedicated page. A selector that matches nothing shows an explicit error instead of silently listing everything.

startMode: "voucher"

استرداد قسيمة / تسجيل تذكرة

تبدأ الرحلة بنموذج رمز بدلًا من قائمة المقالات. يحجز الرمز الصالح تذكرته. بعد نجاح الاسترداد، تحدد configuration.redirectAfterRegistration في إعدادات الحدث لدى ADITUS ما إذا كان المتسوق يبقى في شاشة النجاح أو يُوجّه تلقائيًا. تتبع المتابعة المسار نفسه القائم على التوصيات لاختيار المقالات، مع فحوص تسجيل الدخول والجلسة المعتادة قبل تخصيص التذاكر. يحافظ الرجوع على الحجز. تعيد «إضافة المزيد من القسائم» فتح إدخال الرمز في شاشة القسيمة للسلة نفسها؛ ويفتح «اختيار التذاكر» اختيار المقالات. يبقى الرمز المرفوض في النموذج مع السبب. تتجاوز التذكرة المغطاة بالكامل الدفع.

ينقل الرمز الترويجي المتسوق إلى اختيار المقالات بأسعار السلة ويسرد كل سعر تغيّر. يبقى التنبيه في التذييل بجوار حقل الرمز القابل للاستخدام: يمكن تطبيق رموز ترويجية أخرى على السلة نفسها، واسترداد قسيمة مع الكميات المختارة، وإزالة كل رمز ترويجي على حدة.

Where to set it

Server session or client record

Sessions created server-to-server pass startMode / assortment in the body next to eventSlug. Sessions created in the browser (publishable key only) take all three from the client record your ADITUS contact configures; a body value is ignored there. Only assortment requires eventSlug (400 start_option_requires_event_slug); startMode also works with the event picker; unknown values give 400 invalid_start_mode / invalid_assortment. GET /shop/session-context echoes the resolved startMode and assortment.

mint-entry.ts
// Server side: use the same session route as above, only the body varies.
// You build user and your backend's login/session route yourself.
// eventSlug sets the event. startMode also works without a slug (event
// selection first, then the mode); only assortment needs the slug
// (otherwise 400 start_option_requires_event_slug).

// 1) Article start (default): the event's article selection
const bodyArticles = { publicKey, email: user.email, eventSlug: "ExperienceDaysv52024" };

// 2) Assortment start: only one assortment (id or exact name, case-insensitive)
const bodyAssortment = {
  publicKey, email: user.email,
  eventSlug: "ExperienceDaysv52024",
  assortment: "Pressetickets",
};

// 3) Voucher start: "Redeem voucher / register ticket" first
const bodyVoucher = {
  publicKey, email: user.email,
  eventSlug: "ExperienceDaysv52024",
  startMode: "voucher" as const,
};

const res = await fetch("https://developers.aditus.com/api/shop/session", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.ADITUS_MINT_SECRET}`, "Content-Type": "application/json" },
  body: JSON.stringify(bodyVoucher),
});
const { sessionToken }: { sessionToken: string } = await res.json();
voucher-hooks.css
/* Voucher form: the same tokens and role hooks as the rest of the journey */
.aditus-shop__voucher-panel  { /* panel around hints + form (general panel hook) */ }
.aditus-shop__voucher-hints  { /* the two hint lines */ }
.aditus-shop__voucher-form   { /* form */ }
.aditus-shop__voucher-input  { /* code field (also .aditus-shop__field-input) */ }
.aditus-shop__voucher-redeem { /* "Redeem" button (data-variant="primary") */ }
.aditus-shop__field-error    { /* rejection reason, role="alert" */ }
/* Event home page and event actions: structured expert hooks */
.aditus-shop__home             { /* home page; root data-step="home" */ }
.aditus-shop__home-info        { /* event information */ }
.aditus-shop__home-info-head   { /* heading and logo */ }
.aditus-shop__home-info-name   { /* event name */ }
.aditus-shop__home-info-date   { /* date */ }
.aditus-shop__home-info-venue  { /* venue */ }
.aditus-shop__home-info-desc   { /* description */ }
.aditus-shop__home-actions[data-variant="tiles"] { /* tiles, alternatively compact */ }
.aditus-shop__home-action[data-kind="articles"] { /* buy tickets; also home / voucher / assortment */ }
.aditus-shop__home-action-icon    { /* icon */ }
.aditus-shop__home-action-body    { /* text group */ }
.aditus-shop__home-action-title   { /* label */ }
.aditus-shop__home-action-desc    { /* optional description */ }
.aditus-shop__home-action-chevron { /* arrow */ }
/* Phase hook: [data-step="voucher"] on the root */

غالبًا ما تُرسل القسائم كروابط إلى صفحتك مع الرمز كمعامل استعلام، مثل …/tickets?code=ED24-ADITUS. يمكن للواجهة المصغّرة تولّي هذا الرمز واسترداده دون أن يكتب الزائر شيئًا. يوجد مساران للتسليم؛ كلاهما ينتهي بنفس استدعاء الاسترداد ويتصرفان بعد ذلك بشكل متطابق.

voucherCodeParam: "code"

أ) في المتصفح: تقرأ الواجهة المصغّرة عنوان URL

حدّد اسم المعامل في تكوين mount()‎ (voucherCodeParam) أو على العنصر (voucher-code-param). عند التركيب يقرأه المحوّل مرة واحدة، ويزيله من شريط العنوان عبر history.replaceState (بدون إعادة تحميل، ويبقى كل شيء آخر في العنوان) ثم يسترد الرمز. لذلك لا تؤدي إعادة التحميل أو الإشارة المرجعية أو الرابط المُعاد توجيهه إلى استرداد مزدوج أبدًا. مناسب للصفحات الثابتة والجلسة المُنشأة في المتصفح.

voucherCode في جسم طلب الإنشاء

ب) على الخادم: تسليم الرمز مع الجلسة

تقرأ الواجهة الخلفية لديك المعامل من الطلب الوارد وتضعه في جسم طلب الإنشاء باسم voucherCode (eventSlug اختياري). يصبح الرمز جزءًا من الجلسة ولا يصل إلى الواجهة المصغّرة إلا عبر GET /shop/session-context المُصادَق عليه الذي يسلّمه مرة واحدة فقط ثم يمسحه؛ وتعيد استجابة الإنشاء الرمز المقبول للتحقق. اضبط أيضًا voucherCodeParam ليختفي المعامل كذلك من شريط العنوان. مناسب عندما تُعرض صفحتك من جانب الخادم على أي حال، أو عندما تريد التحقق من الروابط الواردة أو تسجيلها أو تقييد معدلها بنفسك. إذا قدّم المساران رمزًا، يفوز رمز الخادم.

ماذا يحدث للرمز

نفس السلوك على المسارين

يُسترد الرمز مرة واحدة فقط في رحلة جديدة (وتتجاهله الرحلة المستأنفة بعد إعادة التحميل أو العودة من الدفع). مع startMode "voucher" يعمل نموذج الرمز كما هو موضح في «استرداد قسيمة / تسجيل تذكرة». عند البدء بالمقالات، يُطبّق الرمز على السلة الأولى مع الحفاظ على عرض المقالات وموضع التمرير والكميات. تضيف «متابعة» تلك الكميات إلى السلة نفسها. لا تمرر الواجهة المصغّرة الرمز إلى onEvent أبدًا. دون eventSlug، يُسترد بعد اختيار الزائر للفعالية.

voucher-link.ts
// Voucher links point to your site: https://your-site.com/tickets?code=ED24-ADITUS
// The route, request and authentication below are examples for your own website.
// The parameter name is up to you: here "code".

// Option A (browser): the micro-frontend reads the parameter itself.
import { mount } from "@aditus/shop-embed";

mount(document.getElementById("aditus-shop")!, {
  publicKey: "pk_your_site",
  sessionToken,                 // session with eventSlug (article or voucher start)
  voucherCodeParam: "code",     // read ?code=… once, remove it from the address bar, redeem it
});
// Web Component: <aditus-shop public-key="pk_your_site" voucher-code-param="code"></aditus-shop>

// Option B (server): your backend hands the code over when it creates the session.
// (Express example; the code never goes into the session response, only into the session context.)
app.get("/tickets", async (req, res) => {
  const code = typeof req.query.code === "string" ? req.query.code : undefined;
  const r = await fetch("https://developers.aditus.com/api/shop/session", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.ADITUS_MINT_SECRET}`, "Content-Type": "application/json" },
    body: JSON.stringify({
      publicKey: "pk_your_site",
      email: req.user?.email,
      eventSlug: "ExperienceDaysv52024",
      startMode: "voucher",       // or omit: article start, the code lands in the first cart
      voucherCode: code,          // undefined = normal start without a code
    }),
  });
  const { sessionToken }: { sessionToken: string } = await r.json();
  // The page additionally mounts with voucherCodeParam: "code" so that ?code= disappears
  // from the address bar (the session code wins, it is redeemed only once).
  res.render("tickets", { sessionToken });
});

اختياري: احجز القسيمة أثناء إنشاء الجلسة على الخادم

إذا أردت عرض رفض القسيمة على موقعك قبل فتح الواجهة المضمّنة، فعيّن redeemVoucherOnMint: true في طلب POST /api/shop/session الموثوق. على خلاف تسليم voucherCode المعتاد، ينشئ الخادم سلة للمستخدم المصادَق عليه ويحجز القسيمة فيها قبل نجاح إصدار الجلسة؛ ليس هذا فحصًا إضافيًا. عند النجاح، أرسل sessionToken فقط إلى المتصفح. تستأنف الواجهة السلة نفسها دون إنشاء سلة أخرى أو استرداد القسيمة مرتين، سواء كان startMode هو voucher أو articles. من دون هذا الخيار يُطبَّق التسليم العادي لـ voucherCode.

يتطلب هذا الوضع voucherCode وeventSlug محددًا وبريدًا إلكترونيًا لمستخدم معروفًا ورأس Idempotency-Key. وهو متاح فقط عبر الخادم باستخدام سرّ الإصدار، وليس عبر POST /shop/embed-session في المتصفح. يدعم رموز القسائم/التذاكر ولا يدعم PromotionCode. الرمز محجوز في سلة، لكن التذكرة لم تُسجّل أو تُصدَر بعد: لا يزال على المشتري إتمام التسجيل والشراء. قد تفشل الفحوص اللاحقة؛ نجاح إصدار الجلسة لا يضمن إصدار التذكرة.

prepare-voucher.ts
// Server side of your website: one voucher attempt, no additional check call.
// You implement the authentication and storage of this attempt yourself.
// attemptKey is a random id your backend stores durably per logical redemption
// attempt (e.g. crypto.randomUUID()). On network errors, resend the same id
// and the SAME body; never generate a new one before a retry.
async function prepareVoucherSession(
  user: { email: string },
  code: string,
  attemptKey: string,
) {
  const response = await fetch("https://developers.aditus.com/api/shop/session", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.ADITUS_MINT_SECRET}`,
      "Content-Type": "application/json",
      "Idempotency-Key": attemptKey,
    },
    body: JSON.stringify({
      publicKey: "pk_your_site",
      email: user.email, // only from your authenticated server session
      eventSlug: "ExperienceDaysv52024",
      startMode: "voucher", // or "articles"
      voucherCode: code,
      redeemVoucherOnMint: true,
    }),
  });
  const result: {
    sessionToken?: string; expiresAt?: number; voucherRedeemed?: boolean;
    error?: string; reason?: string; upstreamStatus?: number;
  } = await response.json();

  if (response.ok && result.sessionToken && result.voucherRedeemed === true) {
    // Only now pass it to the browser and mount the embed.
    return { sessionToken: result.sessionToken, expiresAt: result.expiresAt };
  }
  if (response.status === 422 && result.error === "voucher_rejected") {
    // Your own UI: handle the business rejection using reason, do not open the embed.
    // Translate only documented reason values into your own texts.
    return { rejected: result.reason };
  }
  if (response.status === 409 && result.error === "voucher_preparation_uncertain") {
    // Uncertain stays uncertain; a repeated POST never turns it into a success.
    // If the Shop API cannot provide the cart id, manual clarification is needed.
    throw new Error("Redemption uncertain; clarify with ADITUS, do not book again.");
  }
  if (response.status === 410 && result.error === "voucher_preparation_expired") {
    throw new Error("Prepared session no longer valid; no blind new session.");
  }
  // 400 = invalid request/key, 409 = conflict/attempt in progress,
  // 502/503 voucher_upstream_error = infrastructure, not a business error.
  throw new Error(`Session creation not completed (${response.status})`);
}

يمثل 422 voucher_rejected مع reason المنقّى رفضًا وظيفيًا؛ اعرضه على موقعك ولا تعرض الواجهة المضمّنة من دون رمز جلسة. يعني 400 طلبًا أو مفتاحًا غير صالح، و409 idempotency_conflict استخدام المفتاح نفسه لمدخلات مختلفة. لا تعتبر العملية المعلّقة أو غير المؤكدة، أو 502/503، أو انتهاء مهلة الشبكة رفضًا للقسيمة. احفظ Idempotency-Key عشوائيًا على خادمك لكل محاولة منطقية وأعِد الطلب بالمفتاح نفسه والمدخلات نفسها فقط؛ فقد يكون الحجز موجودًا بالفعل. لا تسجّل الرمز أو البريد الإلكتروني أو أخطاء النظام الأولية أو رموز الجلسات.

قد يترك الإصدار الناجح سلة محجوزة حتى إذا لم يفتح المشتري الصفحة. تتم محاولة تنظيف السلال المعروفة عند الرفض أو الإلغاء دون ضمان؛ يعيد عامل الخلفية المحاولة عند بدء التشغيل وكل 60 ثانية بما يصل إلى 25 سجلًا في الدورة. يعيد المفتاح والبيانات نفسيهما نتيجة العملية المكتملة ما دامت صالحة. إذا انتهت صلاحية الرمز الأصلي أو أُلغي أو اكتمل أو حُذف أو نُقل، تكون الاستجابة 410 voucher_preparation_expired: لا تنشئ حجزًا جديدًا دون تحقق. يمكن لجلسة لاحقة مخوّلة تمديد أو نقل التحضير عند أول وصول إلى السلة. إزالة الرمز من عنوان URL لصفحتك لا تلغي الحجز.

أخطاء الإصدار المحددة: 400 invalid_redeem_voucher_on_mint / voucher_preparation_requires_user_event_code / invalid_idempotency_key (أو أخطاء التحقق المعتادة). تتضمن استجابة 422 voucher_rejected الحقلين reason وupstreamStatus، لا الرمز أو النص الأولي لنظام التذاكر. الأسباب المحتملة: unknown · articleLimit · exhausted · ownership · used · reserved · limit · cartLimit · locked · canceled · otherEvent · legitimation · rejected · unsupported_code_type.

يشير 409 idempotency_conflict إلى اختلاف المدخلات؛ وتتضمن voucher_preparation_pending الرأس Retry-After: 2 ثانية. تبقى voucher_preparation_uncertain غير مؤكدة عند التكرار ولا تتحول تلقائيًا إلى نجاح. تعني 410 voucher_preparation_expired أن التحضير القديم لم يعد قابلًا للإعادة، و502/503 voucher_upstream_error خللًا في نظام التذاكر. إذا أُنشئت سلة من دون عودة معرفها، فلا تسمح واجهة النظام المتاحة بالتوفيق أو الإفراج تلقائيًا: يبقى السجل غير المؤكد ولا ينشئ الخادم سلة ثانية عند إعادة المحاولة. اطلب توضيحًا يدويًا من ADITUS قبل محاولة جديدة. إذا تعذر تحميل session-context تعرض الواجهة خطأ وخيار إعادة المحاولة بدلًا من إنشاء سلة جديدة بصمت.

Try all three live in the Integration Tester: create a session with eventSlug, pick startMode or an assortment, mount, and watch the request log. The demo system accepts the test voucher ED24-ADITUS on ExperienceDaysv52024 (each redemption reserves the ticket for about 20 minutes; backing out releases it). Open the Integration Tester

اختياري: تصفح بشكل مجهول، قم بتسجيل الدخول عند الخروج

ليس عليك أن تعرف من هو المستخدم مقدمًا. يمكنك التثبيت مع جلسة ضيف وتتيح الواجهة الأمامية الصغيرة للزائر المجهول تصفح الأحداث واختيار المقالات وملء سلة التسوق. فقط عندما يتجاوزون عربة التسوق - باتجاه التسجيل والخروج - سيطلب منك تحديد المستخدم الحقيقي، عبر رد الاتصال onUserRequired. ثم يستمر باقي التدفق مع الحفاظ على العربة.

  1. 1

    جبل مع جلسة ضيف

    لا يزال رمز الجلسة مطلوبًا لتنشيط المتجر - ولكن يمكن أن يكون جلسة ضيف: ما عليك سوى إنشائها دون حقل البريد الإلكتروني، ولا حاجة إلى عنوان نائب. يتصفح الزائر ويملأ العربة الموجودة تحته.

  2. 2

    يؤدي ترك العربة إلى تشغيل onUserRequired

    في اللحظة التي يتقدم فيها الزائر عبر عربة التسوق، تستدعي الواجهة الأمامية الصغيرة رد الاتصال الخاص بك مرة واحدة وتظهر حالة معلقة محايدة. قم بتوثيق المستخدم (تسجيل الدخول/SSO)، ثم قم بإنشاء جلسة جديدة مرتبطة بالمستخدم بنفس الطريقة تمامًا مثل الجلسة الأولى: خادم إلى خادم مع سرك. ويتم إنشاء المستخدم اللاحق من خلال نظام التحويل من جهاز إلى جهاز أيضًا - لا يصل السر أبدًا إلى المتصفح ولا يتم تعيين المستخدم مطلقًا في رمز العميل. يقوم رد الاتصال الخاص بك فقط بترحيل الرمز غير الشفاف الناتج. والأهم من ذلك، أن الواجهة الخلفية لديك تستمد تلك الهوية من جلسة المصادقة الخاصة بها (ملف تعريف الارتباط/JWT)؛ يجب ألا يقبل أبدًا البريد الإلكتروني أو معرف المستخدم من المتصفح، وإلا يمكن لزائر ضار أن يطلب جلسة شخص آخر ويقرأ بياناته الشخصية عبر الملء المسبق.

  3. 3

    قم بإرجاع الرمز المميز الجديد - يتم ترحيل العربة

    قم بإرجاع الرمز المميز للجلسة الجديدة وتقوم الواجهة الأمامية الصغيرة بتبديله وربط المستخدم المحدد بعربة التسوق الحالية - تبقى عربة التسوق ومقالاتها وأي فترات زمنية دون تغيير. ثم يدخل في التسجيل العودة فارغة للإجهاض - يبقى الزائر ببساطة في عربة التسوق، دون أي خطأ.

  4. 4

    احذف onUserRequired لمستخدم ثابت

    إذا تركت رد الاتصال، فلن يتغير شيء من التدفق الكلاسيكي: قم بالتثبيت بجلسة مرتبطة بالمستخدم وتعمل الواجهة الأمامية الصغيرة مثل ذلك المستخدم منذ الخطوة الأولى.

page.ts
import {
  mount,
  type ShopHandle,
  type AditusShopConfig,
} from "@aditus/shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount target #aditus-shop not found");

// A guest session token is enough to mount: created server-to-server as
// usual, just without the email field (no placeholder address needed). The
// visitor may browse and fill the cart before a specific user is known.
const config: AditusShopConfig = {
  apiBase: "https://developers.aditus.com/api/shop",
  publicKey: "pk_your_site",
  sessionToken: guestSessionToken,

  // Called once as soon as the visitor leaves the cart towards registration.
  // This is where you authenticate the user (login/SSO) and create a new,
  // user-bound session server-to-server.
  // Return the fresh token -> the micro-frontend adopts it and rebuilds the
  // cart under the identified user (articles are kept). Return null to
  // cancel -> the visitor stays in the cart, without an error.
  onUserRequired: async () => {
    // Calls only your backend, with no identity in the payload. Your backend reads
    // the signed-in user from its own session (cookie/JWT) and creates the session
    // server-side for exactly this identity. Never pass the email from the browser,
    // otherwise an attacker could insert someone else's address here and reach
    // that person's personal data through the prefill.
    // host.loginAndMintSession(): Promise<string | null> is your own function.
    // Implement login/SSO + authenticated backend call + server-side session creation on your website.
    // On cancel the function returns null; on failure it throws explicitly.
    return host.loginAndMintSession();
  },
};

const handle: ShopHandle = mount(el, config);

تتم المصادقة على موقعك

لا يقوم ADITUS بمصادقة المستخدمين النهائيين لديك. موقعك هو المصدر الوحيد لحقيقة هوية المستخدم - حيث تقوم بتشغيل تسجيل الدخول أو تسجيل الدخول الموحد (SSO) أو الجلسة الخاصة بك. لا يثق ADITUS إلا في رمز الجلسة الذي تعده الواجهة الخلفية لديك بمفتاح سري. سلسلة الثقة هي: مصادقتك تثبت هويتك، ورمزك المميز الصادر بالسر يضمن ذلك، والواجهة الأمامية الصغيرة تعرضه.

  1. 1

    موقعك يصادق المستخدم

    تسجيل الدخول، الدخول الموحّد (SSO)، جلسة الأعضاء - جلسة خاصة بك تمامًا. ADITUS غير مشارك هنا.

  2. 2

    الواجهة الخلفية الخاصة بك تنهي جلسة

    خادم إلى خادم مع المفتاح السري. تقوم بتمرير من يجب أن تكون الواجهة الأمامية الصغيرة بمثابة (البريد الإلكتروني) وتربطه بمفتاحك العام. (publicKey)

  3. 3

    تقوم الصفحة بتثبيت الواجهة الأمامية الصغيرة

    يتم تسليم الرمز غير الشفاف إلى المتصفح وتمريره كـ sessionToken إلى mount(). يبقى السر على الخادم الخاص بك.

  4. 4

    كل مكالمة تحمل الجلسة

    ترسل الواجهة الأمامية الصغيرة جلسة X-Aditus عند كل طلب؛ تحدد واجهة برمجة المتجر مستخدم سلة التسوق من الجلسة.

  5. 5

    لا يوجد رمز مميز → تظل الواجهة الأمامية الصغيرة غير نشطة

    بدون جلسة، لا يتم تنشيط الواجهة الأمامية الصغيرة مطلقًا: فهي تعرض إشعارًا محايدًا ولا تجري أي مكالمات - لا توجد عربة تسوق ولا دفع. اصنع جلسة لتشغيله.

1. أنشئ الجلسة على الواجهة الخلفية الخاصة بك

server.ts
// Server side of your website. The secret key stays on the server and never reaches the browser.
// You build user and the login/session route between the browser and your server yourself; ADITUS does not provide them.
// Your backend has already authenticated the user itself (login/SSO/session).
// Important: user comes from this server session, never from the browser's
// request body. Otherwise an attacker could slip in someone else's email.
const res = await fetch("https://developers.aditus.com/api/shop/session", {
  method: "POST",
  headers: {
    // Your client's mint secret (generated in the ADITUS admin), server-side only.
    Authorization: `Bearer ${process.env.ADITUS_MINT_SECRET}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    publicKey: "pk_your_site",        // binds the session to your key (required)
    email: user.email,                 // who the micro-frontend acts as (omit = anonymous guest session)
    externalUserId: user.id,           // optional metadata (your user id)
    // sharedCartKey: visitor.stableRef, // required for anonymous shared carts (default):
    // opaque reference from your trusted server session, never browser input
    ttlSeconds: 1200,                  // optional: lifetime (default 20 min, max 24 h)
    eventSlug: "ExperienceDaysv52024", // optional: start directly in this event (otherwise the event overview)
    // startMode: "voucher",           // optional: start with the voucher/ticket code form (without eventSlug after the event selection)
    // assortment: "Pressetickets",    // optional (with eventSlug): only this assortment (id or name)
    // voucherCode: req.query.code,    // optional: redeem a code from a voucher link in the embed
    // redeemVoucherOnMint: true,      // optional: reserve it here instead (requires email, eventSlug and Idempotency-Key)
  }),
});

// Choose the response type freely and pass sessionToken to the browser (inline or via fetch).
const { sessionToken }: { sessionToken: string; expiresAt: number } = await res.json();

2. جبل مع الرمز المميز

page.ts
import {
  mount,
  type ShopHandle,
  type AditusShopConfig,
} from "@aditus/shop-embed";

// getElementById can return null. Check it properly in TypeScript instead of using "!".
const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount target #aditus-shop not found");

// AditusShopConfig types every option and gives you autocompletion.
const config: AditusShopConfig = {
  apiBase: "https://developers.aditus.com/api/shop",
  publicKey: "pk_your_site",   // required as soon as a session is involved
  sessionToken,                // issued by your backend, keep it in memory only
  cartScope: "shared",          // default; requires a trusted server identity
};

const handle: ShopHandle = mount(el, config);

// The micro-frontend sends the X-Aditus-Session header with every shop call.
// On logout, revoke the session on the server and remount/update.

3. الإلغاء عند تسجيل الخروج

server.ts
// Server side, e.g. when your user logs out.
// You build the logout route between the browser and your server yourself; only your backend calls ADITUS.
await fetch(`https://developers.aditus.com/api/shop/session/${sessionToken}`, {
  method: "DELETE",
  headers: { Authorization: `Bearer ${process.env.ADITUS_MINT_SECRET}` },
});
// { revoked: true }

يعود الطلب النهائي إلى صفحتك

عند اكتمال شراء التذكرة، لن تحتاج إلى بحث منفصل عن الطلب. نظرًا لأن الواجهة الأمامية المصغرة تعمل أصلاً في صفحتك (Light DOM، وليس iframe)، فإنها تقوم بتسليم الطلب النهائي مباشرة إلى التعليمات البرمجية الخاصة بك عبر رد الاتصال onComplete - وهو استدعاء JS مباشر، بدون رسالة لاحقة. لقد قامت الواجهة الأمامية المصغرة بحل الطلب لك بالفعل، لذلك تتلقى رقم الطلب وروابط التذكرة، وليس مجرد معرف.

mount.ts
import {
  mount,
  type ShopHandle,
  type CompletedOrder,
} from "@aditus/shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount target #aditus-shop not found");

const handle: ShopHandle = mount(el, {
  publicKey: "pk_your_site",
  sessionToken,
  // Called exactly once as soon as the order is placed.
  // The micro-frontend has already resolved the order for you. You get
  // the resolved order, not just an id.
  onComplete: (order: CompletedOrder) => {
    order.id;       // order GUID for further processing (server, reconciliation)
    order.number;   // order number for display
    order.status;   // order status
    order.buyer;    // buyer (name / email), if present
    order.tickets;  // [{ name, links: [{ kind: "pdf" | "apple" | "google", url }] }]

    // -> store order.id, show a confirmation, trigger DOI / newsletter ...
  },
  // Called on every unrecoverable error in the flow.
  onError: (err: Error) => {
    console.error("Shop error:", err.message);
  },
});

ما تتلقى

يتم تشغيل onComplete مرة واحدة لكل عملية دفع ناجحة مع الطلب الذي تم حله: id (معرّف GUID للطلب، مفتاحك للمعالجة اللاحقة)، وnumber (رقم الطلب للعرض)، والحالة، والمشتري (الاسم والبريد الإلكتروني في حالة وجوده) والتذاكر - تحمل كل تذكرة روابط مكتوبة من النوع "pdf" أو "apple" أو "google". يتم تشغيل onError عند حدوث أي خطأ غير قابل للاسترداد في التدفق.

إعادة توجيه طرق الدفع

يتم تنشيط الطرق التي تظل على الصفحة (الفاتورة، الدفع المسبق) عند الاكتمال على الفور. يتم التعامل مع الطريقة التي ترسل المتصفح إلى صفحة دفع خارجية (مثل Saferpay) من خلال Micro-Frontend من النهاية إلى النهاية: فهي تستمد عناوين URL للإرجاع من الصفحة التي تعمل عليها، وترسل المستخدم للخارج، وفي طريق العودة تنهي الطلب وتطلقه على اكتمل - رحلة ذهابًا وإيابًا كاملة بدون رمز على جانبك. فقط إذا كان الإرجاع يجب أن يصل إلى صفحة مختلفة، يمكنك تجاوز redirectUrls.successUrl / CancelUrl / errorUrl.

"إحصاءات Google‏ 4" / أحداث التجارة الإلكترونية (اختياري) (GA4)

يمكن للواجهة الأمامية الصغيرة الإبلاغ عن مسار التسوق إلى التحليلات الخاصة بك - ولكن فقط إذا طلبت ذلك. فهو لا يجمع أي gtag أو GTM أو GA SDK، ولا يقوم بتحميل أي شيء، ولا يقوم بتعيين أي ملفات تعريف ارتباط، ولا يرسل أي شيء من تلقاء نفسه. يمكنك إضافة رد اتصال اختياري واحد، onEvent، وتلقي الأحداث المكتوبة التي تم تشكيلها بالفعل في مخطط التجارة الإلكترونية المحسّنة على GA4 من Google. احذف oneEvent ولن يتغير شيء. إن الأداة التي تقوم بتغذيتها، وما إذا كانت لديك موافقة على إرسالها، تظل في صفك تمامًا.

mount.ts
import {
  mount,
  type ShopHandle,
  type ShopAnalyticsEvent,
} from "@aditus/shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount target #aditus-shop not found");

const handle: ShopHandle = mount(el, {
  publicKey: "pk_your_site",
  sessionToken,
  // Optional. If you leave out onEvent, nothing changes: the micro-frontend
  // sends nothing, loads no gtag/GTM/GA SDK and sets no cookies.
  // Tool, consent and mapping stay entirely with you. This example uses GA4 via gtag.
  onEvent: (event: ShopAnalyticsEvent) => {
    // Consent is yours: only send once the user has agreed.
    if (!hasAnalyticsConsent()) return;
    // The events already follow the GA4 schema (name + items/value/currency ...),
    // so pass them to gtag 1:1.
    const { name, ...params } = event;
    window.gtag?.("event", name, params);
  },
});

بالفعل في شكل GA4

يحمل كل حدث اسم حدث "إحصاءات Google" 4 بالإضافة إلى معلماته القياسية: العناصر (مع item_id، وitem_name، والسعر، والكمية، وitem_category، و- حيثما ينطبق - الخصم والقسيمة)، والقيمة والعملة؛ يضيف add_Payment_info نوع الدفع ويضيف الشراء معرف_المعاملة. قم بتدمير الاسم وتمرير الباقي مباشرة إلى gtag("event"، name، params) - لا حاجة إلى إعادة التعيين. (GA4)

الموافقة تبقى لك

onEvent هي مجرد وظيفة JavaScript عادية في صفحتك والتي نطلق عليها اسمًا مباشرًا. لا ترسل Micro-Frontend نفسها أي تحليلات في أي مكان - لا شيء يغادر الصفحة حتى يرسلها المعالج الخاص بك، لذلك تظل إدارة موافقتك تحت السيطرة الكاملة: بوابة إعادة التوجيه عند الموافقة، أو التوجيه إلى dataLayer في GTM بدلاً من أحداث gtag أو الدفعة أو إسقاط الأحداث بالكامل. يتم اكتشاف أي خطأ يرتكبه المعالج الخاص بك وعزله، لذلك لا يقطع تدفق المستخدم أبدًا - احتفظ بضوء المعالج (دفع gtag/dataLayer) نظرًا لأنه يعمل بشكل مضمّن.

حدثحرائق عندما
view_item_listيرى المستخدم قائمة المقالات (يتم تشغيلها مرة واحدة لكل مجموعة معروضة).
add_to_cartتتم إضافة مقال أو إضافة إلى سلة التسوق.
remove_from_cartتتم إزالة خط سلة التسوق أو الوظيفة الإضافية.
view_cartيتم عرض عرض سلة التسوق (مرة واحدة لكل عربة تسوق).
begin_checkoutيترك المستخدم العربة نحو التسجيل / الخروج.
add_payment_infoيتم اختيار طريقة الدفع (تحمل نوع_الدفع).
purchaseتم تقديم الطلب (يحمل معرف المعاملة والقيمة والعناصر).

ناقل الحدث: Handle.on / Handle.off (اختياري)

إلى جانب عمليات الاسترجاعات الخاصة بالتكوين، يحمل المقبض الذي يتم إرجاعه بواسطة mount() ناقل أحداث صغيرًا اختياريًا بالكامل. اشترك باستخدام Handle.on(event,lister) - يقوم بإرجاع وظيفة إلغاء الاشتراك المطابقة - ويفصل باستخدام Handle.off(event,lister). إذا لم تتصل مطلقًا بـ()، فلن يتغير شيء: لا تضيف الحافلة أي تبعيات، ولا ترسل شيئًا ولا تكلف شيئًا. إنها ملاحظة خالصة لصفحتك - شارة عربة صغيرة، ولافتة تأكيد، وتسجيل خاص بك - وهي تكمل عمليات الاسترجاعات بدلاً من استبدالها.

mount.ts
import {
  mount,
  type ShopHandle,
  type ShopCartUpdate,
} from "@aditus/shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount target #aditus-shop not found");

const handle: ShopHandle = mount(el, {
  publicKey: "pk_your_site",
  sessionToken,
});

// Optional. Without on() the micro-frontend works completely normally.
// The bus is pure observation; no event is required.
// on() returns the unsubscribe function; alternatively handle.off(name, fn).
const offCart = handle.on("cart:update", (cart: ShopCartUpdate) => {
  // On every cart change: count, total, line items (GA4 item shape).
  updateMiniCartBadge(cart.itemCount); // e.g. a badge in the page header
});

handle.on("checkout:complete", ({ order }) => {
  // The order is placed. You receive the same resolved order as in onComplete.
  showConfirmationBanner(order.number);
});

handle.on("analytics", (event) => {
  // Mirror of the GA4 funnel (identical payloads to config.onEvent).
  console.debug("Funnel:", event.name);
});

// Unsubscribe selectively later; the rest stays active:
offCart();
حدثحرائق عندما
cart:updateتتغير سلة التسوق: تم إنشاؤها، أو إضافة العنصر أو إزالته، أو إفراغه. يحمل معرف سلة التسوق وitemCount والقيمة والعملة والخطوط كعناصر على شكل GA4.
phase:changeThe visible step changed. The event carries phase (events · home · articles · voucher · cart · registration · legitimation · buyer · payment · summary · done, same vocabulary as data-step), previous (null for the entry step) and startPhase, the session's entry step. Lets your page drive its own Back / Restart buttons without reading CSS hooks.
checkout:completeتم تقديم الطلب. يُطلق مباشرة بعد config.onComplete مع كائن الطلب المحلّل نفسه، قبل أن تعرض الواجهة الأمامية المصغرة صفحة الإتمام الخاصة بها. استدعِ preventDefault() بشكل متزامن لعرض صفحة الإتمام الخاصة بك بدلًا منها (راجع «صفحة الإتمام الخاصة بك»).
analyticsنسخة مطابقة لمسار GA4 أعلاه: يتم أيضًا إصدار كل حمولة onEvent على الناقل، دون تغيير.

تحديث المستمعين على قيد الحياة ()

الاشتراكات مباشرة على المقبض، وليس على العرض: Handle.update({ ... }) يعيد عرض Micro-Frontend ولكنه يبقي كل مستمع متصلًا. يؤدي إلغاء التثبيت عبر Handle() إلى فصل جميع المستمعين تلقائيًا - ما عليك سوى إيقاف() (أو إلغاء الاشتراك المرتجع) عندما تريد التوقف عن الاستماع بينما يستمر المتجر في العمل.

معزول، لا يحجب أبدًا

يتم القبض على المستمع الذي يرمي وعزله - فهو لا يقطع تدفق المستخدم أبدًا ولا يؤثر أبدًا على المستمعين الآخرين. يعكس حدث "التحليلات" مسار تحويل GA4 بحمولات مماثلة لـ config.onEvent، بحيث يمكنك استهلاك مسار التحويل عبر الناقل أو رد الاتصال أو كليهما؛ تنطبق نفس قاعدة الموافقة: لا شيء يغادر الصفحة حتى يرسلها الكود الخاص بك.

صفحة الإتمام الخاصة بك

يُطلق checkout:complete قبل أن تعرض الواجهة الأمامية المصغرة صفحة الإتمام الخاصة بها. إذا كانت صفحتك تعرض تأكيدها الخاص، فاستدعِ preventDefault() بشكل متزامن داخل المستمع: event.preventDefault() على العنصر <aditus-shop>، أو preventDefault() من الحمولة مع handle.on()، أو completion.preventDefault() من الوسيط الثاني لـ config.onComplete. عندها لا تعرض الواجهة الأمامية المصغرة صفحة إتمام ولا تقوم بالتمرير ولا تعرض أي خطوة أخرى، وتتولى صفحتك الأمر. يتم تقديم الطلب في جميع الأحوال. يجب أن يتم الاستدعاء داخل المستمع نفسه، وليس بعد await. بدونه تعرض الواجهة الأمامية المصغرة صفحة الإتمام كما كان من قبل. ينطبق ذلك أيضًا على العودة من صفحة دفع خارجية.

أنهِ الجلسة بعد ذلك. اكتمل الطلب، لذا يمكن لخادمك إلغاء الجلسة في الخطوة نفسها عبر DELETE /api/shop/session/:token باستخدام مفتاحك السري (راجع «إلغاء الجلسة»). يظل الطلب المُقدَّم وروابط التذاكر في كائن الطلب صالحة، لأن الروابط لا تعتمد على الجلسة. لعملية شراء أخرى، أنشئ جلسة جديدة وأعد تحميل الواجهة الأمامية المصغرة. لا تُنهي الواجهة الأمامية المصغرة الجلسة بنفسها، لأن الإلغاء يتطلب مفتاحك السري. احتفظ بالجلسة فقط إذا كانت صفحتك تواصل البيع فيها عن قصد.

الأوامر: goTo وback

إلى جانب المراقبة، يمكن لصفحتك إرسال طلبين محدودين إلى الواجهة المصغّرة. يعيد كلاهما true عندما ينتقل المتجر إلى خطوة أخرى وfalse في غير ذلك، ولا يعيدان التركيب أبدًا ولا يفقدان مسار الشراء. يعرض goTo("cart") خطوة سلة التسوق لسلتك المصغّرة الخاصة، ولا يُقبل إلا في خطوة التسجيل ما دام لم يُكتب شيء بعد. ينتقل back() خطوة واحدة إلى الخلف، تمامًا مثل زر الرجوع في المتجر. ويعيد false حيثما كان هذا الزر غير موجود أو معطّلًا: في خطوة الدخول (startPhase) وفي صفحة الإتمام وأثناء تنفيذ دفع أو تغيير آخر. يتوفر كلاهما على مقبض mount وعلى <aditus-shop> (element.back() وelement.goTo("cart")).

browser-back.ts
import type { ShopHandle } from "@aditus/shop-embed";

// Optional: the browser's back gesture goes one step back in the shop.
// The micro-frontend itself never writes to the browser history. Your page
// keeps its own history entry while the shop is past its entry step.
export function connectBrowserBack(shop: ShopHandle): () => void {
  let inside = false; // after the entry step and before the completion page
  let entry = false; // our history entry is on top
  const offPhase = shop.on("phase:change", ({ phase, startPhase }) => {
    inside = phase !== startPhase && phase !== "done";
    if (inside && !entry) {
      history.pushState({ aditusShop: true }, "");
      entry = true;
    } else if (!inside && entry) {
      entry = false;
      history.back(); // entry no longer needed
    }
  });
  const onPopState = () => {
    if (!entry) return; // normal navigation of your page
    entry = false;
    // false: the shop's back button is missing or locked (e.g. payment in progress).
    if (!shop.back() && inside) {
      history.pushState({ aditusShop: true }, "");
      entry = true;
    }
  };
  window.addEventListener("popstate", onPopState);
  return () => {
    offPhase();
    window.removeEventListener("popstate", onPopState);
  };
}

// Your own mini cart: back to the cart step, otherwise scroll to the shop.
export function showCart(shop: ShopHandle, container: HTMLElement): void {
  if (!shop.goTo("cart")) container.scrollIntoView({ behavior: "smooth" });
}

لا تكتب الواجهة المصغّرة أبدًا في سجل المتصفح بنفسها: عناوين URL والموجّه ملك لموقعك. يحتفظ المثال بإدخال إضافي واحد بالضبط في السجل ما دام المتجر قد تجاوز خطوة الدخول، لذا فإن إيماءة الرجوع في المتصفح (ومنها السحب على iPhone) تعود أولًا خطوة داخل المتجر ولا تغادر الصفحة إلا من خطوة الدخول. إذا كان موقعك يستخدم موجّهًا من جهة العميل (React Router أو Next.js أو Vue Router)، فأنشئ هذا الإدخال عبر الموجّه بدلًا من history.pushState حتى لا يعامله الموجّه كتنقل غريب.

publicKey مقابل sessionToken

أبقِ الاثنين منفصلين. إنهم يجيبون على أسئلة مختلفة ويتم إقرانهم، وليسوا قابلين للتبادل.

publicKey: الثقة الأصل

قابل للنشر. فهو يأتي ضمن رمز العميل الخاص بك، لذا فهو ليس سرًا ولا يمنح شيئًا في حد ذاته. تفرض الواجهة الخلفية قائمة بيضاء لكل مجال لكل مفتاح: المفتاح يعمل فقط من نطاقاته المسجلة. يجيب "أي موقع هذا؟" فقط خيار صريح وغير آمن عمدًا يضبطه المشغّل لعميل واحد يمكنه رفع هذه القائمة البيضاء؛ وتظل حالة التفعيل وإلزامية الجلسة والأسرار سارية.

sessionToken: هوية

يُنشأ بالسر على الواجهة الخلفية لديك، وهو غير شفاف وقصير العمر. يجيب "من هو المستخدم؟" ترتبط الجلسة بالمفتاح العام الذي أُنشئت من أجله: يجب أن يرسل طلب الجلسة أيضًا هذا المفتاح، ويتم رفض عدم التطابق (403). (publicKey)

قواعد الأمن

  • المفتاح السري موجود فقط على الخادم الخاص بك - ولا يوجد أبدًا في كود العميل أو الحزم أو ملفات env التي يتم شحنها إلى المتصفح.
  • لا تخزّن الرمز بنفسك في localStorage أو في ملفات تعريف الارتباط؛ احتفظ به في الذاكرة وجدّده بإنشاء جلسة جديدة. لإعادة التحميل ولرحلة الدفع ذهابًا وإيابًا تحفظه الواجهة المصغّرة بنفسها في sessionStorage الخاص بعلامة التبويب.
  • الرموز قصيرة العمر (افتراضي 20 دقيقة، بحد أقصى 24 ساعة) ومستخدم واحد.
  • ألغِ الجلسة عند تسجيل الخروج: يُرفض الرمز الملغى فورًا (401). وحتى انتهاء صلاحيته أو إلغائه يبقى الرمز الصالح رمز حامل (bearer)، فتعامل معه على هذا الأساس.
  • قم دائمًا بإقران الجلسة بمفتاحها العام - يتم رفض المفتاح المفقود أو غير المتطابق (403). (publicKey)
  • تعيد الجلسة غير الصالحة أو المنتهية 401. لا يوجد رجوع إلى مستخدم آخر.

ما الذي يمكن لرمز الجلسة فعله

رمز الجلسة هو رمز حامل (bearer). هذا هو النطاق الدقيق الذي تفرضه واجهة ADITUS البرمجية عليه. تفيد هذه المعلومات نموذج التهديدات ومراجعة الأمان لديك.

الرمز ومدة الصلاحية
عشوائي بطول 256 بت، معتم، لا يحمل بيانات ولا يمكن فك ترميزه. المدة الافتراضية 20 دقيقة، قابلة للضبط من دقيقة واحدة إلى 24 ساعة عند الإنشاء. لا تُمدَّد الجلسة أبدًا. جدّدها بإنشاء جلسة جديدة.
الربط
مرتبطة بمفتاح publicKey الذي أُنشئت من أجله. يجب أن يرسل كل طلب جلسة هذا المفتاح تحديدًا، ويتم التحقق من مصدر الطلب مقابل قائمة النطاقات المسموح بها للمفتاح. يُرفض أي مفتاح مختلف أو مصدر غريب (403).
جلسة مجهولة
أُنشئت دون مستخدم: يمكنها تصفح الفعاليات وإنشاء سلتها الخاصة وتعديلها. يُرفض التسجيل والفترات الزمنية وتعيين الدفع وإتمام الشراء من جانب الخادم (403) إلى أن تستبدلها صفحتك بجلسة معرَّفة.
جلسة معرَّفة
بالإضافة إلى ذلك: التسجيل والفترات الزمنية وتعيين الدفع وإتمام الشراء، دائمًا ضمن السلال التي تملكها هذه الجلسة فقط. مع protectUserData تكون بيانات المشتري المخزنة للمستخدم محمية: تُرفض أي كتابة من شأنها تغييرها (403).
ملكية السلة
ترتبط السلة بمالكها عبر تجزئة بمفتاح (HMAC)؛ في الجلسة المجهولة بالرمز نفسه، وفي الجلسة المعرَّفة بهوية المستخدم التي أنشأها خادمك، ضمن نطاق publicKey. لا تحصل جلسات المستخدمين الآخرين على أي وصول إليها، وتُنفَّذ عمليات الكتابة على السلة الواحدة بشكل متسلسل.
البيانات الشخصية
يُعيد سياق الجلسة أعلامًا ومعرّفات ومعرّف المستخدم الذي أنشأه خادمك بنفسه؛ لا شيء لا يعرفه موقعك مسبقًا. لا يمكن الوصول إلى بيانات المشتري إلا عبر مسارات السلة والتسجيل الخاضعة للتحقق من الملكية.
حدود المعدل
القيم الافتراضية: الإنشاء والإلغاء 300 في الدقيقة لكل عميل وعنوان IP، والإكمال التلقائي للعناوين والشركات والمدن 60 في الدقيقة لكل جلسة و3000 في الدقيقة لكل عميل عبر جميع الجلسات. كل حد هو ميزانية خاصة بعميلك ولا تؤثر عليها العملاء الآخرون. يمكن لـ ADITUS تحديد حدود فردية لكل عميل عند الطلب.
سجل التدقيق
تُسجَّل طلبات واجهة برمجة المتجر وتُحفظ لمدة 90 يومًا: العميل، والمسار المطبَّع، والحالة، ومضيف المصدر، وتجزئة مختصرة لرمز الجلسة، ومعرّف السلة أو الطلب. لا يُسجَّل الرمز نفسه أبدًا، ولا البريد الإلكتروني، ولا عنوان IP.
الإلغاء
يُبطل DELETE /api/shop/session/:token الجلسة فورًا. لا توجد ذاكرة مؤقتة بينهما؛ ويتلقى الطلب التالي مباشرة بهذا الرمز 401. استدعِه عند تسجيل الخروج.

نقطة النهاية ومرجع الاستجابة

POST/api/shop/sessionإنشاء الجلسة باستخدام سر Bearer
DELETE/api/shop/session/:tokenإبطال - سر حامل (Bearer)

يأتي الاستدعاءان من خادمك الخلفي: يصادق باستخدام mint secret الخاص بعميلك (المُنشأ في لوحة إدارة ADITUS) كرمز Bearer. جلسة واحدة لكل تسجيل دخول هي النمط المعتاد؛ ولا يرى المتصفح إلا الرمز الناتج. المواقع بلا خادم خلفي لا تستخدم نقاط النهاية هذه: ينشئ العنصر جلسة المتصفح بنفسه عبر POST /api/shop/embed-session المقيّد بالنطاق (public-key مع cart-scope="tab").

يمكنك تجربة هذا التدفق الدقيق باستخدام بيانات الاعتماد الخاصة بك فياختبار التكامل. أنشئ الجلسة وركّب المتجر ثم اتبع مسار الشراء.

إنشاء جلسة: الطلب

رأسقيمةمعنى
AuthorizationBearer <mint-secret>mint secret الخاص بعميلك، والذي تم إنشاؤه في وحدة تحكم المشرف الخاصة بـ ADITUS. من جانب الخادم فقط - يجب ألا يتم شحنه مطلقًا إلى متصفح أو حزمة تطبيقات.
Content-Typeapplication/jsonالجسم هو كائن JSON.
Idempotency-Keystring · 8–200 visible ASCII chars · required with redeemVoucherOnMintمطلوب عند ضبط redeemVoucherOnMint على true، ويتكون من 8–200 حرف ASCII ظاهر. احفظ مفتاحًا ثابتًا لمحاولة استرداد واحدة على خادمك واستخدمه مع الطلب نفسه عند إعادة المحاولة. تؤدي إعادة استعماله بمدخلات مختلفة إلى 409 idempotency_conflict. لا ترسل هذا المفتاح أو سرّ إصدار الجلسة من المتصفح.
مجال الجسميكتبمعنى
sharedCartKeystring · optionalتشارك الجلسات المحددة الصادرة بين الخوادم عادةً عبر الهوية الموثوقة. للسلات المجهولة المشتركة يجب على خادمك إصدار الجلسة مع sharedCartKey: مرجع زائر مبهم وثابت مستمد من جلسة الزائر الموثوقة لديه، وليس بريدًا إلكترونيًا. هذا حقل إصدار للخادم فقط، لا تهيئة متصفح ولا سمة HTML. لا تقبل بريدًا أو هوية أو sharedCartKey اعتباطيًا من المتصفح. الإصدار التلقائي من المتصفح وحده لا يتيح مشاركة آمنة؛ يعيد shared دون هوية موثوقة خطأ صريحًا بعدم الدعم، حتى عند حذف النطاق. لا يحتاج العزل الصريح 'tab' إلى sharedCartKey؛ ولا يوجد تراجع صامت.
publicKeystring · requiredالمفتاح القابل للنشر الذي أُنشئت الجلسة من أجله. يجب أن يكون هو نفس المفتاح الذي يتم تركيب الواجهة الأمامية الصغيرة به - يتم التحقق من كل مكالمة متجر لاحقة (403 عند عدم التطابق). يجب أن ينتمي سر حامل هذا المفتاح إلى عميل هذا المفتاح. (Bearer)
emailstring · optionalمستخدم المتجر الذي تعمل فيه الجلسة. يمكنك الحصول عليها من جلسة خادمك المصادق عليها (تسجيل الدخول/ملف تعريف الارتباط SSO أو JWT) - وليس من طلب المتصفح أبدًا، وإلا يمكن للزائر الحصول على جلسة شخص آخر. قم بحذفها لإنشاء جلسة مجهولة: يمكن للزائر تصفح عربة التسوق وملؤها، لكن الواجهة الأمامية الصغيرة تحظر الخطوة التي تلي عربة التسوق حتى يوفر رد الاتصال onUserRequired جلسة مرتبطة بالمستخدم (راجع تدفق المستخدم الاختياري). لا يزال يتم رفض البريد الإلكتروني الحالي ولكن المشوه (400).
externalUserIdstring · optionalمرجع مستخدم ثابت من جلسة خادمك المصادق عليها. يُحفظ مع الجلسة ويُستخدم لنطاق الهوية عند وجوده؛ لا تغيّره عند التجديد ولا تقبله من إدخال متصفح اعتباطي. ليس مصادقة بحد ذاته.
ttlSecondsnumber · optionalمدة الجلسة بالثواني. الافتراضي 20 دقيقة، بحد أقصى 24 ساعة. عند انتهاء صلاحيتها، تُرجع مكالمات المتجر 401 session_expired. عندها أصدر رمزًا جديدًا؛ ومع refreshSession في الإعدادات تطلبه الواجهة المصغّرة تلقائيًا.
eventSlugstring · optionalFixes the journey's entry point server-side: set, the micro-frontend starts directly on this event's article selection; omitted, it starts on the event overview. Because it is part of the session created by your server, the browser cannot manipulate it. Invalid characters yield 400 invalid_event_slug; session creation fails closed for a slug that matches no live event (404 event_not_found) or an event that is closed / not on sale (409 event_not_sellable). No session is issued and there is no fallback to the event overview.
startMode"articles" طريقة بدء الرحلة. يفتح "articles" (الافتراضي) اختيار المقالات؛ ويفتح "home" صفحة بداية الفعالية مع إجراءات الدخول (التذاكر، القسيمة، التشكيلات المتاحة)؛ ويفتح "voucher" نموذج رمز القسيمة أو التذكرة أولًا. يعمل نموذج القسيمة كما هو موضح في «استرداد قسيمة / تسجيل تذكرة». بدون eventSlug يظهر اختيار الفعالية أولًا ويُطبَّق الوضع على الفعالية التي يختارها المشتري.
assortmentstring · optionalRestricts the article start to one assortment of the pinned event (requires eventSlug; ignored for startMode "voucher"). Give the assortment id or its exact name (case-insensitive); hidden assortments are allowed. A selector that matches nothing shows an explicit error in the micro-frontend instead of silently falling back to the full list. Max. 200 characters, else 400 invalid_assortment.
voucherCodestring · optionalرمز قسيمة/تذكرة يسلّمه خادمك مع الجلسة، ويسترده التطبيق افتراضيًا في رحلة جديدة (بعد اختيار الفعالية إذا لم تكن محددة). يظهر الرفض في النموذج أو في إشعار السلة الأولى. مع redeemVoucherOnMint: true يُحجز الرمز على الخادم قبل نجاح إصدار الجلسة، لفعالية محددة ومستخدم معروف. طوله من 1 إلى 100 حرف دون فراغات أو فواصل URL، وإلا 400 invalid_voucher_code.
redeemVoucherOnMintboolean · optional (default false)اضبطه على true فقط عند إنشاء الجلسة من خادم إلى خادم مع voucherCode وeventSlug محدد وبريد إلكتروني لمستخدم معروف وIdempotency-Key. ينشئ الخادم سلة للمستخدم ويحجز القسيمة قبل إصدار الجلسة؛ وتستخدم الواجهة السلة نفسها دون إعادة إنشاء أو حجز. يدعم كلا وضعي البدء ورموز القسائم/التذاكر فقط، ولا يدعم PromotionCode. من دون هذا الخيار يُطبَّق التسليم العادي لـ voucherCode. التسجيل والشراء لازمان لاحقًا.

إنشاء جلسة: الرد (200)

مجاليكتبمعنى
sessionTokenstringرمز غير شفاف (sess_...). قم بتسليمها إلى المتصفح وتمريرها إلى mount() كـ sessionToken؛ ترسلها الواجهة الأمامية الصغيرة كجلسة X-Aditus في كل مكالمة متجر. لا يحتوي على بيانات المستخدم ولا يمكن فك تشفيره.
expiresAtnumberانتهاء الصلاحية كطابع زمني لنظام Unix بالمللي ثانية. معلوماتية بحتة لجدولتك الخاصة - تتفاعل الواجهة الأمامية الصغيرة مع 401 من تلقاء نفسها.
voucherRedeemedboolean · only with redeemVoucherOnMintتكون القيمة true عند نجاح إنشاء جلسة بقسيمة محجوزة. تمتلك الجلسة سلة فيها الحجز وتستخدمها الواجهة المضمّنة؛ لم يكتمل تسجيل التذكرة بعد.

إلغاء الجلسة

DELETE /api/shop/session/:token بنفس سر الحامل - يجب أن ينتمي السر إلى العميل الذي أُنشئت الجلسة من أجله. قم باستدعائه عند تسجيل الخروج حتى يموت الرمز المميز مع جلستك الخاصة. الاستجابة: { "تم إبطاله": صحيح } (أو خطأ إذا كانت الجلسة قد انتهت بالفعل). الإلغاء أمر غير فعال وآمن للحرق والنسيان. (Bearer)

Server-to-server reads

A backend that drives the micro-frontend (a kiosk shell, an onsite shop) usually needs tenant facts before it creates a session: which events exist and are on sale, which languages the shop speaks, which lookup lists and branding apply. Four read endpoints answer that with the same credentials as session creation (Bearer mint secret + X-Aditus-Public-Key header, same rate limit, Cache-Control: no-store). They are whitelisted projections of the Shop API, never a raw passthrough; ?language= (or Accept-Language, default de) picks the language of names. Errors: 400 invalid_public_key, 401 unauthorized, 503 shop_api_not_configured, 502 upstream_error.

EndpointReturns
GET /api/shop/events?language=deEvery event of the tenant: id, slug, name, year, start/end, location, saleState (open · registration_only · closed), the raw flags, showInOverview, languages, icon/logo URLs. Pick the slug for the session from here.
GET /api/shop/configurationTenant facts: mandator, the lookup list names (salutations, countries, titles, cookie consent) and websiteMetricTracking. Login-provider credentials of the tenant are deliberately never included.
GET /api/shop/languagessystemLanguage بالإضافة إلى قائمة لغات المتجر ({ code, name }). هذه هي القيم التي تقبلها سمة culture في الواجهة المصغّرة.
GET /api/shop/branding?images=ONSITE_SHOP_LOGO,…The tenant's Shop theme: styles as a flat key → value map, the theme images with URLs, and (for up to 40 image keys you name (A–Z, 0–9, _ , -)) whether each key exists, its public URL and content type. Image URLs need no auth and can be used directly in your shell. Add &eventSlug=<slug> or &event=<GUID> to scope theme and keys to one event (event-level overrides, event-only keys such as ONSITE_SHOP_HEADER / ONSITE_SHOP_ATTRACT_*); the answer then names the resolved event. Unknown slug → 404 event_not_found, non-GUID → 400 invalid_event, Shop API down → 503 event_unavailable.

الأخطاء واضحة

حالةشفرةمعنى
503session_not_configuredجلسات التضمين غير مهيأة لهذا العميل.
401unauthorizedقيمة mint secret المستخدمة لإنشاء الجلسة غير صالحة أو مفقودة.
400invalid_public_keyقيمة public key المقدمة غير صالحة.
400invalid_emailعنوان البريد الإلكتروني المقدم غير صالح.
400invalid_tokenقيمة session token المقدمة غير صالحة.
429rate_limitedعدد الطلبات كبير جداً. حاولوا مجدداً بعد قليل.
403public_key_invalidتنسيق public key المقدمة غير صالح.
403public_key_unknownلم يتم التعرف على public key المقدمة.
403public_key_origin_unresolvedتعذر تحديد مصدر هذا الطلب.
403public_key_domain_not_allowedالنطاق الذي صدر منه هذا الطلب غير مسموح له باستخدام public key هذه.
401invalid_sessionالجلسة المقدمة غير صالحة.
401session_expiredانتهت صلاحية الجلسة المقدمة.
403session_requires_public_keyتتطلب هذه الجلسة public key.
403session_key_mismatchلا تتطابق public key مع الجلسة.
403anonymous_sessionهذه العملية غير متاحة لجلسة مجهولة الهوية.

الآن اجعلها لك

تم تعيين الهوية - بعد ذلك، قم بوضع علامة تجارية على الواجهة الأمامية الصغيرة. تقوم أداة التصميم بإنشاء تكوين سمة جاهز للصقه وتشرح كل معلمة، مع معاينة مباشرة.

افتح أداة التصميم

هل تعمل مع وكيل برمجة؟ سلّمه دليل تكامل الواجهة الأمامية الصغيرة القابل للقراءة آليًا. انسخ عنوان URI الأساسي أدناه والصقه لدى وكيلك.

https://developers.aditus.com/ai/micro-frontend.md