Magento 2 Add dropdown list to shipping methodHide shipping method when another availableHow to Select Shipping method before start shopping in magento?OnePage Checkout - hide payment method depending on matrixrate shipping method with multiple codesMagento 2 additional data to shipping methodMagento 2 add custom data to shipping methodSave a custom attribute from shipping method to quote_addressNeed to get additional data in custom shipping method Magento 2?update shipping address for shipping method changeHow to add dropdown in checkout page after shipping method in Magento 2Remove Certain Shipping method on estimate and checkout

C-152 carb heat on before landing in hot weather?

Fill NAs in R with zero if the next valid data point is more than 2 intervals away

Should my manager be aware of private LinkedIn approaches I receive? How to politely have this happen?

Swapping rooks in a 4x4 board

Using sed to replace "A" with a "B" or "C"

What is the mechanical difference between the Spectator's Create Food and Water action and the Banshee's Undead Nature Trait?

Intuition for capacitors in series

How long would it take to cross the Channel in 1890's?

Does squid ink pasta bleed?

Find the C-factor of a vote

Why doesn't a marching band have strings?

How risky is real estate?

If I wouldn't want to read the story, is writing it still a good idea?

Is my Rep in Stack-Exchange Form?

Cascading Repair Costs following Blown Head Gasket on a 2004 Subaru Outback

Can humans ever directly see a few photons at a time? Can a human see a single photon?

How does a blind passenger not die, if driver becomes unconscious

Why is the voltage measurement of this circuit different when the switch is on?

Accidentals and ties

Can White Castle?

Is this one of the engines from the 9/11 aircraft?

How do I set an alias to a terminal line?

Does Marvel have an equivalent of the Green Lantern?

Inverse-quotes-quine



Magento 2 Add dropdown list to shipping method


Hide shipping method when another availableHow to Select Shipping method before start shopping in magento?OnePage Checkout - hide payment method depending on matrixrate shipping method with multiple codesMagento 2 additional data to shipping methodMagento 2 add custom data to shipping methodSave a custom attribute from shipping method to quote_addressNeed to get additional data in custom shipping method Magento 2?update shipping address for shipping method changeHow to add dropdown in checkout page after shipping method in Magento 2Remove Certain Shipping method on estimate and checkout






.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;








16















I develop shipping method for some logistic company. This company has many offices where customer can get his order. I can get offices list by сity in API but i don't now how better represent this step?



For now i just set new MagentoQuoteModelQuoteAddressRateResultMethod
to every office in town, in big town it's count > 100 and i think it's not very good to set 100 lines in checkout.



It will be public module for different checkout design so how can I render near selected my shipping method some dropdown list with list of offices and set price and method after user select one.










share|improve this question
























  • @Zefiryn I found this post very interesting, but I have a question, if I have to show in the select not the offices but the stores that are inside Amasty's module, how I would do the second part of your post? I mean: where is the place where I call the helper of Amasty to fill up the xml component "vendor_carrier_form"? Thanks

    – maverickk89
    Sep 3 '18 at 14:35












  • If you have a new question, please ask it by clicking the Ask Question button. Include a link to this question if it helps provide context. - From Review

    – Jai
    Sep 3 '18 at 16:11











  • this is not a new question but a variation of the way used by Zefiryn... because I used the first part of the post as it is

    – maverickk89
    Sep 4 '18 at 7:28

















16















I develop shipping method for some logistic company. This company has many offices where customer can get his order. I can get offices list by сity in API but i don't now how better represent this step?



For now i just set new MagentoQuoteModelQuoteAddressRateResultMethod
to every office in town, in big town it's count > 100 and i think it's not very good to set 100 lines in checkout.



It will be public module for different checkout design so how can I render near selected my shipping method some dropdown list with list of offices and set price and method after user select one.










share|improve this question
























  • @Zefiryn I found this post very interesting, but I have a question, if I have to show in the select not the offices but the stores that are inside Amasty's module, how I would do the second part of your post? I mean: where is the place where I call the helper of Amasty to fill up the xml component "vendor_carrier_form"? Thanks

    – maverickk89
    Sep 3 '18 at 14:35












  • If you have a new question, please ask it by clicking the Ask Question button. Include a link to this question if it helps provide context. - From Review

    – Jai
    Sep 3 '18 at 16:11











  • this is not a new question but a variation of the way used by Zefiryn... because I used the first part of the post as it is

    – maverickk89
    Sep 4 '18 at 7:28













16












16








16


11






I develop shipping method for some logistic company. This company has many offices where customer can get his order. I can get offices list by сity in API but i don't now how better represent this step?



For now i just set new MagentoQuoteModelQuoteAddressRateResultMethod
to every office in town, in big town it's count > 100 and i think it's not very good to set 100 lines in checkout.



It will be public module for different checkout design so how can I render near selected my shipping method some dropdown list with list of offices and set price and method after user select one.










share|improve this question
















I develop shipping method for some logistic company. This company has many offices where customer can get his order. I can get offices list by сity in API but i don't now how better represent this step?



For now i just set new MagentoQuoteModelQuoteAddressRateResultMethod
to every office in town, in big town it's count > 100 and i think it's not very good to set 100 lines in checkout.



It will be public module for different checkout design so how can I render near selected my shipping method some dropdown list with list of offices and set price and method after user select one.







magento-2.1 checkout shipping-methods






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Mar 14 '17 at 8:19









Siarhey Uchukhlebau

10.4k9 gold badges31 silver badges59 bronze badges




10.4k9 gold badges31 silver badges59 bronze badges










asked Jan 9 '17 at 17:45









Jamess MoriartyJamess Moriarty

811 silver badge4 bronze badges




811 silver badge4 bronze badges












  • @Zefiryn I found this post very interesting, but I have a question, if I have to show in the select not the offices but the stores that are inside Amasty's module, how I would do the second part of your post? I mean: where is the place where I call the helper of Amasty to fill up the xml component "vendor_carrier_form"? Thanks

    – maverickk89
    Sep 3 '18 at 14:35












  • If you have a new question, please ask it by clicking the Ask Question button. Include a link to this question if it helps provide context. - From Review

    – Jai
    Sep 3 '18 at 16:11











  • this is not a new question but a variation of the way used by Zefiryn... because I used the first part of the post as it is

    – maverickk89
    Sep 4 '18 at 7:28

















  • @Zefiryn I found this post very interesting, but I have a question, if I have to show in the select not the offices but the stores that are inside Amasty's module, how I would do the second part of your post? I mean: where is the place where I call the helper of Amasty to fill up the xml component "vendor_carrier_form"? Thanks

    – maverickk89
    Sep 3 '18 at 14:35












  • If you have a new question, please ask it by clicking the Ask Question button. Include a link to this question if it helps provide context. - From Review

    – Jai
    Sep 3 '18 at 16:11











  • this is not a new question but a variation of the way used by Zefiryn... because I used the first part of the post as it is

    – maverickk89
    Sep 4 '18 at 7:28
















@Zefiryn I found this post very interesting, but I have a question, if I have to show in the select not the offices but the stores that are inside Amasty's module, how I would do the second part of your post? I mean: where is the place where I call the helper of Amasty to fill up the xml component "vendor_carrier_form"? Thanks

– maverickk89
Sep 3 '18 at 14:35






@Zefiryn I found this post very interesting, but I have a question, if I have to show in the select not the offices but the stores that are inside Amasty's module, how I would do the second part of your post? I mean: where is the place where I call the helper of Amasty to fill up the xml component "vendor_carrier_form"? Thanks

– maverickk89
Sep 3 '18 at 14:35














If you have a new question, please ask it by clicking the Ask Question button. Include a link to this question if it helps provide context. - From Review

– Jai
Sep 3 '18 at 16:11





If you have a new question, please ask it by clicking the Ask Question button. Include a link to this question if it helps provide context. - From Review

– Jai
Sep 3 '18 at 16:11













this is not a new question but a variation of the way used by Zefiryn... because I used the first part of the post as it is

– maverickk89
Sep 4 '18 at 7:28





this is not a new question but a variation of the way used by Zefiryn... because I used the first part of the post as it is

– maverickk89
Sep 4 '18 at 7:28










4 Answers
4






active

oldest

votes


















16





+50









Magento checkout does not support any kind of form for shipping method additional data. But it provides shippingAdditional block in the checkout which can be used for this. The following solution will work for standard magento checkout.



First let's prepare our container where we can put some form. To do this create a file in view/frontend/layout/checkout_index_index.xml



<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="checkout.root">
<arguments>
<argument name="jsLayout" xsi:type="array">
<item name="components" xsi:type="array">
<item name="checkout" xsi:type="array">
<item name="children" xsi:type="array">
<item name="steps" xsi:type="array">
<item name="children" xsi:type="array">
<item name="shipping-step" xsi:type="array">
<item name="children" xsi:type="array">
<item name="shippingAddress" xsi:type="array">
<item name="children" xsi:type="array">
<item name="shippingAdditional" xsi:type="array">
<item name="component" xsi:type="string">uiComponent</item>
<item name="displayArea" xsi:type="string">shippingAdditional</item>
<item name="children" xsi:type="array">
<item name="vendor_carrier_form" xsi:type="array">
<item name="component" xsi:type="string">Vendor_Module/js/view/checkout/shipping/form</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</argument>
</arguments>
</referenceBlock>
</body>
</page>


Now create a file in Vendor/Module/view/frontend/web/js/view/checkout/shipping/form.js which will render a knockout template. Its content looks like this



define([
'jquery',
'ko',
'uiComponent',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/model/shipping-service',
'Vendor_Module/js/view/checkout/shipping/office-service',
'mage/translate',
], function ($, ko, Component, quote, shippingService, officeService, t)
'use strict';

return Component.extend(
defaults:
template: 'Vendor_Module/checkout/shipping/form'
,

initialize: function (config)
this.offices = ko.observableArray();
this.selectedOffice = ko.observable();
this._super();
,

initObservable: function ()
this._super();

this.showOfficeSelection = ko.computed(function()
return this.ofices().length != 0
, this);

this.selectedMethod = ko.computed(function()
var method = quote.shippingMethod();
var selectedMethod = method != null ? method.carrier_code + '_' + method.method_code : null;
return selectedMethod;
, this);

quote.shippingMethod.subscribe(function(method)
var selectedMethod = method != null ? method.carrier_code + '_' + method.method_code : null;
if (selectedMethod == 'carrier_method')
this.reloadOffices();

, this);

this.selectedOffice.subscribe(function(office)
if (quote.shippingAddress().extensionAttributes == undefined)
quote.shippingAddress().extensionAttributes = ;

quote.shippingAddress().extensionAttributes.carrier_office = office;
);


return this;
,

setOfficeList: function(list)
this.offices(list);
,

reloadOffices: function()
officeService.getOfficeList(quote.shippingAddress(), this);
var defaultOffice = this.offices()[0];
if (defaultOffice)
this.selectedOffice(defaultOffice);

,

getOffice: function()
var office;
if (this.selectedOffice())
for (var i in this.offices())
var m = this.offices()[i];
if (m.name == this.selectedOffice())
office = m;



else
office = this.offices()[0];


return office;
,

initSelector: function()
var startOffice = this.getOffice();

);
);


This file uses knockout template which should be placed in Vendor/Module/view/frontend/web/template/checkout/shipping/form.html



<div id="carrier-office-list-wrapper" data-bind="visible: selectedMethod() == 'carrier_method'">
<p data-bind="visible: !showOfficeSelection(), i18n: 'Please provide postcode to see nearest offices'"></p>
<div data-bind="visible: showOfficeSelection()">
<p>
<span data-bind="i18n: 'Select pickup office.'"></span>
</p>
<select id="carrier-office-list" data-bind="options: offices(),
value: selectedOffice,
optionsValue: 'name',
optionsText: function(item)return item.location + ' (' + item.name +')';">
</select>
</div>
</div>


We have now a select field that will be visible when our method (defined by its code) will be selected in the shipping methods table. Time to fill it with some options. Since values are dependent on the address the best way is to create rest endpoint that will provide available options. In Vendor/Module/etc/webapi.xml



<?xml version="1.0"?>

<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">

<!-- Managing Office List on Checkout page -->
<route url="/V1/module/get-office-list/:postcode/:city" method="GET">
<service class="VendorModuleApiOfficeManagementInterface" method="fetchOffices"/>
<resources>
<resource ref="anonymous" />
</resources>
</route>
</routes>


Now define interface in Vendor/Module/Api/OfficeManagementInterface.php as



namespace VendorModuleApi;

interface OfficeManagementInterface


/**
* Find offices for the customer
*
* @param string $postcode
* @param string $city
* @return VendorModuleApiDataOfficeInterface[]
*/
public function fetchOffices($postcode, $city);



Define interface for office data in VendorModuleApiDataOfficeInterface.php. This interface will be used by webapi module to filter data for the output so you need to define whatever you need to add into the response.



namespace VendorModuleApiData;

/**
* Office Interface
*/
interface OfficeInterface

/**
* @return string
*/
public function getName();

/**
* @return string
*/
public function getLocation();



Time for actual classes. Start with creating preferences for all interfaces in Vendor/Module/etc/di.xml



<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="VendorModuleApiOfficeManagementInterface" type="VendorModuleModelOfficeManagement" />
<preference for="VendorModuleApiDataOfficeInterface" type="VendorModuleModelOffice" />
</config>


Now create VendorModuleModelOfficeManagement.php class that will actually do the logic of fetching the data.



namespace VednorModuleModel;

use VednorModuleApiOfficeManagementInterface;
use VednorModuleApiDataOfficeInterfaceFactory;

class OfficeManagement implements OfficeManagementInterface

protected $officeFactory;

/**
* OfficeManagement constructor.
* @param OfficeInterfaceFactory $officeInterfaceFactory
*/
public function __construct(OfficeInterfaceFactory $officeInterfaceFactory)

