ShippingMethodManagement.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Quote\Model;
  7. use Magento\Customer\Api\Data\AddressInterfaceFactory;
  8. use Magento\Framework\App\ObjectManager;
  9. use Magento\Framework\Exception\CouldNotSaveException;
  10. use Magento\Framework\Exception\InputException;
  11. use Magento\Framework\Exception\NoSuchEntityException;
  12. use Magento\Framework\Exception\StateException;
  13. use Magento\Framework\Reflection\DataObjectProcessor;
  14. use Magento\Quote\Api\Data\AddressInterface;
  15. use Magento\Quote\Api\Data\EstimateAddressInterface;
  16. use Magento\Quote\Api\ShipmentEstimationInterface;
  17. use Magento\Quote\Model\ResourceModel\Quote\Address as QuoteAddressResource;
  18. /**
  19. * Shipping method read service
  20. *
  21. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  22. */
  23. class ShippingMethodManagement implements
  24. \Magento\Quote\Api\ShippingMethodManagementInterface,
  25. \Magento\Quote\Model\ShippingMethodManagementInterface,
  26. ShipmentEstimationInterface
  27. {
  28. /**
  29. * Quote repository.
  30. *
  31. * @var \Magento\Quote\Api\CartRepositoryInterface
  32. */
  33. protected $quoteRepository;
  34. /**
  35. * Shipping method converter
  36. *
  37. * @var \Magento\Quote\Model\Cart\ShippingMethodConverter
  38. */
  39. protected $converter;
  40. /**
  41. * Customer Address repository
  42. *
  43. * @var \Magento\Customer\Api\AddressRepositoryInterface
  44. */
  45. protected $addressRepository;
  46. /**
  47. * @var Quote\TotalsCollector
  48. */
  49. protected $totalsCollector;
  50. /**
  51. * @var \Magento\Framework\Reflection\DataObjectProcessor $dataProcessor
  52. */
  53. private $dataProcessor;
  54. /**
  55. * @var AddressInterfaceFactory $addressFactory
  56. */
  57. private $addressFactory;
  58. /**
  59. * @var QuoteAddressResource
  60. */
  61. private $quoteAddressResource;
  62. /**
  63. * Constructor
  64. *
  65. * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository
  66. * @param Cart\ShippingMethodConverter $converter
  67. * @param \Magento\Customer\Api\AddressRepositoryInterface $addressRepository
  68. * @param Quote\TotalsCollector $totalsCollector
  69. * @param AddressInterfaceFactory|null $addressFactory
  70. * @param QuoteAddressResource|null $quoteAddressResource
  71. */
  72. public function __construct(
  73. \Magento\Quote\Api\CartRepositoryInterface $quoteRepository,
  74. Cart\ShippingMethodConverter $converter,
  75. \Magento\Customer\Api\AddressRepositoryInterface $addressRepository,
  76. \Magento\Quote\Model\Quote\TotalsCollector $totalsCollector,
  77. AddressInterfaceFactory $addressFactory = null,
  78. QuoteAddressResource $quoteAddressResource = null
  79. ) {
  80. $this->quoteRepository = $quoteRepository;
  81. $this->converter = $converter;
  82. $this->addressRepository = $addressRepository;
  83. $this->totalsCollector = $totalsCollector;
  84. $this->addressFactory = $addressFactory ?: ObjectManager::getInstance()
  85. ->get(AddressInterfaceFactory::class);
  86. $this->quoteAddressResource = $quoteAddressResource ?: ObjectManager::getInstance()
  87. ->get(QuoteAddressResource::class);
  88. }
  89. /**
  90. * {@inheritDoc}
  91. */
  92. public function get($cartId)
  93. {
  94. /** @var \Magento\Quote\Model\Quote $quote */
  95. $quote = $this->quoteRepository->getActive($cartId);
  96. /** @var \Magento\Quote\Model\Quote\Address $shippingAddress */
  97. $shippingAddress = $quote->getShippingAddress();
  98. if (!$shippingAddress->getCountryId()) {
  99. throw new StateException(__('The shipping address is missing. Set the address and try again.'));
  100. }
  101. $shippingMethod = $shippingAddress->getShippingMethod();
  102. if (!$shippingMethod) {
  103. return null;
  104. }
  105. $shippingAddress->collectShippingRates();
  106. /** @var \Magento\Quote\Model\Quote\Address\Rate $shippingRate */
  107. $shippingRate = $shippingAddress->getShippingRateByCode($shippingMethod);
  108. if (!$shippingRate) {
  109. return null;
  110. }
  111. return $this->converter->modelToDataObject($shippingRate, $quote->getQuoteCurrencyCode());
  112. }
  113. /**
  114. * {@inheritDoc}
  115. */
  116. public function getList($cartId)
  117. {
  118. $output = [];
  119. /** @var \Magento\Quote\Model\Quote $quote */
  120. $quote = $this->quoteRepository->getActive($cartId);
  121. // no methods applicable for empty carts or carts with virtual products
  122. if ($quote->isVirtual() || 0 == $quote->getItemsCount()) {
  123. return [];
  124. }
  125. $shippingAddress = $quote->getShippingAddress();
  126. if (!$shippingAddress->getCountryId()) {
  127. throw new StateException(__('The shipping address is missing. Set the address and try again.'));
  128. }
  129. $shippingAddress->collectShippingRates();
  130. $shippingRates = $shippingAddress->getGroupedAllShippingRates();
  131. foreach ($shippingRates as $carrierRates) {
  132. foreach ($carrierRates as $rate) {
  133. $output[] = $this->converter->modelToDataObject($rate, $quote->getQuoteCurrencyCode());
  134. }
  135. }
  136. return $output;
  137. }
  138. /**
  139. * {@inheritDoc}
  140. */
  141. public function set($cartId, $carrierCode, $methodCode)
  142. {
  143. /** @var \Magento\Quote\Model\Quote $quote */
  144. $quote = $this->quoteRepository->getActive($cartId);
  145. try {
  146. $this->apply($cartId, $carrierCode, $methodCode);
  147. } catch (\Exception $e) {
  148. throw $e;
  149. }
  150. try {
  151. $this->quoteRepository->save($quote->collectTotals());
  152. } catch (\Exception $e) {
  153. throw new CouldNotSaveException(__('The shipping method can\'t be set. %1', $e->getMessage()));
  154. }
  155. return true;
  156. }
  157. /**
  158. * @param int $cartId The shopping cart ID.
  159. * @param string $carrierCode The carrier code.
  160. * @param string $methodCode The shipping method code.
  161. * @return void
  162. * @throws InputException The shipping method is not valid for an empty cart.
  163. * @throws NoSuchEntityException CThe Cart includes virtual product(s) only, so a shipping address is not used.
  164. * @throws StateException The billing or shipping address is not set.
  165. * @throws \Exception
  166. */
  167. public function apply($cartId, $carrierCode, $methodCode)
  168. {
  169. /** @var \Magento\Quote\Model\Quote $quote */
  170. $quote = $this->quoteRepository->getActive($cartId);
  171. if (0 == $quote->getItemsCount()) {
  172. throw new InputException(
  173. __('The shipping method can\'t be set for an empty cart. Add an item to cart and try again.')
  174. );
  175. }
  176. if ($quote->isVirtual()) {
  177. throw new NoSuchEntityException(
  178. __('The Cart includes virtual product(s) only, so a shipping address is not used.')
  179. );
  180. }
  181. $shippingAddress = $quote->getShippingAddress();
  182. if (!$shippingAddress->getCountryId()) {
  183. // Remove empty quote address
  184. $this->quoteAddressResource->delete($shippingAddress);
  185. throw new StateException(__('The shipping address is missing. Set the address and try again.'));
  186. }
  187. $shippingAddress->setShippingMethod($carrierCode . '_' . $methodCode);
  188. }
  189. /**
  190. * {@inheritDoc}
  191. */
  192. public function estimateByAddress($cartId, \Magento\Quote\Api\Data\EstimateAddressInterface $address)
  193. {
  194. /** @var \Magento\Quote\Model\Quote $quote */
  195. $quote = $this->quoteRepository->getActive($cartId);
  196. // no methods applicable for empty carts or carts with virtual products
  197. if ($quote->isVirtual() || 0 == $quote->getItemsCount()) {
  198. return [];
  199. }
  200. return $this->getShippingMethods($quote, $address);
  201. }
  202. /**
  203. * @inheritdoc
  204. */
  205. public function estimateByExtendedAddress($cartId, AddressInterface $address)
  206. {
  207. /** @var \Magento\Quote\Model\Quote $quote */
  208. $quote = $this->quoteRepository->getActive($cartId);
  209. // no methods applicable for empty carts or carts with virtual products
  210. if ($quote->isVirtual() || 0 == $quote->getItemsCount()) {
  211. return [];
  212. }
  213. return $this->getShippingMethods($quote, $address);
  214. }
  215. /**
  216. * {@inheritDoc}
  217. */
  218. public function estimateByAddressId($cartId, $addressId)
  219. {
  220. /** @var \Magento\Quote\Model\Quote $quote */
  221. $quote = $this->quoteRepository->getActive($cartId);
  222. // no methods applicable for empty carts or carts with virtual products
  223. if ($quote->isVirtual() || 0 == $quote->getItemsCount()) {
  224. return [];
  225. }
  226. $address = $this->addressRepository->getById($addressId);
  227. return $this->getShippingMethods($quote, $address);
  228. }
  229. /**
  230. * Get estimated rates
  231. *
  232. * @param Quote $quote
  233. * @param int $country
  234. * @param string $postcode
  235. * @param int $regionId
  236. * @param string $region
  237. * @param \Magento\Framework\Api\ExtensibleDataInterface|null $address
  238. * @return \Magento\Quote\Api\Data\ShippingMethodInterface[] An array of shipping methods.
  239. * @deprecated 100.1.6
  240. */
  241. protected function getEstimatedRates(
  242. \Magento\Quote\Model\Quote $quote,
  243. $country,
  244. $postcode,
  245. $regionId,
  246. $region,
  247. $address = null
  248. ) {
  249. if (!$address) {
  250. $address = $this->getAddressFactory()->create()
  251. ->setCountryId($country)
  252. ->setPostcode($postcode)
  253. ->setRegionId($regionId)
  254. ->setRegion($region);
  255. }
  256. return $this->getShippingMethods($quote, $address);
  257. }
  258. /**
  259. * Get list of available shipping methods
  260. *
  261. * @param \Magento\Quote\Model\Quote $quote
  262. * @param \Magento\Framework\Api\ExtensibleDataInterface $address
  263. * @return \Magento\Quote\Api\Data\ShippingMethodInterface[]
  264. */
  265. private function getShippingMethods(Quote $quote, $address)
  266. {
  267. $output = [];
  268. $shippingAddress = $quote->getShippingAddress();
  269. $shippingAddress->addData($this->extractAddressData($address));
  270. $shippingAddress->setCollectShippingRates(true);
  271. $this->totalsCollector->collectAddressTotals($quote, $shippingAddress);
  272. $shippingRates = $shippingAddress->getGroupedAllShippingRates();
  273. foreach ($shippingRates as $carrierRates) {
  274. foreach ($carrierRates as $rate) {
  275. $output[] = $this->converter->modelToDataObject($rate, $quote->getQuoteCurrencyCode());
  276. }
  277. }
  278. return $output;
  279. }
  280. /**
  281. * Get transform address interface into Array
  282. *
  283. * @param \Magento\Framework\Api\ExtensibleDataInterface $address
  284. * @return array
  285. */
  286. private function extractAddressData($address)
  287. {
  288. $className = \Magento\Customer\Api\Data\AddressInterface::class;
  289. if ($address instanceof \Magento\Quote\Api\Data\AddressInterface) {
  290. $className = \Magento\Quote\Api\Data\AddressInterface::class;
  291. } elseif ($address instanceof EstimateAddressInterface) {
  292. $className = EstimateAddressInterface::class;
  293. }
  294. return $this->getDataObjectProcessor()->buildOutputDataArray(
  295. $address,
  296. $className
  297. );
  298. }
  299. /**
  300. * Gets the data object processor
  301. *
  302. * @return \Magento\Framework\Reflection\DataObjectProcessor
  303. * @deprecated 101.0.0
  304. */
  305. private function getDataObjectProcessor()
  306. {
  307. if ($this->dataProcessor === null) {
  308. $this->dataProcessor = ObjectManager::getInstance()
  309. ->get(DataObjectProcessor::class);
  310. }
  311. return $this->dataProcessor;
  312. }
  313. }