Il checkout Magento non supporta alcun tipo di modulo per dati aggiuntivi sul metodo di spedizione. Ma fornisce un shippingAdditional
blocco nel checkout che può essere utilizzato per questo. La seguente soluzione funzionerà per il checkout magento standard.
Per prima cosa prepariamo il nostro contenitore dove possiamo mettere un po 'di modulo. Per fare ciò, creare un file inview/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>
Ora crea un file in Vendor/Module/view/frontend/web/js/view/checkout/shipping/form.js
cui verrà visualizzato un modello knockout. Il suo contenuto è simile al seguente
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();
}
});
});
Questo file utilizza un modello knockout che dovrebbe essere inserito 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>
Ora abbiamo un campo selezionato che sarà visibile quando il nostro metodo (definito dal suo codice) sarà selezionato nella tabella dei metodi di spedizione. È ora di riempirlo con alcune opzioni. Poiché i valori dipendono dall'indirizzo, il modo migliore è creare un endpoint di riposo che fornirà le opzioni disponibili. NelVendor/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="Vendor\Module\Api\OfficeManagementInterface" method="fetchOffices"/>
<resources>
<resource ref="anonymous" />
</resources>
</route>
</routes>
Ora definisci l'interfaccia in Vendor/Module/Api/OfficeManagementInterface.php
as
namespace Vendor\Module\Api;
interface OfficeManagementInterface
{
/**
* Find offices for the customer
*
* @param string $postcode
* @param string $city
* @return \Vendor\Module\Api\Data\OfficeInterface[]
*/
public function fetchOffices($postcode, $city);
}
Definire l'interfaccia per i dati dell'ufficio in Vendor\Module\Api\Data\OfficeInterface.php
. Questa interfaccia verrà utilizzata dal modulo webapi per filtrare i dati per l'output, quindi è necessario definire tutto ciò che è necessario aggiungere nella risposta.
namespace Vendor\Module\Api\Data;
/**
* Office Interface
*/
interface OfficeInterface
{
/**
* @return string
*/
public function getName();
/**
* @return string
*/
public function getLocation();
}
Tempo per le lezioni reali. Inizia con la creazione di preferenze per tutte le interfacce inVendor/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="Vendor\Module\Api\OfficeManagementInterface" type="Vendor\Module\Model\OfficeManagement" />
<preference for="Vendor\Module\Api\Data\OfficeInterface" type="Vendor\Module\Model\Office" />
</config>
Ora crea Vendor\Module\Model\OfficeManagement.php
classe che farà effettivamente la logica del recupero dei dati.
namespace Vednor\Module\Model;
use Vednor\Module\Api\OfficeManagementInterface;
use Vednor\Module\Api\Data\OfficeInterfaceFactory;
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 \Vendor\Module\Api\Data\OfficeInterface[]
*/
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;
}
}
E infine lezione per OfficeInterface
inVendor/Module/Model/Office.php
namespace Vendor\Module\Model;
use Magento\Framework\DataObject;
use Vendor\Module\Api\Data\OfficeInterface;
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');
}
}
Questo dovrebbe mostrare il campo di selezione e aggiornarlo quando l'indirizzo viene cambiato. Ma ci manca un altro elemento per la manipolazione del frontend. Dobbiamo creare una funzione che chiamerà l'endpoint. Call to it è già incluso in Vendor/Module/view/frontend/web/js/view/checkout/shipping/form.js
ed è Vendor_Module/js/view/checkout/shipping/office-service
class che dovrebbe andare Vendor/Module/view/frontend/web/js/view/checkout/shipping/office-service.js
con il seguente codice:
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);
}
);
}
}
};
}
);
Utilizza altri 2 file js. Vendor_Module/js/view/checkout/shipping/model/resource-url-manager
crea un URL per l'endpoint ed è piuttosto semplice
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
è un modo per mantenere il risultato nella memoria locale. Il suo codice è:
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, quindi dovremmo lavorare tutti sul frontend. Ma ora c'è un altro problema da risolvere. Poiché il checkout non è a conoscenza di questo modulo, non invierà il risultato della selezione al back-end. Per far sì che ciò accada, dobbiamo utilizzare la extension_attributes
funzione. Questo è un modo in magento2 di informare il sistema che alcuni dati aggiuntivi dovrebbero essere inclusi nelle chiamate rimanenti. Senza di essa il magento filtrerebbe quei dati e non raggiungerebbero mai il codice.
Quindi prima di tutto Vendor/Module/etc/extension_attributes.xml
definisci:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
<extension_attributes for="Magento\Quote\Api\Data\AddressInterface">
<attribute code="carrier_office" type="string"/>
</extension_attributes>
</config>
Questo valore è già inserito nella richiesta form.js
per this.selectedOffice.subscribe()
definizione. Quindi la configurazione sopra passerà solo all'ingresso. Per recuperarlo nel codice, crea un plug-inVendor/Module/etc/di.xml
<type name="Magento\Quote\Model\Quote\Address">
<plugin name="inpost-address" type="Vendor\Module\Quote\AddressPlugin" sortOrder="1" disabled="false"/>
</type>
All'interno di quella classe
namespace Vendor\Module\Plugin\Quote;
use Magento\Quote\Model\Quote\Address;
use Vendor\Module\Model\Carrier;
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;
}
}
Ovviamente dove risparmierai il valore dipende interamente dalle tue esigenze. Il codice sopra richiederebbe creando ulteriore colonna carrier_office
in quote_address
e sales_address
tabelle e un evento (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="Vendor\Module\Observer\Model\Order" />
</event>
</config>
Ciò copierebbe i dati salvati nell'indirizzo del preventivo all'indirizzo di vendita.
Ho scritto questo per il mio modulo per il corriere polacco InPost, quindi ho cambiato alcuni nomi che potrebbero violare il codice, ma spero che questo ti dia ciò di cui hai bisogno.
[MODIFICARE]
Modello Carrier richiesto da @sangan
namespace Vendor\Module\Model;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Phrase;
use Magento\Quote\Model\Quote\Address\RateRequest;
use Magento\Shipping\Model\Carrier\AbstractCarrier;
use Magento\Shipping\Model\Carrier\CarrierInterface;
use Magento\Shipping\Model\Simplexml\ElementFactory;
class Carrier extends AbstractCarrier implements CarrierInterface
{
const CARRIER_CODE = 'mycarier';
const METHOD_CODE = 'mymethod';
/** @var string */
protected $_code = self::CARRIER_CODE;
/** @var bool */
protected $_isFixed = true;
/**
* Prepare stores to show on frontend
*
* @param RateRequest $request
* @return \Magento\Framework\DataObject|bool|null
*/
public function collectRates(RateRequest $request)
{
if (!$this->getConfigData('active')) {
return false;
}
/** @var \Magento\Shipping\Model\Rate\Result $result */
$result = $this->_rateFactory->create();
/** @var \Magento\Quote\Model\Quote\Address\RateResult\Method $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;
}
}