$this->officeFactory = $officeInterfaceFactory;


/**
* Get offices for the given postcode and city
*
* @param string $postcode
* @param string $limit
* @return VendorModuleApiDataOfficeInterface[]
*/
public function fetchOffices($postcode, $city)

$result = [];
for($i = 0, $i < 4;$i++)
$office = $this->officeFactory->create();
$office->setName("Office $i");
$office->setLocation("Address $i");
$result[] = $office;


return $result;




And finally class for OfficeInterface in Vendor/Module/Model/Office.php



namespace VendorModuleModel;

use MagentoFrameworkDataObject;
use VendorModuleApiDataOfficeInterface;

class Office extends DataObject implements OfficeInterface

/**
* @return string
*/
public function getName()

return (string)$this->_getData('name');


/**
* @return string
*/
public function getLocation()

return (string)$this->_getData('location');




This should show select field and update it when address is changed. But we are missing one more element for frontend manipulation. We need to create function that will call the endpoint. Call to it is already included in Vendor/Module/view/frontend/web/js/view/checkout/shipping/form.js and it is Vendor_Module/js/view/checkout/shipping/office-service class which should go to Vendor/Module/view/frontend/web/js/view/checkout/shipping/office-service.js with the following code:



define(
[
'Vendor_Module/js/view/checkout/shipping/model/resource-url-manager',
'Magento_Checkout/js/model/quote',
'Magento_Customer/js/model/customer',
'mage/storage',
'Magento_Checkout/js/model/shipping-service',
'Vendor_Module/js/view/checkout/shipping/model/office-registry',
'Magento_Checkout/js/model/error-processor'
],
function (resourceUrlManager, quote, customer, storage, shippingService, officeRegistry, errorProcessor)
'use strict';

return
/**
* Get nearest machine list for specified address
* @param Object address
*/
getOfficeList: function (address, form)
shippingService.isLoading(true);
var cacheKey = address.getCacheKey(),
cache = officeRegistry.get(cacheKey),
serviceUrl = resourceUrlManager.getUrlForOfficeList(quote);

if (cache)
form.setOfficeList(cache);
shippingService.isLoading(false);
else
storage.get(
serviceUrl, false
).done(
function (result)
officeRegistry.set(cacheKey, result);
form.setOfficeList(result);

).fail(
function (response)
errorProcessor.process(response);

).always(
function ()
shippingService.isLoading(false);

);


;

);


It uses 2 more js files. Vendor_Module/js/view/checkout/shipping/model/resource-url-manager creates a url to the endpoint and is pretty simple



define(
[
'Magento_Customer/js/model/customer',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/model/url-builder',
'mageUtils'
],
function(customer, quote, urlBuilder, utils)
"use strict";
return
getUrlForOfficeList: function(quote, limit)
var params = postcode: quote.shippingAddress().postcode, city: quote.shippingAddress().city;
var urls =
'default': '/module/get-office-list/:postcode/:city'
;
return this.getUrl(urls, params);
,

/** Get url for service */
getUrl: function(urls, urlParams)
var url;

if (utils.isEmpty(urls))
return 'Provided service call does not exist.';


if (!utils.isEmpty(urls['default']))
url = urls['default'];
else
url = urls[this.getCheckoutMethod()];

return urlBuilder.createUrl(url, urlParams);
,

getCheckoutMethod: function()
return customer.isLoggedIn() ? 'customer' : 'guest';

;

);


Vendor_Module/js/view/checkout/shipping/model/office-registry is a way of keeping result in the local storage. Its code is:



define(
[],
function()
"use strict";
var cache = [];
return
get: function(addressKey)
if (cache[addressKey])
return cache[addressKey];

return false;
,
set: function(addressKey, data)
cache[addressKey] = data;

;

);


Ok, so we should have all working on frontend. But now there is another problem to resolve. Since checkout doesn't know anything about this form it won't send the selection result to the backend. To make this happen we need to use extension_attributes feature. This is a way in magento2 to inform the system that some additional data are expected to be in the rest calls. Without it magento would filter out those data and they would never reach the code.



So first in Vendor/Module/etc/extension_attributes.xml define:



<?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="MagentoQuoteApiDataAddressInterface">
<attribute code="carrier_office" type="string"/>
</extension_attributes>
</config>


This value is already inserted in the request in form.js by this.selectedOffice.subscribe() definition. So the above configuration will only pass it at the entrance. To fetch it in the code create a plugin in Vendor/Module/etc/di.xml



<type name="MagentoQuoteModelQuoteAddress">
<plugin name="inpost-address" type="VendorModuleQuoteAddressPlugin" sortOrder="1" disabled="false"/>
</type>


Inside that class



namespace VendorModulePluginQuote;

use MagentoQuoteModelQuoteAddress;
use VendorModuleModelCarrier;

class AddressPlugin

/**
* Hook into setShippingMethod.
* As this is magic function processed by __call method we need to hook around __call
* to get the name of the called method. after__call does not provide this information.
*
* @param Address $subject
* @param callable $proceed
* @param string $method
* @param mixed $vars
* @return Address
*/
public function around__call($subject, $proceed, $method, $vars)

$result = $proceed($method, $vars);
if ($method == 'setShippingMethod'
&& $vars[0] == Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
&& $subject->getExtensionAttributes()
&& $subject->getExtensionAttributes()->getCarrierOffice()
)
$subject->setCarrierOffice($subject->getExtensionAttributes()->getCarrierOffice());

elseif (
$method == 'setShippingMethod'
&& $vars[0] != Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
)
//reset office when changing shipping method
$subject->getCarrierOffice(null);

return $result;




Of course where you will save the value depends entirely on your requirements. The above code would require creating additional column carrier_office in quote_address and sales_address tables and an event (in Vendor/Module/etc/events.xml)



<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="sales_model_service_quote_submit_before">
<observer name="copy_carrier_office" instance="VendorModuleObserverModelOrder" />
</event>
</config>


That would copy data saved in quote address to sales address.



I wrote this for my module for polish carrier InPost so I changed some names which might break the code but I hope this will give you what you need.



[EDIT]



Carrier model asked by @sangan



namespace VendorModuleModel;

use MagentoFrameworkAppConfigScopeConfigInterface;
use MagentoFrameworkPhrase;
use MagentoQuoteModelQuoteAddressRateRequest;
use MagentoShippingModelCarrierAbstractCarrier;
use MagentoShippingModelCarrierCarrierInterface;
use MagentoShippingModelSimplexmlElementFactory;

class Carrier extends AbstractCarrier implements CarrierInterface
null
*/
public function collectRates(RateRequest $request)

if (!$this->getConfigData('active'))
return false;


/** @var MagentoShippingModelRateResult $result */
$result = $this->_rateFactory->create();

/** @var MagentoQuoteModelQuoteAddressRateResultMethod $method */
$method = $this->_rateMethodFactory->create();
$method->setCarrier($this->_code);
$method->setCarrierTitle($this->getConfigData('title'));

$price = $this->getFinalPriceWithHandlingFee(0);
$method->setMethod(self::METHOD_CODE);
$method->setMethodTitle(new Phrase('MyMethod'));
$method->setPrice($price);
$method->setCost($price);
$result->append($method);;

return $result;



/**
* @return array
*/
public function getAllowedMethods()

$methods = [
'mymethod' => new Phrase('MyMethod')
];
return $methods;







share|improve this answer

























  • Thank you for your extended reply, i’ll try to solve my issue using your method and will reply with the result these days.

    – Siarhey Uchukhlebau
    Mar 10 '17 at 8:05











  • @Zefiryn I have created a custom shipping method, below it will display a dropdown having shipping account numbers of customer(there is a custom customer attribute created) so if I have to display this dropdown how much percent of your code will be helpful? What should I pick up from the code you provided?

    – Shireen N
    Oct 6 '17 at 14:38












  • @shireen I would say around 70%. You need to change the part where it fetches machines to account numbers. So api definition will be slighlty diferent and js part of it

    – Zefiryn
    Oct 6 '17 at 17:27











  • i have tried this module ...but its not showing any changes so please share the working module.if any

    – sangan
    Nov 7 '17 at 7:09











  • after adding successful module ..in checkout ajax loading continuously.. in console error showing like below : require.js:166 Uncaught Error: Script error for: Vendor_Module/js/view/checkout/shipping/model/office-registry. requirejs.org/docs/errors.html#scripterror

    – sangan
    Nov 8 '17 at 6:07



















2














I am adding new answer to expand on what was already provided previously but without distorting it.



This is the route that QuoteAddressPlugin was hooking into:



1. MagentoCheckoutApiShippingInformationManagementInterface::saveAddressInformation()
2. MagentoQuoteModelQuoteRepository::save()
3. MagentoQuoteModelQuoteRepositorySaveHandler::save()
4. MagentoQuoteModelQuoteRepositorySaveHandler::processShippingAssignment()
5. MagentoQuoteModelQuoteShippingAssignmentShippingAssignmentPersister::save()
6. MagentoQuoteModelQuoteShippingAssignmentShippingAssignmentProcessor::save()
7. MagentoQuoteModelQuoteShippingAssignmentShippingProcessor::save()
8. MagentoQuoteModelShippingMethodManagement::apply()


The last method was calling MagentoQuoteModelQuoteAddress::setShippingMethod() which was actually call for MagentoQuoteModelQuoteAddress::__call() which I used. Right now I found a better place for the plugin, it is MagentoQuoteModelShippingAssignment::setShipping() method. So the plugin part can be rewritten to:



<type name="MagentoQuoteModelShippingAssignment">
<plugin name="carrier-office-plugin" type="VendorModulePluginQuoteShippingAssignmentPlugin" sortOrder="1" disabled="false"/>
</type>


and plugin itself:



namespace VednorModulePluginQuote;

use MagentoQuoteApiDataAddressInterface;
use MagentoQuoteApiDataShippingInterface;
use MagentoQuoteModelShippingAssignment;
use VendorModuleModelCarrier;

/**
* ShippingAssignmentPlugin
*/
class ShippingAssignmentPlugin

/**
* Hook into setShipping.
*
* @param ShippingAssignment $subject
* @param ShippingInterface $value
* @return Address
*/
public function beforeSetShipping($subject, ShippingInterface $value)

$method = $value->getMethod();
/** @var AddressInterface $address */
$address = $value->getAddress();
if ($method === Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
&& $address->getExtensionAttributes()
&& $address->getExtensionAttributes()->getCarrierOffice()
)
$address->setCarrierOffice($address->getExtensionAttributes()->getCarrierOffice());

elseif ($method !== Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE)
//reset inpost machine when changing shipping method
$address->setCarrierOffice(null);

return [$value];







