Checkoutبالتفصيل.
أعقد جزء في Magento وأكتر واحد بيلمس كل الطبقات. هنفهم المعمارية (quote → order، الـ Knockout، الطبقات)، وبعدين الجانب العملي (إزاي تضيف خطوة، حقل، أو تعدّل shipping/payment).
الـ checkout في Magento 2 بيشتغل على Quote، وهو السلة المؤقتة اللي فيها المنتجات والعناوين وطرق الشحن والدفع والإجماليات. لما العميل يدوس Place Order، البيانات بتتنسخ لـ Order دائم. الواجهة مبنية بـ Knockout.js و UI Components، والإجماليات بتتحسب بـ total collectors، والتخصيص بيتم بالـ layout والـ JS components والـ plugins.
الصورة الكاملة
إيه اللي بيحصل من "أضف للسلة" لحد "تم الطلب".
طول الـ checkout، أنت بتتعامل مع Quote (سلة مؤقتة). لحظة "Place Order" بس، الـ Quote بيتحوّل لـ Order (طلب دائم). ده أهم تحوّل في الرحلة كلها.
الـ Quote — قلب الـ checkout
كل حاجة في الـ checkout بتدور حوالين الـ Quote. افهمه كويس.
الـ Quote هو تمثيل السلة/الطلب المؤقت قبل التأكيد. بيحمل كل شيء: المنتجات، الكميات، عنوان الشحن، طريقة الدفع، الخصومات، والـ totals.
- Quote — الكيان الرئيسي (quote table).
- Quote Items — المنتجات في السلة.
- Quote Address — عناوين الشحن والفواتير.
- Quote Payment — طريقة الدفع المختارة.
لأن السلة بتتغيّر كتير (زوّد، شيل، غيّر عنوان). لو كانت order من الأول، هيبقى فوضى من الطلبات الناقصة. الـ Quote بيسمح بالتغيير الحر، والـ Order بيتعمل مرة واحدة عند التأكيد.
Quote → Order
أهم لحظة: تحويل السلة المؤقتة لطلب دائم.
- الـ QuoteManagement::placeOrder() بيتنادى.
- الـ Quote data بتتنسخ لـ Order (منتجات، عناوين، totals).
- الـ payment بيتنفّذ (authorize/capture).
- الـ inventory بيتحجز/يتخصم (reservations).
- الـ Quote بيتعلّم كـ inactive، والـ Order بيتحفظ.
- events بتتطلق (sales_order_place_after) للـ observers.
الـ Order بيتكوّن من Order، Order Items، Order Address، Order Payment — بنية موازية للـ Quote بس دائمة. وبعدها بتيجي الـ Invoice وShipment وCredit Memo.
الـ Frontend (Knockout)
ليه الـ checkout مختلف عن باقي الصفحات في Magento.
الـ checkout مبني كـ single-page application (SPA) باستخدام Knockout.js. مش بيعمل reload للصفحة — بيحدّث أجزاء منها ديناميكيًا وبيتواصل مع الـ backend بالـ AJAX/REST.
- checkout_index_index.xml — الـ layout اللي بيعرّف بنية الـ checkout كلها.
- JS Components — ملفات Knockout بتدير كل جزء (shipping, payment, summary).
- HTML Templates — قوالب .html للـ UI (مش phtml).
- LayoutProcessor — الـ class اللي بيبني الـ JS layout config من الـ PHP.
Totals & Collectors
إزاي الأسعار النهائية بتتحسب (subtotal, shipping, tax, discount).
الـ totals (الإجماليات) بتتحسب عن طريق سلسلة من الـ collectors — كل واحد مسؤول عن جزء: subtotal، shipping، tax، discount، grand total. بيشتغلوا بترتيب معيّن.
عشان كل نوع حساب معزول وقابل للإضافة. لو عايز تضيف رسم مخصّص (زي رسوم توصيل خاصة)، بتضيف collector جديد من غير ما تلمس الباقي — تطبيق للـ Open/Closed principle.
<!-- register a custom total collector --> <section name="quote"> <group name="totals"> <item name="custom_fee" instance="Vendor\Module\Model\Total\CustomFee" sort_order="350"/> </group> </section>
تخصيص: إضافة حقل
أشهر مطلب: إضافة حقل جديد (زي "تعليمات التوصيل").
- LayoutProcessor plugin — تضيف الحقل في الـ JS layout config.
- الحقل يظهر في الـ shipping أو payment step عن طريق الـ config ده.
- extension_attributes.xml — تعرّف attribute جديد على الـ Quote/Address.
- JS: set-shipping-information — تعدّل عشان تبعت قيمة الحقل للـ backend.
- Plugin على الـ backend — تحفظ القيمة على الـ Quote.
- نقل للـ Order — observer/plugin بينقل القيمة من Quote للـ Order عند التحويل.
public function process($jsLayout) { $jsLayout['components']['checkout'] ['children']['steps']...['delivery_note'] = [ 'component' => 'Magento_Ui/js/form/element/abstract', 'config' => [ 'customScope' => 'shippingAddress', 'template' => 'ui/form/field', 'elementTmpl' => 'ui/form/element/input', ], 'label' => __('Delivery Note'), ]; return $jsLayout; }
تخصيص: إضافة خطوة
إضافة step كاملة جديدة (زي "gift options" أو "تأكيد").
- checkout_index_index.xml — تسجّل الـ step الجديدة في الـ layout.
- JS Component — يورّث من uiComponent ويعرّف الـ step.
- HTML template — الـ UI بتاع الخطوة.
- sortOrder — يحدد مكان الخطوة في التسلسل.
- navigation logic — الانتقال من وللخطوة.
return Component.extend({ defaults: { template: 'Vendor_Module/custom-step' }, // register this step initialize: function () { this._super(); registry.set(this.name, { isVisible: observable(true) }); return this; }, navigate: function () { /* go to step */ }, sortOrder: 15 });
تخصيص: Shipping & Payment
إضافة طريقة شحن أو دفع مخصّصة.
تعمل carrier class بيورّث من AbstractCarrier وبيطبّق CarrierInterface. الـ method collectRates() بترجّع الأسعار المتاحة.
class Custom extends AbstractCarrier implements CarrierInterface { protected $_code = 'custom_shipping'; public function collectRates(RateRequest $request) { $result = $this->rateResultFactory->create(); $method = $this->rateMethodFactory->create(); $method->setPrice(10.00); $result->append($method); return $result; } }
طرق الدفع الحديثة بتستخدم Payment Provider Gateway. بتعرّف الـ method في payment.xml وconfig.xml، وتعمل JS component للـ frontend، وأحيانًا Command classes للـ authorize/capture.
- config.xml — إعدادات الـ method الافتراضية.
- payment JS renderer — الـ UI في الـ payment step.
- Gateway Commands — authorize, capture, refund.
- ACL + system.xml — إعدادات الأدمن.
الأداء وأسئلة الإنترفيو
تحسين أداء الـ checkout والأسئلة المتوقعة.
- JS bundling/minification — الـ checkout تقيل بالـ JS، فده بيفرق كتير.
- راجع الـ plugins على quote/totals — بتتنادى عشرات المرات، أي plugin تقيل بيتضاعف.
- قلّل الـ collectors المخصّصة التقيلة — كل collector بيشتغل مع كل تحديث.
- الـ estimate-shipping-methods — من أبطأ الـ APIs، راقبها.
- caching للبيانات الثابتة — زي طرق الشحن لو مش بتتغيّر كتير.
الـ Checkout بالتفصيل ✓
دلوقتي فاهم الـ checkout من الجذور: المعمارية (Quote → Order، Knockout، Collectors) والتخصيص العملي (حقل، خطوة، shipping، payment). ده أعقد جزء في Magento وأكتر واحد بيميّز المطوّر المحترف.
الخلاصة اللي تفتكرها: الـ Quote هو قلب الـ checkout، والتحويل لـ Order هو اللحظة الحاسمة، والتخصيص دايمًا بيلمس frontend (Knockout) و backend (Quote) مع بعض.
خلّصت الدرس؟علّمه عشان تتابع تقدّمك في الكورس.
أسئلة شائعة
ما الفرق بين Quote و Order في Magento 2؟
الـ Quote هو السلة المؤقتة طول الـ checkout، بيحمل المنتجات والكميات والعناوين وطريقة الدفع والخصومات والـ totals وبيتغيّر بحرية. الـ Order هو الطلب الدائم اللي بيتعمل لحظة Place Order بنسخ بيانات الـ Quote، ومبيتغيّرش بعدها. وجداول الـ Order في Magento flat مش EAV عشان الكتابة تبقى سريعة.
إيه اللي بيحصل لما العميل يدوس Place Order في Magento 2؟
بيتنادى QuoteManagement::placeOrder()، وبيانات الـ Quote (المنتجات والعناوين والـ totals) بتتنسخ لـ Order، والـ payment بيتنفّذ (authorize أو capture)، والمخزون بيتحجز عن طريق الـ reservations. بعدها الـ Quote بيتعلّم inactive والـ Order بيتحفظ، وevents زي sales_order_place_after بتتطلق للـ observers.
ليه الـ checkout في Magento 2 مختلف عن باقي الصفحات؟
لأن الـ checkout مش صفحة phtml عادية، ده single-page application مبني على Knockout.js والـ UI Components وبيكلّم الـ backend بالـ REST APIs من غير reload. بنيته متعرّفة في checkout_index_index.xml والـ UI في قوالب .html، وعشان كده أي تخصيص محتاج JavaScript مش PHP بس.
إزاي أضيف حقل جديد في الـ checkout في Magento 2؟
الحقل محتاج شغل frontend وbackend مع بعض: تضيفه في الـ JS layout عن طريق LayoutProcessor، وتعرّف extension attribute على الـ Quote/Address في extension_attributes.xml، وتعدّل الـ JS بتاع set-shipping-information عشان يبعت القيمة. بعدها plugin على الـ backend يحفظ القيمة على الـ Quote، وobserver أو plugin ينقلها للـ Order وقت التحويل.
إزاي بتتحسب الـ totals في Magento 2 checkout؟
عن طريق سلسلة total collectors، كل واحد مسؤول عن جزء: subtotal وshipping وtax وdiscount وgrand total، وبيشتغلوا بالترتيب حسب sort_order. ولو عايز رسم مخصّص بتسجّل collector جديد في etc/sales.xml تحت الـ section بتاع الـ quote من غير ما تلمس باقي الحسابات.
إزاي أعمل custom shipping method في Magento 2؟
بتعمل carrier class بيورّث من AbstractCarrier وبيطبّق CarrierInterface، وبتحدّد له $_code. والـ method collectRates(RateRequest $request) هي اللي بترجّع الأسعار المتاحة كـ result فيه الـ methods وسعر كل واحدة.
ليه الـ checkout في Magento 2 بطيء وإزاي أحسّن أداءه؟
غالبًا السبب JavaScript تقيل، أو plugins على الـ quote والـ totals بتتنادى عشرات المرات، أو collectors مخصّصة تقيلة بتشتغل مع كل تحديث. الحل: JS bundling وminification، مراجعة الـ plugins، تقليل الـ collectors التقيلة، مراقبة estimate-shipping-methods، وcaching للبيانات الثابتة زي طرق الشحن.