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;
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
add a comment |
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
@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
add a comment |
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
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
magento-2.1 checkout shipping-methods
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
add a comment |
@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
add a comment |
4 Answers
4
active
oldest
votes
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;
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
|
show 9 more comments
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];
add a comment |
@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...
add a comment |
@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)
- Why do you use around interception when there is no method implementation?
- 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. - From where Magento execute
setShippingMethod()
? How normally this method is used? I cant find any simillar interceptions in Magento code.
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 exceptsetShippingMethod
call onAddressInterface
object and since there is no such method then I had to use around__call to see ifsetShippingMethod
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
add a comment |
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
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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;
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
|
show 9 more comments
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;
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
|
show 9 more comments
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;
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;
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
|
show 9 more comments
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
|
show 9 more comments
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];
add a comment |
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];
add a comment |
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];
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];
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
add a comment |
add a comment |
@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...
add a comment |
@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...
add a comment |
@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...
@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...
answered Jul 3 '18 at 11:46
user2089098user2089098
661 silver badge5 bronze badges
661 silver badge5 bronze badges
add a comment |
add a comment |
@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)
- Why do you use around interception when there is no method implementation?
- 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. - From where Magento execute
setShippingMethod()
? How normally this method is used? I cant find any simillar interceptions in Magento code.
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 exceptsetShippingMethod
call onAddressInterface
object and since there is no such method then I had to use around__call to see ifsetShippingMethod
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
add a comment |
@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)
- Why do you use around interception when there is no method implementation?
- 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. - From where Magento execute
setShippingMethod()
? How normally this method is used? I cant find any simillar interceptions in Magento code.
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 exceptsetShippingMethod
call onAddressInterface
object and since there is no such method then I had to use around__call to see ifsetShippingMethod
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
add a comment |
@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)
- Why do you use around interception when there is no method implementation?
- 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. - From where Magento execute
setShippingMethod()
? How normally this method is used? I cant find any simillar interceptions in Magento code.
@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)
- Why do you use around interception when there is no method implementation?
- 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. - From where Magento execute
setShippingMethod()
? How normally this method is used? I cant find any simillar interceptions in Magento code.
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 exceptsetShippingMethod
call onAddressInterface
object and since there is no such method then I had to use around__call to see ifsetShippingMethod
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
add a comment |
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 exceptsetShippingMethod
call onAddressInterface
object and since there is no such method then I had to use around__call to see ifsetShippingMethod
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
add a comment |
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.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
@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