share|improve this answer
































    1














    @Zefiryn, I came across the problem with:
    quote.shippingAddress().extensionAttributes.carrier_office = office;



    When I get into checkout first time (new private window) as a guest (but the same occurs with registered client) attribute office is not saved into database after first "Next". Although in console I see correct output for: console.log(quote.shippingAddress().extensionAttributes.carrier_office);



    When I get back to the first checkout page and select office again then it is saved. What could be the reason of this behavior?



    I tried to use:
    address.trigger_reload = new Date().getTime();
    rateRegistry.set(address.getKey(), null);
    rateRegistry.set(address.getCacheKey(), null);
    quote.shippingAddress(address);



    but without success...






    share|improve this answer






























      0














      @Zefiryn, Can you explain in few words how does your above plugin work?
      Im little confused because as I know __call method is executed if we try execute method that not exists for particular object. It seems to be true because in app/code/Magento/Quote/Model/Quote/Address.php I dont see such method - only comment:



      /**
      * Sales Quote address model
      ...
      * @method Address setShippingMethod(string $value)



      1. Why do you use around interception when there is no method implementation?

      2. Next I see $subject->setInpostMachine and $subject->getCarrierOffice(null); Does it mean that above plugin's method will be executed again as there is no setInpostMachine() and getCarrierOffice() method in Adress Class? It looks like loop to me.

      3. From where Magento execute setShippingMethod()? How normally this method is used? I cant find any simillar interceptions in Magento code.





      share|improve this answer

























      • Ok, so I prepared the answer based on a module I wrote for testing, it used inpost_machine field, so this one just did not get correctly changed to carrier_office in this place. Second, at the time I was developing this module I haven't found a place where I could get both selected carrier and address with extension attributes sent except setShippingMethod call on AddressInterface object and since there is no such method then I had to use around__call to see if setShippingMethod was called or some other magic field. Right now I've found a better place and I will post it in new reply.

        – Zefiryn
        Jan 6 '18 at 15:11













      Your Answer








      StackExchange.ready(function()
      var channelOptions =
      tags: "".split(" "),
      id: "479"
      ;
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function()
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled)
      StackExchange.using("snippets", function()
      createEditor();
      );

      else
      createEditor();

      );

      function createEditor()
      StackExchange.prepareEditor(
      heartbeatType: 'answer',
      autoActivateHeartbeat: false,
      convertImagesToLinks: false,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: null,
      bindNavPrevention: true,
      postfix: "",
      imageUploader:
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      ,
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      );



      );













      draft saved

      draft discarded


















      StackExchange.ready(
      function ()
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fmagento.stackexchange.com%2fquestions%2f153868%2fmagento-2-add-dropdown-list-to-shipping-method%23new-answer', 'question_page');

      );

      Post as a guest















      Required, but never shown

























      4 Answers
      4






      active

      oldest

      votes








      4 Answers
      4






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      16





      +50









      Magento checkout does not support any kind of form for shipping method additional data. But it provides shippingAdditional block in the checkout which can be used for this. The following solution will work for standard magento checkout.



      First let's prepare our container where we can put some form. To do this create a file in view/frontend/layout/checkout_index_index.xml



      <?xml version="1.0"?>
      <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
      <body>
      <referenceBlock name="checkout.root">
      <arguments>
      <argument name="jsLayout" xsi:type="array">
      <item name="components" xsi:type="array">
      <item name="checkout" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="steps" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="shipping-step" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="shippingAddress" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="shippingAdditional" xsi:type="array">
      <item name="component" xsi:type="string">uiComponent</item>
      <item name="displayArea" xsi:type="string">shippingAdditional</item>
      <item name="children" xsi:type="array">
      <item name="vendor_carrier_form" xsi:type="array">
      <item name="component" xsi:type="string">Vendor_Module/js/view/checkout/shipping/form</item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </argument>
      </arguments>
      </referenceBlock>
      </body>
      </page>


      Now create a file in Vendor/Module/view/frontend/web/js/view/checkout/shipping/form.js which will render a knockout template. Its content looks like this



      define([
      'jquery',
      'ko',
      'uiComponent',
      'Magento_Checkout/js/model/quote',
      'Magento_Checkout/js/model/shipping-service',
      'Vendor_Module/js/view/checkout/shipping/office-service',
      'mage/translate',
      ], function ($, ko, Component, quote, shippingService, officeService, t)
      'use strict';

      return Component.extend(
      defaults:
      template: 'Vendor_Module/checkout/shipping/form'
      ,

      initialize: function (config)
      this.offices = ko.observableArray();
      this.selectedOffice = ko.observable();
      this._super();
      ,

      initObservable: function ()
      this._super();

      this.showOfficeSelection = ko.computed(function()
      return this.ofices().length != 0
      , this);

      this.selectedMethod = ko.computed(function()
      var method = quote.shippingMethod();
      var selectedMethod = method != null ? method.carrier_code + '_' + method.method_code : null;
      return selectedMethod;
      , this);

      quote.shippingMethod.subscribe(function(method)
      var selectedMethod = method != null ? method.carrier_code + '_' + method.method_code : null;
      if (selectedMethod == 'carrier_method')
      this.reloadOffices();

      , this);

      this.selectedOffice.subscribe(function(office)
      if (quote.shippingAddress().extensionAttributes == undefined)
      quote.shippingAddress().extensionAttributes = ;

      quote.shippingAddress().extensionAttributes.carrier_office = office;
      );


      return this;
      ,

      setOfficeList: function(list)
      this.offices(list);
      ,

      reloadOffices: function()
      officeService.getOfficeList(quote.shippingAddress(), this);
      var defaultOffice = this.offices()[0];
      if (defaultOffice)
      this.selectedOffice(defaultOffice);

      ,

      getOffice: function()
      var office;
      if (this.selectedOffice())
      for (var i in this.offices())
      var m = this.offices()[i];
      if (m.name == this.selectedOffice())
      office = m;



      else
      office = this.offices()[0];


      return office;
      ,

      initSelector: function()
      var startOffice = this.getOffice();

      );
      );


      This file uses knockout template which should be placed in Vendor/Module/view/frontend/web/template/checkout/shipping/form.html



      <div id="carrier-office-list-wrapper" data-bind="visible: selectedMethod() == 'carrier_method'">
      <p data-bind="visible: !showOfficeSelection(), i18n: 'Please provide postcode to see nearest offices'"></p>
      <div data-bind="visible: showOfficeSelection()">
      <p>
      <span data-bind="i18n: 'Select pickup office.'"></span>
      </p>
      <select id="carrier-office-list" data-bind="options: offices(),
      value: selectedOffice,
      optionsValue: 'name',
      optionsText: function(item)return item.location + ' (' + item.name +')';">
      </select>
      </div>
      </div>


      We have now a select field that will be visible when our method (defined by its code) will be selected in the shipping methods table. Time to fill it with some options. Since values are dependent on the address the best way is to create rest endpoint that will provide available options. In Vendor/Module/etc/webapi.xml



      <?xml version="1.0"?>

      <routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">

      <!-- Managing Office List on Checkout page -->
      <route url="/V1/module/get-office-list/:postcode/:city" method="GET">
      <service class="VendorModuleApiOfficeManagementInterface" method="fetchOffices"/>
      <resources>
      <resource ref="anonymous" />
      </resources>
      </route>
      </routes>


      Now define interface in Vendor/Module/Api/OfficeManagementInterface.php as



      namespace VendorModuleApi;

      interface OfficeManagementInterface


      /**
      * Find offices for the customer
      *
      * @param string $postcode
      * @param string $city
      * @return VendorModuleApiDataOfficeInterface[]
      */
      public function fetchOffices($postcode, $city);



      Define interface for office data in VendorModuleApiDataOfficeInterface.php. This interface will be used by webapi module to filter data for the output so you need to define whatever you need to add into the response.



      namespace VendorModuleApiData;

      /**
      * Office Interface
      */
      interface OfficeInterface

      /**
      * @return string
      */
      public function getName();

      /**
      * @return string
      */
      public function getLocation();



      Time for actual classes. Start with creating preferences for all interfaces in Vendor/Module/etc/di.xml



      <?xml version="1.0"?>

      <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
      <preference for="VendorModuleApiOfficeManagementInterface" type="VendorModuleModelOfficeManagement" />
      <preference for="VendorModuleApiDataOfficeInterface" type="VendorModuleModelOffice" />
      </config>


      Now create VendorModuleModelOfficeManagement.php class that will actually do the logic of fetching the data.



      namespace VednorModuleModel;

      use VednorModuleApiOfficeManagementInterface;
      use VednorModuleApiDataOfficeInterfaceFactory;

      class OfficeManagement implements OfficeManagementInterface

      protected $officeFactory;

      /**
      * OfficeManagement constructor.
      * @param OfficeInterfaceFactory $officeInterfaceFactory
      */
      public function __construct(OfficeInterfaceFactory $officeInterfaceFactory)

      $this->officeFactory = $officeInterfaceFactory;


      /**
      * Get offices for the given postcode and city
      *
      * @param string $postcode
      * @param string $limit
      * @return VendorModuleApiDataOfficeInterface[]
      */
      public function fetchOffices($postcode, $city)

      $result = [];
      for($i = 0, $i < 4;$i++)
      $office = $this->officeFactory->create();
      $office->setName("Office $i");
      $office->setLocation("Address $i");
      $result[] = $office;


      return $result;




      And finally class for OfficeInterface in Vendor/Module/Model/Office.php



      namespace VendorModuleModel;

      use MagentoFrameworkDataObject;
      use VendorModuleApiDataOfficeInterface;

      class Office extends DataObject implements OfficeInterface

      /**
      * @return string
      */
      public function getName()

      return (string)$this->_getData('name');


      /**
      * @return string
      */
      public function getLocation()

      return (string)$this->_getData('location');




      This should show select field and update it when address is changed. But we are missing one more element for frontend manipulation. We need to create function that will call the endpoint. Call to it is already included in Vendor/Module/view/frontend/web/js/view/checkout/shipping/form.js and it is Vendor_Module/js/view/checkout/shipping/office-service class which should go to Vendor/Module/view/frontend/web/js/view/checkout/shipping/office-service.js with the following code:



      define(
      [
      'Vendor_Module/js/view/checkout/shipping/model/resource-url-manager',
      'Magento_Checkout/js/model/quote',
      'Magento_Customer/js/model/customer',
      'mage/storage',
      'Magento_Checkout/js/model/shipping-service',
      'Vendor_Module/js/view/checkout/shipping/model/office-registry',
      'Magento_Checkout/js/model/error-processor'
      ],
      function (resourceUrlManager, quote, customer, storage, shippingService, officeRegistry, errorProcessor)
      'use strict';

      return
      /**
      * Get nearest machine list for specified address
      * @param Object address
      */
      getOfficeList: function (address, form)
      shippingService.isLoading(true);
      var cacheKey = address.getCacheKey(),
      cache = officeRegistry.get(cacheKey),
      serviceUrl = resourceUrlManager.getUrlForOfficeList(quote);

      if (cache)
      form.setOfficeList(cache);
      shippingService.isLoading(false);
      else
      storage.get(
      serviceUrl, false
      ).done(
      function (result)
      officeRegistry.set(cacheKey, result);
      form.setOfficeList(result);

      ).fail(
      function (response)
      errorProcessor.process(response);

      ).always(
      function ()
      shippingService.isLoading(false);

      );


      ;

      );


      It uses 2 more js files. Vendor_Module/js/view/checkout/shipping/model/resource-url-manager creates a url to the endpoint and is pretty simple



      define(
      [
      'Magento_Customer/js/model/customer',
      'Magento_Checkout/js/model/quote',
      'Magento_Checkout/js/model/url-builder',
      'mageUtils'
      ],
      function(customer, quote, urlBuilder, utils)
      "use strict";
      return
      getUrlForOfficeList: function(quote, limit)
      var params = postcode: quote.shippingAddress().postcode, city: quote.shippingAddress().city;
      var urls =
      'default': '/module/get-office-list/:postcode/:city'
      ;
      return this.getUrl(urls, params);
      ,

      /** Get url for service */
      getUrl: function(urls, urlParams)
      var url;

      if (utils.isEmpty(urls))
      return 'Provided service call does not exist.';


      if (!utils.isEmpty(urls['default']))
      url = urls['default'];
      else
      url = urls[this.getCheckoutMethod()];

      return urlBuilder.createUrl(url, urlParams);
      ,

      getCheckoutMethod: function()
      return customer.isLoggedIn() ? 'customer' : 'guest';

      ;

      );


      Vendor_Module/js/view/checkout/shipping/model/office-registry is a way of keeping result in the local storage. Its code is:



      define(
      [],
      function()
      "use strict";
      var cache = [];
      return
      get: function(addressKey)
      if (cache[addressKey])
      return cache[addressKey];

      return false;
      ,
      set: function(addressKey, data)
      cache[addressKey] = data;

      ;

      );


      Ok, so we should have all working on frontend. But now there is another problem to resolve. Since checkout doesn't know anything about this form it won't send the selection result to the backend. To make this happen we need to use extension_attributes feature. This is a way in magento2 to inform the system that some additional data are expected to be in the rest calls. Without it magento would filter out those data and they would never reach the code.



      So first in Vendor/Module/etc/extension_attributes.xml define:



      <?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="MagentoQuoteApiDataAddressInterface">
      <attribute code="carrier_office" type="string"/>
      </extension_attributes>
      </config>


      This value is already inserted in the request in form.js by this.selectedOffice.subscribe() definition. So the above configuration will only pass it at the entrance. To fetch it in the code create a plugin in Vendor/Module/etc/di.xml



      <type name="MagentoQuoteModelQuoteAddress">
      <plugin name="inpost-address" type="VendorModuleQuoteAddressPlugin" sortOrder="1" disabled="false"/>
      </type>


      Inside that class



      namespace VendorModulePluginQuote;

      use MagentoQuoteModelQuoteAddress;
      use VendorModuleModelCarrier;

      class AddressPlugin

      /**
      * Hook into setShippingMethod.
      * As this is magic function processed by __call method we need to hook around __call
      * to get the name of the called method. after__call does not provide this information.
      *
      * @param Address $subject
      * @param callable $proceed
      * @param string $method
      * @param mixed $vars
      * @return Address
      */
      public function around__call($subject, $proceed, $method, $vars)

      $result = $proceed($method, $vars);
      if ($method == 'setShippingMethod'
      && $vars[0] == Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
      && $subject->getExtensionAttributes()
      && $subject->getExtensionAttributes()->getCarrierOffice()
      )
      $subject->setCarrierOffice($subject->getExtensionAttributes()->getCarrierOffice());

      elseif (
      $method == 'setShippingMethod'
      && $vars[0] != Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
      )
      //reset office when changing shipping method
      $subject->getCarrierOffice(null);

      return $result;




      Of course where you will save the value depends entirely on your requirements. The above code would require creating additional column carrier_office in quote_address and sales_address tables and an event (in Vendor/Module/etc/events.xml)



      <?xml version="1.0"?>

      <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
      <event name="sales_model_service_quote_submit_before">
      <observer name="copy_carrier_office" instance="VendorModuleObserverModelOrder" />
      </event>
      </config>


      That would copy data saved in quote address to sales address.



      I wrote this for my module for polish carrier InPost so I changed some names which might break the code but I hope this will give you what you need.



      [EDIT]



      Carrier model asked by @sangan



      namespace VendorModuleModel;

      use MagentoFrameworkAppConfigScopeConfigInterface;
      use MagentoFrameworkPhrase;
      use MagentoQuoteModelQuoteAddressRateRequest;
      use MagentoShippingModelCarrierAbstractCarrier;
      use MagentoShippingModelCarrierCarrierInterface;
      use MagentoShippingModelSimplexmlElementFactory;

      class Carrier extends AbstractCarrier implements CarrierInterface
      null
      */
      public function collectRates(RateRequest $request)

      if (!$this->getConfigData('active'))
      return false;


      /** @var MagentoShippingModelRateResult $result */
      $result = $this->_rateFactory->create();

      /** @var MagentoQuoteModelQuoteAddressRateResultMethod $method */
      $method = $this->_rateMethodFactory->create();
      $method->setCarrier($this->_code);
      $method->setCarrierTitle($this->getConfigData('title'));

      $price = $this->getFinalPriceWithHandlingFee(0);
      $method->setMethod(self::METHOD_CODE);
      $method->setMethodTitle(new Phrase('MyMethod'));
      $method->setPrice($price);
      $method->setCost($price);
      $result->append($method);;

      return $result;



      /**
      * @return array
      */
      public function getAllowedMethods()

      $methods = [
      'mymethod' => new Phrase('MyMethod')
      ];
      return $methods;







      share|improve this answer

























      • Thank you for your extended reply, i’ll try to solve my issue using your method and will reply with the result these days.

        – Siarhey Uchukhlebau
        Mar 10 '17 at 8:05











      • @Zefiryn I have created a custom shipping method, below it will display a dropdown having shipping account numbers of customer(there is a custom customer attribute created) so if I have to display this dropdown how much percent of your code will be helpful? What should I pick up from the code you provided?

        – Shireen N
        Oct 6 '17 at 14:38












      • @shireen I would say around 70%. You need to change the part where it fetches machines to account numbers. So api definition will be slighlty diferent and js part of it

        – Zefiryn
        Oct 6 '17 at 17:27











      • i have tried this module ...but its not showing any changes so please share the working module.if any

        – sangan
        Nov 7 '17 at 7:09











      • after adding successful module ..in checkout ajax loading continuously.. in console error showing like below : require.js:166 Uncaught Error: Script error for: Vendor_Module/js/view/checkout/shipping/model/office-registry. requirejs.org/docs/errors.html#scripterror

        – sangan
        Nov 8 '17 at 6:07
















      16





      +50









      Magento checkout does not support any kind of form for shipping method additional data. But it provides shippingAdditional block in the checkout which can be used for this. The following solution will work for standard magento checkout.



      First let's prepare our container where we can put some form. To do this create a file in view/frontend/layout/checkout_index_index.xml



      <?xml version="1.0"?>
      <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
      <body>
      <referenceBlock name="checkout.root">
      <arguments>
      <argument name="jsLayout" xsi:type="array">
      <item name="components" xsi:type="array">
      <item name="checkout" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="steps" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="shipping-step" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="shippingAddress" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="shippingAdditional" xsi:type="array">
      <item name="component" xsi:type="string">uiComponent</item>
      <item name="displayArea" xsi:type="string">shippingAdditional</item>
      <item name="children" xsi:type="array">
      <item name="vendor_carrier_form" xsi:type="array">
      <item name="component" xsi:type="string">Vendor_Module/js/view/checkout/shipping/form</item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </argument>
      </arguments>
      </referenceBlock>
      </body>
      </page>


      Now create a file in Vendor/Module/view/frontend/web/js/view/checkout/shipping/form.js which will render a knockout template. Its content looks like this



      define([
      'jquery',
      'ko',
      'uiComponent',
      'Magento_Checkout/js/model/quote',
      'Magento_Checkout/js/model/shipping-service',
      'Vendor_Module/js/view/checkout/shipping/office-service',
      'mage/translate',
      ], function ($, ko, Component, quote, shippingService, officeService, t)
      'use strict';

      return Component.extend(
      defaults:
      template: 'Vendor_Module/checkout/shipping/form'
      ,

      initialize: function (config)
      this.offices = ko.observableArray();
      this.selectedOffice = ko.observable();
      this._super();
      ,

      initObservable: function ()
      this._super();

      this.showOfficeSelection = ko.computed(function()
      return this.ofices().length != 0
      , this);

      this.selectedMethod = ko.computed(function()
      var method = quote.shippingMethod();
      var selectedMethod = method != null ? method.carrier_code + '_' + method.method_code : null;
      return selectedMethod;
      , this);

      quote.shippingMethod.subscribe(function(method)
      var selectedMethod = method != null ? method.carrier_code + '_' + method.method_code : null;
      if (selectedMethod == 'carrier_method')
      this.reloadOffices();

      , this);

      this.selectedOffice.subscribe(function(office)
      if (quote.shippingAddress().extensionAttributes == undefined)
      quote.shippingAddress().extensionAttributes = ;

      quote.shippingAddress().extensionAttributes.carrier_office = office;
      );


      return this;
      ,

      setOfficeList: function(list)
      this.offices(list);
      ,

      reloadOffices: function()
      officeService.getOfficeList(quote.shippingAddress(), this);
      var defaultOffice = this.offices()[0];
      if (defaultOffice)
      this.selectedOffice(defaultOffice);

      ,

      getOffice: function()
      var office;
      if (this.selectedOffice())
      for (var i in this.offices())
      var m = this.offices()[i];
      if (m.name == this.selectedOffice())
      office = m;



      else
      office = this.offices()[0];


      return office;
      ,

      initSelector: function()
      var startOffice = this.getOffice();

      );
      );


      This file uses knockout template which should be placed in Vendor/Module/view/frontend/web/template/checkout/shipping/form.html



      <div id="carrier-office-list-wrapper" data-bind="visible: selectedMethod() == 'carrier_method'">
      <p data-bind="visible: !showOfficeSelection(), i18n: 'Please provide postcode to see nearest offices'"></p>
      <div data-bind="visible: showOfficeSelection()">
      <p>
      <span data-bind="i18n: 'Select pickup office.'"></span>
      </p>
      <select id="carrier-office-list" data-bind="options: offices(),
      value: selectedOffice,
      optionsValue: 'name',
      optionsText: function(item)return item.location + ' (' + item.name +')';">
      </select>
      </div>
      </div>


      We have now a select field that will be visible when our method (defined by its code) will be selected in the shipping methods table. Time to fill it with some options. Since values are dependent on the address the best way is to create rest endpoint that will provide available options. In Vendor/Module/etc/webapi.xml



      <?xml version="1.0"?>

      <routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">

      <!-- Managing Office List on Checkout page -->
      <route url="/V1/module/get-office-list/:postcode/:city" method="GET">
      <service class="VendorModuleApiOfficeManagementInterface" method="fetchOffices"/>
      <resources>
      <resource ref="anonymous" />
      </resources>
      </route>
      </routes>


      Now define interface in Vendor/Module/Api/OfficeManagementInterface.php as



      namespace VendorModuleApi;

      interface OfficeManagementInterface


      /**
      * Find offices for the customer
      *
      * @param string $postcode
      * @param string $city
      * @return VendorModuleApiDataOfficeInterface[]
      */
      public function fetchOffices($postcode, $city);



      Define interface for office data in VendorModuleApiDataOfficeInterface.php. This interface will be used by webapi module to filter data for the output so you need to define whatever you need to add into the response.



      namespace VendorModuleApiData;

      /**
      * Office Interface
      */
      interface OfficeInterface

      /**
      * @return string
      */
      public function getName();

      /**
      * @return string
      */
      public function getLocation();



      Time for actual classes. Start with creating preferences for all interfaces in Vendor/Module/etc/di.xml



      <?xml version="1.0"?>

      <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
      <preference for="VendorModuleApiOfficeManagementInterface" type="VendorModuleModelOfficeManagement" />
      <preference for="VendorModuleApiDataOfficeInterface" type="VendorModuleModelOffice" />
      </config>


      Now create VendorModuleModelOfficeManagement.php class that will actually do the logic of fetching the data.



      namespace VednorModuleModel;

      use VednorModuleApiOfficeManagementInterface;
      use VednorModuleApiDataOfficeInterfaceFactory;

      class OfficeManagement implements OfficeManagementInterface

      protected $officeFactory;

      /**
      * OfficeManagement constructor.
      * @param OfficeInterfaceFactory $officeInterfaceFactory
      */
      public function __construct(OfficeInterfaceFactory $officeInterfaceFactory)

      $this->officeFactory = $officeInterfaceFactory;


      /**
      * Get offices for the given postcode and city
      *
      * @param string $postcode
      * @param string $limit
      * @return VendorModuleApiDataOfficeInterface[]
      */
      public function fetchOffices($postcode, $city)

      $result = [];
      for($i = 0, $i < 4;$i++)
      $office = $this->officeFactory->create();
      $office->setName("Office $i");
      $office->setLocation("Address $i");
      $result[] = $office;


      return $result;




      And finally class for OfficeInterface in Vendor/Module/Model/Office.php



      namespace VendorModuleModel;

      use MagentoFrameworkDataObject;
      use VendorModuleApiDataOfficeInterface;

      class Office extends DataObject implements OfficeInterface

      /**
      * @return string
      */
      public function getName()

      return (string)$this->_getData('name');


      /**
      * @return string
      */
      public function getLocation()

      return (string)$this->_getData('location');




      This should show select field and update it when address is changed. But we are missing one more element for frontend manipulation. We need to create function that will call the endpoint. Call to it is already included in Vendor/Module/view/frontend/web/js/view/checkout/shipping/form.js and it is Vendor_Module/js/view/checkout/shipping/office-service class which should go to Vendor/Module/view/frontend/web/js/view/checkout/shipping/office-service.js with the following code:



      define(
      [
      'Vendor_Module/js/view/checkout/shipping/model/resource-url-manager',
      'Magento_Checkout/js/model/quote',
      'Magento_Customer/js/model/customer',
      'mage/storage',
      'Magento_Checkout/js/model/shipping-service',
      'Vendor_Module/js/view/checkout/shipping/model/office-registry',
      'Magento_Checkout/js/model/error-processor'
      ],
      function (resourceUrlManager, quote, customer, storage, shippingService, officeRegistry, errorProcessor)
      'use strict';

      return
      /**
      * Get nearest machine list for specified address
      * @param Object address
      */
      getOfficeList: function (address, form)
      shippingService.isLoading(true);
      var cacheKey = address.getCacheKey(),
      cache = officeRegistry.get(cacheKey),
      serviceUrl = resourceUrlManager.getUrlForOfficeList(quote);

      if (cache)
      form.setOfficeList(cache);
      shippingService.isLoading(false);
      else
      storage.get(
      serviceUrl, false
      ).done(
      function (result)
      officeRegistry.set(cacheKey, result);
      form.setOfficeList(result);

      ).fail(
      function (response)
      errorProcessor.process(response);

      ).always(
      function ()
      shippingService.isLoading(false);

      );


      ;

      );


      It uses 2 more js files. Vendor_Module/js/view/checkout/shipping/model/resource-url-manager creates a url to the endpoint and is pretty simple



      define(
      [
      'Magento_Customer/js/model/customer',
      'Magento_Checkout/js/model/quote',
      'Magento_Checkout/js/model/url-builder',
      'mageUtils'
      ],
      function(customer, quote, urlBuilder, utils)
      "use strict";
      return
      getUrlForOfficeList: function(quote, limit)
      var params = postcode: quote.shippingAddress().postcode, city: quote.shippingAddress().city;
      var urls =
      'default': '/module/get-office-list/:postcode/:city'
      ;
      return this.getUrl(urls, params);
      ,

      /** Get url for service */
      getUrl: function(urls, urlParams)
      var url;

      if (utils.isEmpty(urls))
      return 'Provided service call does not exist.';


      if (!utils.isEmpty(urls['default']))
      url = urls['default'];
      else
      url = urls[this.getCheckoutMethod()];

      return urlBuilder.createUrl(url, urlParams);
      ,

      getCheckoutMethod: function()
      return customer.isLoggedIn() ? 'customer' : 'guest';

      ;

      );


      Vendor_Module/js/view/checkout/shipping/model/office-registry is a way of keeping result in the local storage. Its code is:



      define(
      [],
      function()
      "use strict";
      var cache = [];
      return
      get: function(addressKey)
      if (cache[addressKey])
      return cache[addressKey];

      return false;
      ,
      set: function(addressKey, data)
      cache[addressKey] = data;

      ;

      );


      Ok, so we should have all working on frontend. But now there is another problem to resolve. Since checkout doesn't know anything about this form it won't send the selection result to the backend. To make this happen we need to use extension_attributes feature. This is a way in magento2 to inform the system that some additional data are expected to be in the rest calls. Without it magento would filter out those data and they would never reach the code.



      So first in Vendor/Module/etc/extension_attributes.xml define:



      <?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="MagentoQuoteApiDataAddressInterface">
      <attribute code="carrier_office" type="string"/>
      </extension_attributes>
      </config>


      This value is already inserted in the request in form.js by this.selectedOffice.subscribe() definition. So the above configuration will only pass it at the entrance. To fetch it in the code create a plugin in Vendor/Module/etc/di.xml



      <type name="MagentoQuoteModelQuoteAddress">
      <plugin name="inpost-address" type="VendorModuleQuoteAddressPlugin" sortOrder="1" disabled="false"/>
      </type>


      Inside that class



      namespace VendorModulePluginQuote;

      use MagentoQuoteModelQuoteAddress;
      use VendorModuleModelCarrier;

      class AddressPlugin

      /**
      * Hook into setShippingMethod.
      * As this is magic function processed by __call method we need to hook around __call
      * to get the name of the called method. after__call does not provide this information.
      *
      * @param Address $subject
      * @param callable $proceed
      * @param string $method
      * @param mixed $vars
      * @return Address
      */
      public function around__call($subject, $proceed, $method, $vars)

      $result = $proceed($method, $vars);
      if ($method == 'setShippingMethod'
      && $vars[0] == Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
      && $subject->getExtensionAttributes()
      && $subject->getExtensionAttributes()->getCarrierOffice()
      )
      $subject->setCarrierOffice($subject->getExtensionAttributes()->getCarrierOffice());

      elseif (
      $method == 'setShippingMethod'
      && $vars[0] != Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
      )
      //reset office when changing shipping method
      $subject->getCarrierOffice(null);

      return $result;




      Of course where you will save the value depends entirely on your requirements. The above code would require creating additional column carrier_office in quote_address and sales_address tables and an event (in Vendor/Module/etc/events.xml)



      <?xml version="1.0"?>

      <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
      <event name="sales_model_service_quote_submit_before">
      <observer name="copy_carrier_office" instance="VendorModuleObserverModelOrder" />
      </event>
      </config>


      That would copy data saved in quote address to sales address.



      I wrote this for my module for polish carrier InPost so I changed some names which might break the code but I hope this will give you what you need.



      [EDIT]



      Carrier model asked by @sangan



      namespace VendorModuleModel;

      use MagentoFrameworkAppConfigScopeConfigInterface;
      use MagentoFrameworkPhrase;
      use MagentoQuoteModelQuoteAddressRateRequest;
      use MagentoShippingModelCarrierAbstractCarrier;
      use MagentoShippingModelCarrierCarrierInterface;
      use MagentoShippingModelSimplexmlElementFactory;

      class Carrier extends AbstractCarrier implements CarrierInterface
      null
      */
      public function collectRates(RateRequest $request)

      if (!$this->getConfigData('active'))
      return false;


      /** @var MagentoShippingModelRateResult $result */
      $result = $this->_rateFactory->create();

      /** @var MagentoQuoteModelQuoteAddressRateResultMethod $method */
      $method = $this->_rateMethodFactory->create();
      $method->setCarrier($this->_code);
      $method->setCarrierTitle($this->getConfigData('title'));

      $price = $this->getFinalPriceWithHandlingFee(0);
      $method->setMethod(self::METHOD_CODE);
      $method->setMethodTitle(new Phrase('MyMethod'));
      $method->setPrice($price);
      $method->setCost($price);
      $result->append($method);;

      return $result;



      /**
      * @return array
      */
      public function getAllowedMethods()

      $methods = [
      'mymethod' => new Phrase('MyMethod')
      ];
      return $methods;







      share|improve this answer

























      • Thank you for your extended reply, i’ll try to solve my issue using your method and will reply with the result these days.

        – Siarhey Uchukhlebau
        Mar 10 '17 at 8:05











      • @Zefiryn I have created a custom shipping method, below it will display a dropdown having shipping account numbers of customer(there is a custom customer attribute created) so if I have to display this dropdown how much percent of your code will be helpful? What should I pick up from the code you provided?

        – Shireen N
        Oct 6 '17 at 14:38












      • @shireen I would say around 70%. You need to change the part where it fetches machines to account numbers. So api definition will be slighlty diferent and js part of it

        – Zefiryn
        Oct 6 '17 at 17:27











      • i have tried this module ...but its not showing any changes so please share the working module.if any

        – sangan
        Nov 7 '17 at 7:09











      • after adding successful module ..in checkout ajax loading continuously.. in console error showing like below : require.js:166 Uncaught Error: Script error for: Vendor_Module/js/view/checkout/shipping/model/office-registry. requirejs.org/docs/errors.html#scripterror

        – sangan
        Nov 8 '17 at 6:07














      16





      +50







      16





      +50



      16




      +50





      Magento checkout does not support any kind of form for shipping method additional data. But it provides shippingAdditional block in the checkout which can be used for this. The following solution will work for standard magento checkout.



      First let's prepare our container where we can put some form. To do this create a file in view/frontend/layout/checkout_index_index.xml



      <?xml version="1.0"?>
      <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
      <body>
      <referenceBlock name="checkout.root">
      <arguments>
      <argument name="jsLayout" xsi:type="array">
      <item name="components" xsi:type="array">
      <item name="checkout" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="steps" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="shipping-step" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="shippingAddress" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="shippingAdditional" xsi:type="array">
      <item name="component" xsi:type="string">uiComponent</item>
      <item name="displayArea" xsi:type="string">shippingAdditional</item>
      <item name="children" xsi:type="array">
      <item name="vendor_carrier_form" xsi:type="array">
      <item name="component" xsi:type="string">Vendor_Module/js/view/checkout/shipping/form</item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </argument>
      </arguments>
      </referenceBlock>
      </body>
      </page>


      Now create a file in Vendor/Module/view/frontend/web/js/view/checkout/shipping/form.js which will render a knockout template. Its content looks like this



      define([
      'jquery',
      'ko',
      'uiComponent',
      'Magento_Checkout/js/model/quote',
      'Magento_Checkout/js/model/shipping-service',
      'Vendor_Module/js/view/checkout/shipping/office-service',
      'mage/translate',
      ], function ($, ko, Component, quote, shippingService, officeService, t)
      'use strict';

      return Component.extend(
      defaults:
      template: 'Vendor_Module/checkout/shipping/form'
      ,

      initialize: function (config)
      this.offices = ko.observableArray();
      this.selectedOffice = ko.observable();
      this._super();
      ,

      initObservable: function ()
      this._super();

      this.showOfficeSelection = ko.computed(function()
      return this.ofices().length != 0
      , this);

      this.selectedMethod = ko.computed(function()
      var method = quote.shippingMethod();
      var selectedMethod = method != null ? method.carrier_code + '_' + method.method_code : null;
      return selectedMethod;
      , this);

      quote.shippingMethod.subscribe(function(method)
      var selectedMethod = method != null ? method.carrier_code + '_' + method.method_code : null;
      if (selectedMethod == 'carrier_method')
      this.reloadOffices();

      , this);

      this.selectedOffice.subscribe(function(office)
      if (quote.shippingAddress().extensionAttributes == undefined)
      quote.shippingAddress().extensionAttributes = ;

      quote.shippingAddress().extensionAttributes.carrier_office = office;
      );


      return this;
      ,

      setOfficeList: function(list)
      this.offices(list);
      ,

      reloadOffices: function()
      officeService.getOfficeList(quote.shippingAddress(), this);
      var defaultOffice = this.offices()[0];
      if (defaultOffice)
      this.selectedOffice(defaultOffice);

      ,

      getOffice: function()
      var office;
      if (this.selectedOffice())
      for (var i in this.offices())
      var m = this.offices()[i];
      if (m.name == this.selectedOffice())
      office = m;



      else
      office = this.offices()[0];


      return office;
      ,

      initSelector: function()
      var startOffice = this.getOffice();

      );
      );


      This file uses knockout template which should be placed in Vendor/Module/view/frontend/web/template/checkout/shipping/form.html



      <div id="carrier-office-list-wrapper" data-bind="visible: selectedMethod() == 'carrier_method'">
      <p data-bind="visible: !showOfficeSelection(), i18n: 'Please provide postcode to see nearest offices'"></p>
      <div data-bind="visible: showOfficeSelection()">
      <p>
      <span data-bind="i18n: 'Select pickup office.'"></span>
      </p>
      <select id="carrier-office-list" data-bind="options: offices(),
      value: selectedOffice,
      optionsValue: 'name',
      optionsText: function(item)return item.location + ' (' + item.name +')';">
      </select>
      </div>
      </div>


      We have now a select field that will be visible when our method (defined by its code) will be selected in the shipping methods table. Time to fill it with some options. Since values are dependent on the address the best way is to create rest endpoint that will provide available options. In Vendor/Module/etc/webapi.xml



      <?xml version="1.0"?>

      <routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">

      <!-- Managing Office List on Checkout page -->
      <route url="/V1/module/get-office-list/:postcode/:city" method="GET">
      <service class="VendorModuleApiOfficeManagementInterface" method="fetchOffices"/>
      <resources>
      <resource ref="anonymous" />
      </resources>
      </route>
      </routes>


      Now define interface in Vendor/Module/Api/OfficeManagementInterface.php as



      namespace VendorModuleApi;

      interface OfficeManagementInterface


      /**
      * Find offices for the customer
      *
      * @param string $postcode
      * @param string $city
      * @return VendorModuleApiDataOfficeInterface[]
      */
      public function fetchOffices($postcode, $city);



      Define interface for office data in VendorModuleApiDataOfficeInterface.php. This interface will be used by webapi module to filter data for the output so you need to define whatever you need to add into the response.



      namespace VendorModuleApiData;

      /**
      * Office Interface
      */
      interface OfficeInterface

      /**
      * @return string
      */
      public function getName();

      /**
      * @return string
      */
      public function getLocation();



      Time for actual classes. Start with creating preferences for all interfaces in Vendor/Module/etc/di.xml



      <?xml version="1.0"?>

      <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
      <preference for="VendorModuleApiOfficeManagementInterface" type="VendorModuleModelOfficeManagement" />
      <preference for="VendorModuleApiDataOfficeInterface" type="VendorModuleModelOffice" />
      </config>


      Now create VendorModuleModelOfficeManagement.php class that will actually do the logic of fetching the data.



      namespace VednorModuleModel;

      use VednorModuleApiOfficeManagementInterface;
      use VednorModuleApiDataOfficeInterfaceFactory;

      class OfficeManagement implements OfficeManagementInterface

      protected $officeFactory;

      /**
      * OfficeManagement constructor.
      * @param OfficeInterfaceFactory $officeInterfaceFactory
      */
      public function __construct(OfficeInterfaceFactory $officeInterfaceFactory)

      $this->officeFactory = $officeInterfaceFactory;


      /**
      * Get offices for the given postcode and city
      *
      * @param string $postcode
      * @param string $limit
      * @return VendorModuleApiDataOfficeInterface[]
      */
      public function fetchOffices($postcode, $city)

      $result = [];
      for($i = 0, $i < 4;$i++)
      $office = $this->officeFactory->create();
      $office->setName("Office $i");
      $office->setLocation("Address $i");
      $result[] = $office;


      return $result;




      And finally class for OfficeInterface in Vendor/Module/Model/Office.php



      namespace VendorModuleModel;

      use MagentoFrameworkDataObject;
      use VendorModuleApiDataOfficeInterface;

      class Office extends DataObject implements OfficeInterface

      /**
      * @return string
      */
      public function getName()

      return (string)$this->_getData('name');


      /**
      * @return string
      */
      public function getLocation()

      return (string)$this->_getData('location');




      This should show select field and update it when address is changed. But we are missing one more element for frontend manipulation. We need to create function that will call the endpoint. Call to it is already included in Vendor/Module/view/frontend/web/js/view/checkout/shipping/form.js and it is Vendor_Module/js/view/checkout/shipping/office-service class which should go to Vendor/Module/view/frontend/web/js/view/checkout/shipping/office-service.js with the following code:



      define(
      [
      'Vendor_Module/js/view/checkout/shipping/model/resource-url-manager',
      'Magento_Checkout/js/model/quote',
      'Magento_Customer/js/model/customer',
      'mage/storage',
      'Magento_Checkout/js/model/shipping-service',
      'Vendor_Module/js/view/checkout/shipping/model/office-registry',
      'Magento_Checkout/js/model/error-processor'
      ],
      function (resourceUrlManager, quote, customer, storage, shippingService, officeRegistry, errorProcessor)
      'use strict';

      return
      /**
      * Get nearest machine list for specified address
      * @param Object address
      */
      getOfficeList: function (address, form)
      shippingService.isLoading(true);
      var cacheKey = address.getCacheKey(),
      cache = officeRegistry.get(cacheKey),
      serviceUrl = resourceUrlManager.getUrlForOfficeList(quote);

      if (cache)
      form.setOfficeList(cache);
      shippingService.isLoading(false);
      else
      storage.get(
      serviceUrl, false
      ).done(
      function (result)
      officeRegistry.set(cacheKey, result);
      form.setOfficeList(result);

      ).fail(
      function (response)
      errorProcessor.process(response);

      ).always(
      function ()
      shippingService.isLoading(false);

      );


      ;

      );


      It uses 2 more js files. Vendor_Module/js/view/checkout/shipping/model/resource-url-manager creates a url to the endpoint and is pretty simple



      define(
      [
      'Magento_Customer/js/model/customer',
      'Magento_Checkout/js/model/quote',
      'Magento_Checkout/js/model/url-builder',
      'mageUtils'
      ],
      function(customer, quote, urlBuilder, utils)
      "use strict";
      return
      getUrlForOfficeList: function(quote, limit)
      var params = postcode: quote.shippingAddress().postcode, city: quote.shippingAddress().city;
      var urls =
      'default': '/module/get-office-list/:postcode/:city'
      ;
      return this.getUrl(urls, params);
      ,

      /** Get url for service */
      getUrl: function(urls, urlParams)
      var url;

      if (utils.isEmpty(urls))
      return 'Provided service call does not exist.';


      if (!utils.isEmpty(urls['default']))
      url = urls['default'];
      else
      url = urls[this.getCheckoutMethod()];

      return urlBuilder.createUrl(url, urlParams);
      ,

      getCheckoutMethod: function()
      return customer.isLoggedIn() ? 'customer' : 'guest';

      ;

      );


      Vendor_Module/js/view/checkout/shipping/model/office-registry is a way of keeping result in the local storage. Its code is:



      define(
      [],
      function()
      "use strict";
      var cache = [];
      return
      get: function(addressKey)
      if (cache[addressKey])
      return cache[addressKey];

      return false;
      ,
      set: function(addressKey, data)
      cache[addressKey] = data;

      ;

      );


      Ok, so we should have all working on frontend. But now there is another problem to resolve. Since checkout doesn't know anything about this form it won't send the selection result to the backend. To make this happen we need to use extension_attributes feature. This is a way in magento2 to inform the system that some additional data are expected to be in the rest calls. Without it magento would filter out those data and they would never reach the code.



      So first in Vendor/Module/etc/extension_attributes.xml define:



      <?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="MagentoQuoteApiDataAddressInterface">
      <attribute code="carrier_office" type="string"/>
      </extension_attributes>
      </config>


      This value is already inserted in the request in form.js by this.selectedOffice.subscribe() definition. So the above configuration will only pass it at the entrance. To fetch it in the code create a plugin in Vendor/Module/etc/di.xml



      <type name="MagentoQuoteModelQuoteAddress">
      <plugin name="inpost-address" type="VendorModuleQuoteAddressPlugin" sortOrder="1" disabled="false"/>
      </type>


      Inside that class



      namespace VendorModulePluginQuote;

      use MagentoQuoteModelQuoteAddress;
      use VendorModuleModelCarrier;

      class AddressPlugin

      /**
      * Hook into setShippingMethod.
      * As this is magic function processed by __call method we need to hook around __call
      * to get the name of the called method. after__call does not provide this information.
      *
      * @param Address $subject
      * @param callable $proceed
      * @param string $method
      * @param mixed $vars
      * @return Address
      */
      public function around__call($subject, $proceed, $method, $vars)

      $result = $proceed($method, $vars);
      if ($method == 'setShippingMethod'
      && $vars[0] == Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
      && $subject->getExtensionAttributes()
      && $subject->getExtensionAttributes()->getCarrierOffice()
      )
      $subject->setCarrierOffice($subject->getExtensionAttributes()->getCarrierOffice());

      elseif (
      $method == 'setShippingMethod'
      && $vars[0] != Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
      )
      //reset office when changing shipping method
      $subject->getCarrierOffice(null);

      return $result;




      Of course where you will save the value depends entirely on your requirements. The above code would require creating additional column carrier_office in quote_address and sales_address tables and an event (in Vendor/Module/etc/events.xml)



      <?xml version="1.0"?>

      <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
      <event name="sales_model_service_quote_submit_before">
      <observer name="copy_carrier_office" instance="VendorModuleObserverModelOrder" />
      </event>
      </config>


      That would copy data saved in quote address to sales address.



      I wrote this for my module for polish carrier InPost so I changed some names which might break the code but I hope this will give you what you need.



      [EDIT]



      Carrier model asked by @sangan



      namespace VendorModuleModel;

      use MagentoFrameworkAppConfigScopeConfigInterface;
      use MagentoFrameworkPhrase;
      use MagentoQuoteModelQuoteAddressRateRequest;
      use MagentoShippingModelCarrierAbstractCarrier;
      use MagentoShippingModelCarrierCarrierInterface;
      use MagentoShippingModelSimplexmlElementFactory;

      class Carrier extends AbstractCarrier implements CarrierInterface
      null
      */
      public function collectRates(RateRequest $request)

      if (!$this->getConfigData('active'))
      return false;


      /** @var MagentoShippingModelRateResult $result */
      $result = $this->_rateFactory->create();

      /** @var MagentoQuoteModelQuoteAddressRateResultMethod $method */
      $method = $this->_rateMethodFactory->create();
      $method->setCarrier($this->_code);
      $method->setCarrierTitle($this->getConfigData('title'));

      $price = $this->getFinalPriceWithHandlingFee(0);
      $method->setMethod(self::METHOD_CODE);
      $method->setMethodTitle(new Phrase('MyMethod'));
      $method->setPrice($price);
      $method->setCost($price);
      $result->append($method);;

      return $result;



      /**
      * @return array
      */
      public function getAllowedMethods()

      $methods = [
      'mymethod' => new Phrase('MyMethod')
      ];
      return $methods;







      share|improve this answer















      Magento checkout does not support any kind of form for shipping method additional data. But it provides shippingAdditional block in the checkout which can be used for this. The following solution will work for standard magento checkout.



      First let's prepare our container where we can put some form. To do this create a file in view/frontend/layout/checkout_index_index.xml



      <?xml version="1.0"?>
      <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
      <body>
      <referenceBlock name="checkout.root">
      <arguments>
      <argument name="jsLayout" xsi:type="array">
      <item name="components" xsi:type="array">
      <item name="checkout" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="steps" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="shipping-step" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="shippingAddress" xsi:type="array">
      <item name="children" xsi:type="array">
      <item name="shippingAdditional" xsi:type="array">
      <item name="component" xsi:type="string">uiComponent</item>
      <item name="displayArea" xsi:type="string">shippingAdditional</item>
      <item name="children" xsi:type="array">
      <item name="vendor_carrier_form" xsi:type="array">
      <item name="component" xsi:type="string">Vendor_Module/js/view/checkout/shipping/form</item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </item>
      </argument>
      </arguments>
      </referenceBlock>
      </body>
      </page>


      Now create a file in Vendor/Module/view/frontend/web/js/view/checkout/shipping/form.js which will render a knockout template. Its content looks like this



      define([
      'jquery',
      'ko',
      'uiComponent',
      'Magento_Checkout/js/model/quote',
      'Magento_Checkout/js/model/shipping-service',
      'Vendor_Module/js/view/checkout/shipping/office-service',
      'mage/translate',
      ], function ($, ko, Component, quote, shippingService, officeService, t)
      'use strict';

      return Component.extend(
      defaults:
      template: 'Vendor_Module/checkout/shipping/form'
      ,

      initialize: function (config)
      this.offices = ko.observableArray();
      this.selectedOffice = ko.observable();
      this._super();
      ,

      initObservable: function ()
      this._super();

      this.showOfficeSelection = ko.computed(function()
      return this.ofices().length != 0
      , this);

      this.selectedMethod = ko.computed(function()
      var method = quote.shippingMethod();
      var selectedMethod = method != null ? method.carrier_code + '_' + method.method_code : null;
      return selectedMethod;
      , this);

      quote.shippingMethod.subscribe(function(method)
      var selectedMethod = method != null ? method.carrier_code + '_' + method.method_code : null;
      if (selectedMethod == 'carrier_method')
      this.reloadOffices();

      , this);

      this.selectedOffice.subscribe(function(office)
      if (quote.shippingAddress().extensionAttributes == undefined)
      quote.shippingAddress().extensionAttributes = ;

      quote.shippingAddress().extensionAttributes.carrier_office = office;
      );


      return this;
      ,

      setOfficeList: function(list)
      this.offices(list);
      ,

      reloadOffices: function()
      officeService.getOfficeList(quote.shippingAddress(), this);
      var defaultOffice = this.offices()[0];
      if (defaultOffice)
      this.selectedOffice(defaultOffice);

      ,

      getOffice: function()
      var office;
      if (this.selectedOffice())
      for (var i in this.offices())
      var m = this.offices()[i];
      if (m.name == this.selectedOffice())
      office = m;



      else
      office = this.offices()[0];


      return office;
      ,

      initSelector: function()
      var startOffice = this.getOffice();

      );
      );


      This file uses knockout template which should be placed in Vendor/Module/view/frontend/web/template/checkout/shipping/form.html



      <div id="carrier-office-list-wrapper" data-bind="visible: selectedMethod() == 'carrier_method'">
      <p data-bind="visible: !showOfficeSelection(), i18n: 'Please provide postcode to see nearest offices'"></p>
      <div data-bind="visible: showOfficeSelection()">
      <p>
      <span data-bind="i18n: 'Select pickup office.'"></span>
      </p>
      <select id="carrier-office-list" data-bind="options: offices(),
      value: selectedOffice,
      optionsValue: 'name',
      optionsText: function(item)return item.location + ' (' + item.name +')';">
      </select>
      </div>
      </div>


      We have now a select field that will be visible when our method (defined by its code) will be selected in the shipping methods table. Time to fill it with some options. Since values are dependent on the address the best way is to create rest endpoint that will provide available options. In Vendor/Module/etc/webapi.xml



      <?xml version="1.0"?>

      <routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">

      <!-- Managing Office List on Checkout page -->
      <route url="/V1/module/get-office-list/:postcode/:city" method="GET">
      <service class="VendorModuleApiOfficeManagementInterface" method="fetchOffices"/>
      <resources>
      <resource ref="anonymous" />
      </resources>
      </route>
      </routes>


      Now define interface in Vendor/Module/Api/OfficeManagementInterface.php as



      namespace VendorModuleApi;

      interface OfficeManagementInterface


      /**
      * Find offices for the customer
      *
      * @param string $postcode
      * @param string $city
      * @return VendorModuleApiDataOfficeInterface[]
      */
      public function fetchOffices($postcode, $city);



      Define interface for office data in VendorModuleApiDataOfficeInterface.php. This interface will be used by webapi module to filter data for the output so you need to define whatever you need to add into the response.



      namespace VendorModuleApiData;

      /**
      * Office Interface
      */
      interface OfficeInterface

      /**
      * @return string
      */
      public function getName();

      /**
      * @return string
      */
      public function getLocation();



      Time for actual classes. Start with creating preferences for all interfaces in Vendor/Module/etc/di.xml



      <?xml version="1.0"?>

      <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
      <preference for="VendorModuleApiOfficeManagementInterface" type="VendorModuleModelOfficeManagement" />
      <preference for="VendorModuleApiDataOfficeInterface" type="VendorModuleModelOffice" />
      </config>


      Now create VendorModuleModelOfficeManagement.php class that will actually do the logic of fetching the data.



      namespace VednorModuleModel;

      use VednorModuleApiOfficeManagementInterface;
      use VednorModuleApiDataOfficeInterfaceFactory;

      class OfficeManagement implements OfficeManagementInterface

      protected $officeFactory;

      /**
      * OfficeManagement constructor.
      * @param OfficeInterfaceFactory $officeInterfaceFactory
      */
      public function __construct(OfficeInterfaceFactory $officeInterfaceFactory)

      $this->officeFactory = $officeInterfaceFactory;


      /**
      * Get offices for the given postcode and city
      *
      * @param string $postcode
      * @param string $limit
      * @return VendorModuleApiDataOfficeInterface[]
      */
      public function fetchOffices($postcode, $city)

      $result = [];
      for($i = 0, $i < 4;$i++)
      $office = $this->officeFactory->create();
      $office->setName("Office $i");
      $office->setLocation("Address $i");
      $result[] = $office;


      return $result;




      And finally class for OfficeInterface in Vendor/Module/Model/Office.php



      namespace VendorModuleModel;

      use MagentoFrameworkDataObject;
      use VendorModuleApiDataOfficeInterface;

      class Office extends DataObject implements OfficeInterface

      /**
      * @return string
      */
      public function getName()

      return (string)$this->_getData('name');


      /**
      * @return string
      */
      public function getLocation()

      return (string)$this->_getData('location');




      This should show select field and update it when address is changed. But we are missing one more element for frontend manipulation. We need to create function that will call the endpoint. Call to it is already included in Vendor/Module/view/frontend/web/js/view/checkout/shipping/form.js and it is Vendor_Module/js/view/checkout/shipping/office-service class which should go to Vendor/Module/view/frontend/web/js/view/checkout/shipping/office-service.js with the following code:



      define(
      [
      'Vendor_Module/js/view/checkout/shipping/model/resource-url-manager',
      'Magento_Checkout/js/model/quote',
      'Magento_Customer/js/model/customer',
      'mage/storage',
      'Magento_Checkout/js/model/shipping-service',
      'Vendor_Module/js/view/checkout/shipping/model/office-registry',
      'Magento_Checkout/js/model/error-processor'
      ],
      function (resourceUrlManager, quote, customer, storage, shippingService, officeRegistry, errorProcessor)
      'use strict';

      return
      /**
      * Get nearest machine list for specified address
      * @param Object address
      */
      getOfficeList: function (address, form)
      shippingService.isLoading(true);
      var cacheKey = address.getCacheKey(),
      cache = officeRegistry.get(cacheKey),
      serviceUrl = resourceUrlManager.getUrlForOfficeList(quote);

      if (cache)
      form.setOfficeList(cache);
      shippingService.isLoading(false);
      else
      storage.get(
      serviceUrl, false
      ).done(
      function (result)
      officeRegistry.set(cacheKey, result);
      form.setOfficeList(result);

      ).fail(
      function (response)
      errorProcessor.process(response);

      ).always(
      function ()
      shippingService.isLoading(false);

      );


      ;

      );


      It uses 2 more js files. Vendor_Module/js/view/checkout/shipping/model/resource-url-manager creates a url to the endpoint and is pretty simple



      define(
      [
      'Magento_Customer/js/model/customer',
      'Magento_Checkout/js/model/quote',
      'Magento_Checkout/js/model/url-builder',
      'mageUtils'
      ],
      function(customer, quote, urlBuilder, utils)
      "use strict";
      return
      getUrlForOfficeList: function(quote, limit)
      var params = postcode: quote.shippingAddress().postcode, city: quote.shippingAddress().city;
      var urls =
      'default': '/module/get-office-list/:postcode/:city'
      ;
      return this.getUrl(urls, params);
      ,

      /** Get url for service */
      getUrl: function(urls, urlParams)
      var url;

      if (utils.isEmpty(urls))
      return 'Provided service call does not exist.';


      if (!utils.isEmpty(urls['default']))
      url = urls['default'];
      else
      url = urls[this.getCheckoutMethod()];

      return urlBuilder.createUrl(url, urlParams);
      ,

      getCheckoutMethod: function()
      return customer.isLoggedIn() ? 'customer' : 'guest';

      ;

      );


      Vendor_Module/js/view/checkout/shipping/model/office-registry is a way of keeping result in the local storage. Its code is:



      define(
      [],
      function()
      "use strict";
      var cache = [];
      return
      get: function(addressKey)
      if (cache[addressKey])
      return cache[addressKey];

      return false;
      ,
      set: function(addressKey, data)
      cache[addressKey] = data;

      ;

      );


      Ok, so we should have all working on frontend. But now there is another problem to resolve. Since checkout doesn't know anything about this form it won't send the selection result to the backend. To make this happen we need to use extension_attributes feature. This is a way in magento2 to inform the system that some additional data are expected to be in the rest calls. Without it magento would filter out those data and they would never reach the code.



      So first in Vendor/Module/etc/extension_attributes.xml define:



      <?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="MagentoQuoteApiDataAddressInterface">
      <attribute code="carrier_office" type="string"/>
      </extension_attributes>
      </config>


      This value is already inserted in the request in form.js by this.selectedOffice.subscribe() definition. So the above configuration will only pass it at the entrance. To fetch it in the code create a plugin in Vendor/Module/etc/di.xml



      <type name="MagentoQuoteModelQuoteAddress">
      <plugin name="inpost-address" type="VendorModuleQuoteAddressPlugin" sortOrder="1" disabled="false"/>
      </type>


      Inside that class



      namespace VendorModulePluginQuote;

      use MagentoQuoteModelQuoteAddress;
      use VendorModuleModelCarrier;

      class AddressPlugin

      /**
      * Hook into setShippingMethod.
      * As this is magic function processed by __call method we need to hook around __call
      * to get the name of the called method. after__call does not provide this information.
      *
      * @param Address $subject
      * @param callable $proceed
      * @param string $method
      * @param mixed $vars
      * @return Address
      */
      public function around__call($subject, $proceed, $method, $vars)

      $result = $proceed($method, $vars);
      if ($method == 'setShippingMethod'
      && $vars[0] == Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
      && $subject->getExtensionAttributes()
      && $subject->getExtensionAttributes()->getCarrierOffice()
      )
      $subject->setCarrierOffice($subject->getExtensionAttributes()->getCarrierOffice());

      elseif (
      $method == 'setShippingMethod'
      && $vars[0] != Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
      )
      //reset office when changing shipping method
      $subject->getCarrierOffice(null);

      return $result;




      Of course where you will save the value depends entirely on your requirements. The above code would require creating additional column carrier_office in quote_address and sales_address tables and an event (in Vendor/Module/etc/events.xml)



      <?xml version="1.0"?>

      <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
      <event name="sales_model_service_quote_submit_before">
      <observer name="copy_carrier_office" instance="VendorModuleObserverModelOrder" />
      </event>
      </config>


      That would copy data saved in quote address to sales address.



      I wrote this for my module for polish carrier InPost so I changed some names which might break the code but I hope this will give you what you need.



      [EDIT]



      Carrier model asked by @sangan



      namespace VendorModuleModel;

      use MagentoFrameworkAppConfigScopeConfigInterface;
      use MagentoFrameworkPhrase;
      use MagentoQuoteModelQuoteAddressRateRequest;
      use MagentoShippingModelCarrierAbstractCarrier;
      use MagentoShippingModelCarrierCarrierInterface;
      use MagentoShippingModelSimplexmlElementFactory;

      class Carrier extends AbstractCarrier implements CarrierInterface
      null
      */
      public function collectRates(RateRequest $request)

      if (!$this->getConfigData('active'))
      return false;


      /** @var MagentoShippingModelRateResult $result */
      $result = $this->_rateFactory->create();

      /** @var MagentoQuoteModelQuoteAddressRateResultMethod $method */
      $method = $this->_rateMethodFactory->create();
      $method->setCarrier($this->_code);
      $method->setCarrierTitle($this->getConfigData('title'));

      $price = $this->getFinalPriceWithHandlingFee(0);
      $method->setMethod(self::METHOD_CODE);
      $method->setMethodTitle(new Phrase('MyMethod'));
      $method->setPrice($price);
      $method->setCost($price);
      $result->append($method);;

      return $result;



      /**
      * @return array
      */
      public function getAllowedMethods()

      $methods = [
      'mymethod' => new Phrase('MyMethod')
      ];
      return $methods;








      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited Jun 15 at 6:31









      Sagar Guhe

      9411 bronze badges




      9411 bronze badges










      answered Mar 9 '17 at 23:52









      ZefirynZefiryn

      4,6632 gold badges17 silver badges28 bronze badges




      4,6632 gold badges17 silver badges28 bronze badges












      • Thank you for your extended reply, i’ll try to solve my issue using your method and will reply with the result these days.

        – Siarhey Uchukhlebau
        Mar 10 '17 at 8:05











      • @Zefiryn I have created a custom shipping method, below it will display a dropdown having shipping account numbers of customer(there is a custom customer attribute created) so if I have to display this dropdown how much percent of your code will be helpful? What should I pick up from the code you provided?

        – Shireen N
        Oct 6 '17 at 14:38












      • @shireen I would say around 70%. You need to change the part where it fetches machines to account numbers. So api definition will be slighlty diferent and js part of it

        – Zefiryn
        Oct 6 '17 at 17:27











      • i have tried this module ...but its not showing any changes so please share the working module.if any

        – sangan
        Nov 7 '17 at 7:09











      • after adding successful module ..in checkout ajax loading continuously.. in console error showing like below : require.js:166 Uncaught Error: Script error for: Vendor_Module/js/view/checkout/shipping/model/office-registry. requirejs.org/docs/errors.html#scripterror

        – sangan
        Nov 8 '17 at 6:07


















      • Thank you for your extended reply, i’ll try to solve my issue using your method and will reply with the result these days.

        – Siarhey Uchukhlebau
        Mar 10 '17 at 8:05











      • @Zefiryn I have created a custom shipping method, below it will display a dropdown having shipping account numbers of customer(there is a custom customer attribute created) so if I have to display this dropdown how much percent of your code will be helpful? What should I pick up from the code you provided?

        – Shireen N
        Oct 6 '17 at 14:38












      • @shireen I would say around 70%. You need to change the part where it fetches machines to account numbers. So api definition will be slighlty diferent and js part of it

        – Zefiryn
        Oct 6 '17 at 17:27











      • i have tried this module ...but its not showing any changes so please share the working module.if any

        – sangan
        Nov 7 '17 at 7:09











      • after adding successful module ..in checkout ajax loading continuously.. in console error showing like below : require.js:166 Uncaught Error: Script error for: Vendor_Module/js/view/checkout/shipping/model/office-registry. requirejs.org/docs/errors.html#scripterror

        – sangan
        Nov 8 '17 at 6:07

















      Thank you for your extended reply, i’ll try to solve my issue using your method and will reply with the result these days.

      – Siarhey Uchukhlebau
      Mar 10 '17 at 8:05





      Thank you for your extended reply, i’ll try to solve my issue using your method and will reply with the result these days.

      – Siarhey Uchukhlebau
      Mar 10 '17 at 8:05













      @Zefiryn I have created a custom shipping method, below it will display a dropdown having shipping account numbers of customer(there is a custom customer attribute created) so if I have to display this dropdown how much percent of your code will be helpful? What should I pick up from the code you provided?

      – Shireen N
      Oct 6 '17 at 14:38






      @Zefiryn I have created a custom shipping method, below it will display a dropdown having shipping account numbers of customer(there is a custom customer attribute created) so if I have to display this dropdown how much percent of your code will be helpful? What should I pick up from the code you provided?

      – Shireen N
      Oct 6 '17 at 14:38














      @shireen I would say around 70%. You need to change the part where it fetches machines to account numbers. So api definition will be slighlty diferent and js part of it

      – Zefiryn
      Oct 6 '17 at 17:27





      @shireen I would say around 70%. You need to change the part where it fetches machines to account numbers. So api definition will be slighlty diferent and js part of it

      – Zefiryn
      Oct 6 '17 at 17:27













      i have tried this module ...but its not showing any changes so please share the working module.if any

      – sangan
      Nov 7 '17 at 7:09





      i have tried this module ...but its not showing any changes so please share the working module.if any

      – sangan
      Nov 7 '17 at 7:09













      after adding successful module ..in checkout ajax loading continuously.. in console error showing like below : require.js:166 Uncaught Error: Script error for: Vendor_Module/js/view/checkout/shipping/model/office-registry. requirejs.org/docs/errors.html#scripterror

      – sangan
      Nov 8 '17 at 6:07






      after adding successful module ..in checkout ajax loading continuously.. in console error showing like below : require.js:166 Uncaught Error: Script error for: Vendor_Module/js/view/checkout/shipping/model/office-registry. requirejs.org/docs/errors.html#scripterror

      – sangan
      Nov 8 '17 at 6:07














      2














      I am adding new answer to expand on what was already provided previously but without distorting it.



      This is the route that QuoteAddressPlugin was hooking into:



      1. MagentoCheckoutApiShippingInformationManagementInterface::saveAddressInformation()
      2. MagentoQuoteModelQuoteRepository::save()
      3. MagentoQuoteModelQuoteRepositorySaveHandler::save()
      4. MagentoQuoteModelQuoteRepositorySaveHandler::processShippingAssignment()
      5. MagentoQuoteModelQuoteShippingAssignmentShippingAssignmentPersister::save()
      6. MagentoQuoteModelQuoteShippingAssignmentShippingAssignmentProcessor::save()
      7. MagentoQuoteModelQuoteShippingAssignmentShippingProcessor::save()
      8. MagentoQuoteModelShippingMethodManagement::apply()


      The last method was calling MagentoQuoteModelQuoteAddress::setShippingMethod() which was actually call for MagentoQuoteModelQuoteAddress::__call() which I used. Right now I found a better place for the plugin, it is MagentoQuoteModelShippingAssignment::setShipping() method. So the plugin part can be rewritten to:



      <type name="MagentoQuoteModelShippingAssignment">
      <plugin name="carrier-office-plugin" type="VendorModulePluginQuoteShippingAssignmentPlugin" sortOrder="1" disabled="false"/>
      </type>


      and plugin itself:



      namespace VednorModulePluginQuote;

      use MagentoQuoteApiDataAddressInterface;
      use MagentoQuoteApiDataShippingInterface;
      use MagentoQuoteModelShippingAssignment;
      use VendorModuleModelCarrier;

      /**
      * ShippingAssignmentPlugin
      */
      class ShippingAssignmentPlugin

      /**
      * Hook into setShipping.
      *
      * @param ShippingAssignment $subject
      * @param ShippingInterface $value
      * @return Address
      */
      public function beforeSetShipping($subject, ShippingInterface $value)

      $method = $value->getMethod();
      /** @var AddressInterface $address */
      $address = $value->getAddress();
      if ($method === Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
      && $address->getExtensionAttributes()
      && $address->getExtensionAttributes()->getCarrierOffice()
      )
      $address->setCarrierOffice($address->getExtensionAttributes()->getCarrierOffice());

      elseif ($method !== Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE)
      //reset inpost machine when changing shipping method
      $address->setCarrierOffice(null);

      return [$value];







      share|improve this answer





























        2














        I am adding new answer to expand on what was already provided previously but without distorting it.



        This is the route that QuoteAddressPlugin was hooking into:



        1. MagentoCheckoutApiShippingInformationManagementInterface::saveAddressInformation()
        2. MagentoQuoteModelQuoteRepository::save()
        3. MagentoQuoteModelQuoteRepositorySaveHandler::save()
        4. MagentoQuoteModelQuoteRepositorySaveHandler::processShippingAssignment()
        5. MagentoQuoteModelQuoteShippingAssignmentShippingAssignmentPersister::save()
        6. MagentoQuoteModelQuoteShippingAssignmentShippingAssignmentProcessor::save()
        7. MagentoQuoteModelQuoteShippingAssignmentShippingProcessor::save()
        8. MagentoQuoteModelShippingMethodManagement::apply()


        The last method was calling MagentoQuoteModelQuoteAddress::setShippingMethod() which was actually call for MagentoQuoteModelQuoteAddress::__call() which I used. Right now I found a better place for the plugin, it is MagentoQuoteModelShippingAssignment::setShipping() method. So the plugin part can be rewritten to:



        <type name="MagentoQuoteModelShippingAssignment">
        <plugin name="carrier-office-plugin" type="VendorModulePluginQuoteShippingAssignmentPlugin" sortOrder="1" disabled="false"/>
        </type>


        and plugin itself:



        namespace VednorModulePluginQuote;

        use MagentoQuoteApiDataAddressInterface;
        use MagentoQuoteApiDataShippingInterface;
        use MagentoQuoteModelShippingAssignment;
        use VendorModuleModelCarrier;

        /**
        * ShippingAssignmentPlugin
        */
        class ShippingAssignmentPlugin

        /**
        * Hook into setShipping.
        *
        * @param ShippingAssignment $subject
        * @param ShippingInterface $value
        * @return Address
        */
        public function beforeSetShipping($subject, ShippingInterface $value)

        $method = $value->getMethod();
        /** @var AddressInterface $address */
        $address = $value->getAddress();
        if ($method === Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
        && $address->getExtensionAttributes()
        && $address->getExtensionAttributes()->getCarrierOffice()
        )
        $address->setCarrierOffice($address->getExtensionAttributes()->getCarrierOffice());

        elseif ($method !== Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE)
        //reset inpost machine when changing shipping method
        $address->setCarrierOffice(null);

        return [$value];







        share|improve this answer



























          2












          2








          2







          I am adding new answer to expand on what was already provided previously but without distorting it.



          This is the route that QuoteAddressPlugin was hooking into:



          1. MagentoCheckoutApiShippingInformationManagementInterface::saveAddressInformation()
          2. MagentoQuoteModelQuoteRepository::save()
          3. MagentoQuoteModelQuoteRepositorySaveHandler::save()
          4. MagentoQuoteModelQuoteRepositorySaveHandler::processShippingAssignment()
          5. MagentoQuoteModelQuoteShippingAssignmentShippingAssignmentPersister::save()
          6. MagentoQuoteModelQuoteShippingAssignmentShippingAssignmentProcessor::save()
          7. MagentoQuoteModelQuoteShippingAssignmentShippingProcessor::save()
          8. MagentoQuoteModelShippingMethodManagement::apply()


          The last method was calling MagentoQuoteModelQuoteAddress::setShippingMethod() which was actually call for MagentoQuoteModelQuoteAddress::__call() which I used. Right now I found a better place for the plugin, it is MagentoQuoteModelShippingAssignment::setShipping() method. So the plugin part can be rewritten to:



          <type name="MagentoQuoteModelShippingAssignment">
          <plugin name="carrier-office-plugin" type="VendorModulePluginQuoteShippingAssignmentPlugin" sortOrder="1" disabled="false"/>
          </type>


          and plugin itself:



          namespace VednorModulePluginQuote;

          use MagentoQuoteApiDataAddressInterface;
          use MagentoQuoteApiDataShippingInterface;
          use MagentoQuoteModelShippingAssignment;
          use VendorModuleModelCarrier;

          /**
          * ShippingAssignmentPlugin
          */
          class ShippingAssignmentPlugin

          /**
          * Hook into setShipping.
          *
          * @param ShippingAssignment $subject
          * @param ShippingInterface $value
          * @return Address
          */
          public function beforeSetShipping($subject, ShippingInterface $value)

          $method = $value->getMethod();
          /** @var AddressInterface $address */
          $address = $value->getAddress();
          if ($method === Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
          && $address->getExtensionAttributes()
          && $address->getExtensionAttributes()->getCarrierOffice()
          )
          $address->setCarrierOffice($address->getExtensionAttributes()->getCarrierOffice());

          elseif ($method !== Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE)
          //reset inpost machine when changing shipping method
          $address->setCarrierOffice(null);

          return [$value];







          share|improve this answer















          I am adding new answer to expand on what was already provided previously but without distorting it.



          This is the route that QuoteAddressPlugin was hooking into:



          1. MagentoCheckoutApiShippingInformationManagementInterface::saveAddressInformation()
          2. MagentoQuoteModelQuoteRepository::save()
          3. MagentoQuoteModelQuoteRepositorySaveHandler::save()
          4. MagentoQuoteModelQuoteRepositorySaveHandler::processShippingAssignment()
          5. MagentoQuoteModelQuoteShippingAssignmentShippingAssignmentPersister::save()
          6. MagentoQuoteModelQuoteShippingAssignmentShippingAssignmentProcessor::save()
          7. MagentoQuoteModelQuoteShippingAssignmentShippingProcessor::save()
          8. MagentoQuoteModelShippingMethodManagement::apply()


          The last method was calling MagentoQuoteModelQuoteAddress::setShippingMethod() which was actually call for MagentoQuoteModelQuoteAddress::__call() which I used. Right now I found a better place for the plugin, it is MagentoQuoteModelShippingAssignment::setShipping() method. So the plugin part can be rewritten to:



          <type name="MagentoQuoteModelShippingAssignment">
          <plugin name="carrier-office-plugin" type="VendorModulePluginQuoteShippingAssignmentPlugin" sortOrder="1" disabled="false"/>
          </type>


          and plugin itself:



          namespace VednorModulePluginQuote;

          use MagentoQuoteApiDataAddressInterface;
          use MagentoQuoteApiDataShippingInterface;
          use MagentoQuoteModelShippingAssignment;
          use VendorModuleModelCarrier;

          /**
          * ShippingAssignmentPlugin
          */
          class ShippingAssignmentPlugin

          /**
          * Hook into setShipping.
          *
          * @param ShippingAssignment $subject
          * @param ShippingInterface $value
          * @return Address
          */
          public function beforeSetShipping($subject, ShippingInterface $value)

          $method = $value->getMethod();
          /** @var AddressInterface $address */
          $address = $value->getAddress();
          if ($method === Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
          && $address->getExtensionAttributes()
          && $address->getExtensionAttributes()->getCarrierOffice()
          )
          $address->setCarrierOffice($address->getExtensionAttributes()->getCarrierOffice());

          elseif ($method !== Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE)
          //reset inpost machine when changing shipping method
          $address->setCarrierOffice(null);

          return [$value];








          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Jun 14 at 14:58









          Sagar Guhe

          9411 bronze badges




          9411 bronze badges










          answered Jan 6 '18 at 15:18









          ZefirynZefiryn

          4,6632 gold badges17 silver badges28 bronze badges




          4,6632 gold badges17 silver badges28 bronze badges





















              1














              @Zefiryn, I came across the problem with:
              quote.shippingAddress().extensionAttributes.carrier_office = office;



              When I get into checkout first time (new private window) as a guest (but the same occurs with registered client) attribute office is not saved into database after first "Next". Although in console I see correct output for: console.log(quote.shippingAddress().extensionAttributes.carrier_office);



              When I get back to the first checkout page and select office again then it is saved. What could be the reason of this behavior?



              I tried to use:
              address.trigger_reload = new Date().getTime();
              rateRegistry.set(address.getKey(), null);
              rateRegistry.set(address.getCacheKey(), null);
              quote.shippingAddress(address);



              but without success...






              share|improve this answer



























                1














                @Zefiryn, I came across the problem with:
                quote.shippingAddress().extensionAttributes.carrier_office = office;



                When I get into checkout first time (new private window) as a guest (but the same occurs with registered client) attribute office is not saved into database after first "Next". Although in console I see correct output for: console.log(quote.shippingAddress().extensionAttributes.carrier_office);



                When I get back to the first checkout page and select office again then it is saved. What could be the reason of this behavior?



                I tried to use:
                address.trigger_reload = new Date().getTime();
                rateRegistry.set(address.getKey(), null);
                rateRegistry.set(address.getCacheKey(), null);
                quote.shippingAddress(address);



                but without success...






                share|improve this answer

























                  1












                  1








                  1







                  @Zefiryn, I came across the problem with:
                  quote.shippingAddress().extensionAttributes.carrier_office = office;



                  When I get into checkout first time (new private window) as a guest (but the same occurs with registered client) attribute office is not saved into database after first "Next". Although in console I see correct output for: console.log(quote.shippingAddress().extensionAttributes.carrier_office);



                  When I get back to the first checkout page and select office again then it is saved. What could be the reason of this behavior?



                  I tried to use:
                  address.trigger_reload = new Date().getTime();
                  rateRegistry.set(address.getKey(), null);
                  rateRegistry.set(address.getCacheKey(), null);
                  quote.shippingAddress(address);



                  but without success...






                  share|improve this answer













                  @Zefiryn, I came across the problem with:
                  quote.shippingAddress().extensionAttributes.carrier_office = office;



                  When I get into checkout first time (new private window) as a guest (but the same occurs with registered client) attribute office is not saved into database after first "Next". Although in console I see correct output for: console.log(quote.shippingAddress().extensionAttributes.carrier_office);



                  When I get back to the first checkout page and select office again then it is saved. What could be the reason of this behavior?



                  I tried to use:
                  address.trigger_reload = new Date().getTime();
                  rateRegistry.set(address.getKey(), null);
                  rateRegistry.set(address.getCacheKey(), null);
                  quote.shippingAddress(address);



                  but without success...







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Jul 3 '18 at 11:46









                  user2089098user2089098

                  661 silver badge5 bronze badges




                  661 silver badge5 bronze badges





















                      0














                      @Zefiryn, Can you explain in few words how does your above plugin work?
                      Im little confused because as I know __call method is executed if we try execute method that not exists for particular object. It seems to be true because in app/code/Magento/Quote/Model/Quote/Address.php I dont see such method - only comment:



                      /**
                      * Sales Quote address model
                      ...
                      * @method Address setShippingMethod(string $value)



                      1. Why do you use around interception when there is no method implementation?

                      2. Next I see $subject->setInpostMachine and $subject->getCarrierOffice(null); Does it mean that above plugin's method will be executed again as there is no setInpostMachine() and getCarrierOffice() method in Adress Class? It looks like loop to me.

                      3. From where Magento execute setShippingMethod()? How normally this method is used? I cant find any simillar interceptions in Magento code.





                      share|improve this answer

























                      • Ok, so I prepared the answer based on a module I wrote for testing, it used inpost_machine field, so this one just did not get correctly changed to carrier_office in this place. Second, at the time I was developing this module I haven't found a place where I could get both selected carrier and address with extension attributes sent except setShippingMethod call on AddressInterface object and since there is no such method then I had to use around__call to see if setShippingMethod was called or some other magic field. Right now I've found a better place and I will post it in new reply.

                        – Zefiryn
                        Jan 6 '18 at 15:11















                      0














                      @Zefiryn, Can you explain in few words how does your above plugin work?
                      Im little confused because as I know __call method is executed if we try execute method that not exists for particular object. It seems to be true because in app/code/Magento/Quote/Model/Quote/Address.php I dont see such method - only comment:



                      /**
                      * Sales Quote address model
                      ...
                      * @method Address setShippingMethod(string $value)



                      1. Why do you use around interception when there is no method implementation?

                      2. Next I see $subject->setInpostMachine and $subject->getCarrierOffice(null); Does it mean that above plugin's method will be executed again as there is no setInpostMachine() and getCarrierOffice() method in Adress Class? It looks like loop to me.

                      3. From where Magento execute setShippingMethod()? How normally this method is used? I cant find any simillar interceptions in Magento code.





                      share|improve this answer

























                      • Ok, so I prepared the answer based on a module I wrote for testing, it used inpost_machine field, so this one just did not get correctly changed to carrier_office in this place. Second, at the time I was developing this module I haven't found a place where I could get both selected carrier and address with extension attributes sent except setShippingMethod call on AddressInterface object and since there is no such method then I had to use around__call to see if setShippingMethod was called or some other magic field. Right now I've found a better place and I will post it in new reply.

                        – Zefiryn
                        Jan 6 '18 at 15:11













                      0












                      0








                      0







                      @Zefiryn, Can you explain in few words how does your above plugin work?
                      Im little confused because as I know __call method is executed if we try execute method that not exists for particular object. It seems to be true because in app/code/Magento/Quote/Model/Quote/Address.php I dont see such method - only comment:



                      /**
                      * Sales Quote address model
                      ...
                      * @method Address setShippingMethod(string $value)



                      1. Why do you use around interception when there is no method implementation?

                      2. Next I see $subject->setInpostMachine and $subject->getCarrierOffice(null); Does it mean that above plugin's method will be executed again as there is no setInpostMachine() and getCarrierOffice() method in Adress Class? It looks like loop to me.

                      3. From where Magento execute setShippingMethod()? How normally this method is used? I cant find any simillar interceptions in Magento code.





                      share|improve this answer















                      @Zefiryn, Can you explain in few words how does your above plugin work?
                      Im little confused because as I know __call method is executed if we try execute method that not exists for particular object. It seems to be true because in app/code/Magento/Quote/Model/Quote/Address.php I dont see such method - only comment:



                      /**
                      * Sales Quote address model
                      ...
                      * @method Address setShippingMethod(string $value)



                      1. Why do you use around interception when there is no method implementation?

                      2. Next I see $subject->setInpostMachine and $subject->getCarrierOffice(null); Does it mean that above plugin's method will be executed again as there is no setInpostMachine() and getCarrierOffice() method in Adress Class? It looks like loop to me.

                      3. From where Magento execute setShippingMethod()? How normally this method is used? I cant find any simillar interceptions in Magento code.






                      share|improve this answer














                      share|improve this answer



                      share|improve this answer








                      edited Dec 23 '17 at 22:23

























                      answered Dec 23 '17 at 20:58









                      user2089098user2089098

                      661 silver badge5 bronze badges




                      661 silver badge5 bronze badges












                      • Ok, so I prepared the answer based on a module I wrote for testing, it used inpost_machine field, so this one just did not get correctly changed to carrier_office in this place. Second, at the time I was developing this module I haven't found a place where I could get both selected carrier and address with extension attributes sent except setShippingMethod call on AddressInterface object and since there is no such method then I had to use around__call to see if setShippingMethod was called or some other magic field. Right now I've found a better place and I will post it in new reply.

                        – Zefiryn
                        Jan 6 '18 at 15:11

















                      • Ok, so I prepared the answer based on a module I wrote for testing, it used inpost_machine field, so this one just did not get correctly changed to carrier_office in this place. Second, at the time I was developing this module I haven't found a place where I could get both selected carrier and address with extension attributes sent except setShippingMethod call on AddressInterface object and since there is no such method then I had to use around__call to see if setShippingMethod was called or some other magic field. Right now I've found a better place and I will post it in new reply.

                        – Zefiryn
                        Jan 6 '18 at 15:11
















                      Ok, so I prepared the answer based on a module I wrote for testing, it used inpost_machine field, so this one just did not get correctly changed to carrier_office in this place. Second, at the time I was developing this module I haven't found a place where I could get both selected carrier and address with extension attributes sent except setShippingMethod call on AddressInterface object and since there is no such method then I had to use around__call to see if setShippingMethod was called or some other magic field. Right now I've found a better place and I will post it in new reply.

                      – Zefiryn
                      Jan 6 '18 at 15:11





                      Ok, so I prepared the answer based on a module I wrote for testing, it used inpost_machine field, so this one just did not get correctly changed to carrier_office in this place. Second, at the time I was developing this module I haven't found a place where I could get both selected carrier and address with extension attributes sent except setShippingMethod call on AddressInterface object and since there is no such method then I had to use around__call to see if setShippingMethod was called or some other magic field. Right now I've found a better place and I will post it in new reply.

                      – Zefiryn
                      Jan 6 '18 at 15:11

















                      draft saved

                      draft discarded
















































                      Thanks for contributing an answer to Magento Stack Exchange!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid


                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.

                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function ()
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fmagento.stackexchange.com%2fquestions%2f153868%2fmagento-2-add-dropdown-list-to-shipping-method%23new-answer', 'question_page');

                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown







                      Popular posts from this blog

                      Get product attribute by attribute group code in magento 2get product attribute by product attribute group in magento 2Magento 2 Log Bundle Product Data in List Page?How to get all product attribute of a attribute group of Default attribute set?Magento 2.1 Create a filter in the product grid by new attributeMagento 2 : Get Product Attribute values By GroupMagento 2 How to get all existing values for one attributeMagento 2 get custom attribute of a single product inside a pluginMagento 2.3 How to get all the Multi Source Inventory (MSI) locations collection in custom module?Magento2: how to develop rest API to get new productsGet product attribute by attribute group code ( [attribute_group_code] ) in magento 2

                      Category:9 (number) SubcategoriesMedia in category "9 (number)"Navigation menuUpload mediaGND ID: 4485639-8Library of Congress authority ID: sh85091979ReasonatorScholiaStatistics

                      Magento 2.3: How do i solve this, Not registered handle, on custom form?How can i rewrite TierPrice Block in Magento2magento 2 captcha not rendering if I override layout xmlmain.CRITICAL: Plugin class doesn't existMagento 2 : Problem while adding custom button order view page?Magento 2.2.5: Overriding Admin Controller sales/orderMagento 2.2.5: Add, Update and Delete existing products Custom OptionsMagento 2.3 : File Upload issue in UI Component FormMagento2 Not registered handleHow to configured Form Builder Js in my custom magento 2.3.0 module?Magento 2.3. How to create image upload field in an admin form