Today I am going to explain how to add custom fields to all steps of checkout page and save it after order is placed and also how to use posted data after placing order
1st fields delivery_date :- where customer will mention at the delivery date in shipping step
2nd fields order Comments :- will be in Payment step and after placing order this comments will be add to order comment history
Step 1 :- make sure delivery_date is added in all need table like quote, sales_order and sales_order_grid through install or upgrade script
<?php namespace Sugarcode\Deliverydate\Setup; use Magento\Framework\Setup\InstallSchemaInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\SchemaSetupInterface; /** * @codeCoverageIgnore */ class InstallSchema implements InstallSchemaInterface { /** * {@inheritdoc} * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { $installer = $setup; $installer->startSetup(); $installer->getConnection()->addColumn( $installer->getTable('quote'), 'delivery_date', [ 'type' => 'datetime', 'nullable' => false, 'comment' => 'Delivery Date', ] ); $installer->getConnection()->addColumn( $installer->getTable('sales_order'), 'delivery_date', [ 'type' => 'datetime', 'nullable' => false, 'comment' => 'Delivery Date', ] ); $installer->getConnection()->addColumn( $installer->getTable('sales_order_grid'), 'delivery_date', [ 'type' => 'datetime', 'nullable' => false, 'comment' => 'Delivery Date', ] ); $setup->endSetup(); } } Step 2 :- Adding custom fields in shipping and payment steps , we can achieve in two way one from layout xml and other one is plugin below is the way how to add the fields through plugin
We create a di.xml file in our module - Sugarcode/Deliverydate/etc/frontend/di.xml
We use the frontend area to keep it clean, our plugin should run only on the frontend.
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\Checkout\Block\Checkout\LayoutProcessor"> <plugin name="add-delivery-date-field" type="Sugarcode\Deliverydate\Plugin\Checkout\LayoutProcessorPlugin" sortOrder="10"/> </type> </config> Sugarcode\Plugin\Checkout\LayoutProcessor.php
<?php namespace Sugarcode\Plugin\Checkout; class LayoutProcessor { /** * @var \Magento\Framework\App\Config\ScopeConfigInterface */ protected $scopeConfig; /** * @var \Magento\Checkout\Model\Session */ protected $checkoutSession; /** * @var \Magento\Customer\Model\AddressFactory */ protected $customerAddressFactory; /** * @var \Magento\Framework\Data\Form\FormKey */ protected $formKey; public function __construct( \Magento\Framework\View\Element\Template\Context $context, \Magento\CheckoutAgreements\Model\ResourceModel\Agreement\CollectionFactory $agreementCollectionFactory, \Magento\Checkout\Model\Session $checkoutSession, \Magento\Customer\Model\AddressFactory $customerAddressFactory ) { $this->scopeConfig = $context->getScopeConfig(); $this->checkoutSession = $checkoutSession; $this->customerAddressFactory = $customerAddressFactory; } /** * @param \Magento\Checkout\Block\Checkout\LayoutProcessor $subject * @param array $jsLayout * @return array */ public function afterProcess( \Magento\Checkout\Block\Checkout\LayoutProcessor $subject, array $jsLayout ) { $jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children'] ['shippingAddress']['children']['before-form']['children']['delivery_date'] = [ 'component' => 'Magento_Ui/js/form/element/abstract', 'config' => [ 'customScope' => 'shippingAddress', 'template' => 'ui/form/field', 'elementTmpl' => 'ui/form/element/date', 'options' => [], 'id' => 'delivery-date' ], 'dataScope' => 'shippingAddress.delivery_date', 'label' => 'Delivery Date', 'provider' => 'checkoutProvider', 'visible' => true, 'validation' => [], 'sortOrder' => 200, 'id' => 'delivery-date' ]; $jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children'] ['payment']['children']['payments-list']['children']['before-place-order']['children']['comment'] = [ 'component' => 'Magento_Ui/js/form/element/textarea', 'config' => [ 'customScope' => 'shippingAddress', 'template' => 'ui/form/field', 'options' => [], 'id' => 'comment' ], 'dataScope' => 'ordercomment.comment', 'label' => 'Order Comment', 'notice' => __('Comments'), 'provider' => 'checkoutProvider', 'visible' => true, 'sortOrder' => 250, 'id' => 'comment' ]; return $jsLayout; } } Now all the fields are in checkout page , now how to save the data
unlike M1 in M2 all Checkout page is completely knockout JS and API
We have two steps first one is shipping and second step is payment where we need to save both fields
Below is how to save the data after shipping addresses are saved
Shipping Step
To save shipping information in M2 uses
app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/default.js
to prepare json and the call api so we need to override this js and in php side save will happened
\Magento\Checkout\Model\ShippingInformationManagement::SaveAddressInformation() and ShippingInformationManagement implemented by Magento\Checkout\Api\Data\ShippingInformationInterface
M2 has one powerful concept called extension_attributes which is used for dynamic data to core tables lets make it use of that
step 3 :- create a file Deliverydate/etc/extension_attributes.xml
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd"> <extension_attributes for="Magento\Quote\Api\Data\AddressInterface"> <attribute code="delivery_date" type="string"/> </extension_attributes> <extension_attributes for="Magento\Quote\Api\Data\PaymentInterface"> <attribute code="comment" type="string"/> </extension_attributes> </config> to override js create a js file Deliverydate/view/frontend/requirejs-config.js we need to use mixns
var config = { config: { mixins: { 'Magento_Checkout/js/action/place-order': { 'Sugarcode_Deliverydate/js/order/place-order-mixin': true }, 'Magento_Checkout/js/action/set-payment-information': { 'Sugarcode_Deliverydate/js/order/set-payment-information-mixin': true }, 'Magento_Checkout/js/action/set-shipping-information': { 'Sugarcode_Deliverydate/js/order/set-shipping-information-mixin': true } } }; js/order/set-shipping-information-mixin.js delivery_date
/** * @author aakimov */ /*jshint browser:true jquery:true*/ /*global alert*/ define([ 'jquery', 'mage/utils/wrapper', 'Magento_Checkout/js/model/quote' ], function ($, wrapper, quote) { 'use strict'; return function (setShippingInformationAction) { return wrapper.wrap(setShippingInformationAction, function (originalAction) { var shippingAddress = quote.shippingAddress(); if (shippingAddress['extension_attributes'] === undefined) { shippingAddress['extension_attributes'] = {}; } // you can extract value of extension attribute from any place (in this example I use customAttributes approach) shippingAddress['extension_attributes']['delivery_date'] = jQuery('[name="delivery_date"]').val(); // pass execution to original action ('Magento_Checkout/js/action/set-shipping-information') return originalAction(); }); }; }); The next step is saving this custom field post data to the quote. Let's make another plugin by adding an xml node in our etc/di.xml
<type name="Magento\Checkout\Model\ShippingInformationManagement"> <plugin name="save-in-quote" type="Sugarcode\Deliverydate\Plugin\Checkout\ShippingInformationManagementPlugin" sortOrder="10"/> </type> Create a file Sugarcode\Deliverydate\Plugin\Checkout\ShippingInformationManagementPlugin.php
<?php namespace Sugarcode\Deliverydate\Plugin\Checkout; class ShippingInformationManagementPlugin { protected $quoteRepository; public function __construct( \Magento\Quote\Model\QuoteRepository $quoteRepository ) { $this->quoteRepository = $quoteRepository; } /** * @param \Magento\Checkout\Model\ShippingInformationManagement $subject * @param $cartId * @param \Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation */ public function beforeSaveAddressInformation( \Magento\Checkout\Model\ShippingInformationManagement $subject, $cartId, \Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation ) { $extAttributes = $addressInformation->getShippingAddress()->getExtensionAttributes(); $deliveryDate = $extAttributes->getDeliveryDate(); $quote = $this->quoteRepository->getActive($cartId); $quote->setDeliveryDate($deliveryDate); } } soon after when you move to payment steps data will be saved in quote table
to save same data after placing order we need to use fieldset
etc/fieldset.xml
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Object/etc/fieldset.xsd"> <scope id="global"> <fieldset id="sales_convert_quote"> <field name="delivery_date"> <aspect name="to_order"/> </field> </fieldset> </scope> </config> Now lets Save the payment steps field
if we have extra fields in payment step and we need to post that data then we need to override the other js as we done for shipping step
as like shipping information we have payment information
ww can achive by override is Magento_Checkout/js/action/place-order.js (but it will have issue when agreement is enabled so we need to use mixins as mention in re)
Sugarcode_Deliverydate/js/order/place-order-mixin.js /** * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ define([ 'jquery', 'mage/utils/wrapper', 'Sugarcode_Deliverydate/js/order/ordercomment-assigner' ], function ($, wrapper, ordercommentAssigner) { 'use strict'; return function (placeOrderAction) { /** Override default place order action and add comments to request */ return wrapper.wrap(placeOrderAction, function (originalAction, paymentData, messageContainer) { ordercommentAssigner(paymentData); return originalAction(paymentData, messageContainer); }); }; }); Sugarcode_Deliverydate/js/order/ordercomment-assigner.js
/*jshint browser:true jquery:true*/ /*global alert*/ define([ 'jquery' ], function ($) { 'use strict'; /** Override default place order action and add comment to request */ return function (paymentData) { if (paymentData['extension_attributes'] === undefined) { paymentData['extension_attributes'] = {}; } paymentData['extension_attributes']['comment'] = jQuery('[name="ordercomment[comment]"]').val(); }; }); Sugarcode_Deliverydate/js/order/set-payment-information-mixin.js
/*jshint browser:true jquery:true*/ /*global alert*/ define([ 'jquery', 'mage/utils/wrapper', 'Sugarcode_Deliverydate/js/order/ordercomment-assigner' ], function ($, wrapper, ordercommentAssigner) { 'use strict'; return function (placeOrderAction) { /** Override place-order-mixin for set-payment-information action as they differs only by method signature */ return wrapper.wrap(placeOrderAction, function (originalAction, messageContainer, paymentData) { ordercommentAssigner(paymentData); return originalAction(messageContainer, paymentData); }); }; }); and need to create a plugin for Magento\Checkout\Model\PaymentInformationManagement
so in etc/di add below code
<type name="Magento\Checkout\Model\PaymentInformationManagement"> <plugin name="order_comments_save-in-order" type="Sugarcode\Deliverydate\Plugin\Checkout\PaymentInformationManagementPlugin" sortOrder="10"/> </type> and then create a file Sugarcode\Deliverydate\Plugin\Checkout\PaymentInformationManagementPlugin.php