diff --git a/Model/Express/ExpressShippingMethod.php b/Model/Shipping/ShippingMethod.php similarity index 88% rename from Model/Express/ExpressShippingMethod.php rename to Model/Shipping/ShippingMethod.php index ff7ef431..71fd0177 100644 --- a/Model/Express/ExpressShippingMethod.php +++ b/Model/Shipping/ShippingMethod.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Rvvup\Payments\Model\Express; +namespace Rvvup\Payments\Model\Shipping; use Magento\Quote\Api\Data\ShippingMethodInterface; -class ExpressShippingMethod +class ShippingMethod { /** @var string */ @@ -42,6 +42,9 @@ public function getId(): string return $this->id; } + /** + * @return string + */ public function getLabel(): string { return $this->label; @@ -55,11 +58,18 @@ public function getAmount(): string return $this->amount; } + /** + * @return string + */ public function getCurrency(): string { return $this->currency; } + /** + * @param ShippingMethodInterface $shippingMethod + * @return string + */ private function generateLabel(ShippingMethodInterface $shippingMethod): string { if ($shippingMethod->getCarrierTitle() === null && $shippingMethod->getMethodTitle() === null) { diff --git a/Service/Express/ExpressPaymentManager.php b/Service/Express/ExpressPaymentManager.php index c8f2f944..ebd3e351 100644 --- a/Service/Express/ExpressPaymentManager.php +++ b/Service/Express/ExpressPaymentManager.php @@ -6,46 +6,38 @@ use Magento\Checkout\Model\Type\Onepage; use Magento\Customer\Api\Data\GroupInterface; +use Magento\Customer\Model\Session; use Magento\Framework\Exception\InputException; use Magento\Quote\Api\CartRepositoryInterface; -use Magento\Quote\Api\ShipmentEstimationInterface; -use Magento\Checkout\Api\Data\ShippingInformationInterfaceFactory; -use Magento\Checkout\Api\ShippingInformationManagementInterface; use Magento\Quote\Model\Quote; -use Magento\Customer\Model\Session; use Magento\Quote\Model\Quote\Address; -use Rvvup\Payments\Model\Express\ExpressShippingMethod; +use Rvvup\Payments\Service\Shipping\ShippingMethodService; class ExpressPaymentManager { - /** @var ShipmentEstimationInterface */ - private $shipmentEstimation; - /** @var CartRepositoryInterface */ private $quoteRepository; - /** @var ShippingInformationInterfaceFactory */ - private $shippingInformationFactory; - - /** @var ShippingInformationManagementInterface */ - private $shippingInformationManagement; - /** @var Session */ private $customerSession; + /** @var ShippingMethodService */ + private $shippingMethodService; + + /** + * @param CartRepositoryInterface $quoteRepository + * @param Session $customerSession + * @param ShippingMethodService $shippingMethodService + */ public function __construct( - ShipmentEstimationInterface $shipmentEstimation, - CartRepositoryInterface $quoteRepository, - ShippingInformationInterfaceFactory $shippingInformationFactory, - ShippingInformationManagementInterface $shippingInformationManagement, - Session $customerSession + CartRepositoryInterface $quoteRepository, + Session $customerSession, + ShippingMethodService $shippingMethodService ) { - $this->shipmentEstimation = $shipmentEstimation; $this->quoteRepository = $quoteRepository; - $this->shippingInformationFactory = $shippingInformationFactory; - $this->shippingInformationManagement = $shippingInformationManagement; $this->customerSession = $customerSession; + $this->shippingMethodService = $shippingMethodService; } /** @@ -65,9 +57,8 @@ public function updateShippingAddress(Quote $quote, array $address): array ->setPostcode($address['postcode'] ?? null) ->setCollectShippingRates(true); - $shippingMethods = $this->getAvailableShippingMethods($quote); - $methodId = empty($shippingMethods) ? null : $shippingMethods[0]->getId(); - $this->setShippingMethodInQuote($quote, $methodId, $shippingAddress); + $shippingMethods = $this->shippingMethodService->setFirstShippingMethodInQuote($quote, $shippingAddress) + ["availableShippingMethods"]; $quote->setTotalsCollectedFlag(false); $quote->collectTotals(); @@ -78,59 +69,9 @@ public function updateShippingAddress(Quote $quote, array $address): array /** * @param Quote $quote - * @param string|null $methodId + * @param array $data * @return Quote */ - public function updateShippingMethod( - Quote $quote, - ?string $methodId - ): Quote { - $shippingAddress = $quote->getShippingAddress(); - $quote = $this->setShippingMethodInQuote($quote, $methodId, $shippingAddress); - - $quote->setTotalsCollectedFlag(false); - $quote->collectTotals(); - - $this->quoteRepository->save($quote); - - return $quote; - } - - /** - * @param Quote $quote - * @param string|null $methodId - * @param Address $shippingAddress - * @return Quote - * @throws InputException - */ - public function setShippingMethodInQuote( - Quote $quote, - ?string $methodId, - Quote\Address $shippingAddress - ): Quote { - $availableMethods = $this->getAvailableShippingMethods($quote); - $isMethodAvailable = count(array_filter($availableMethods, function ($method) use ($methodId) { - return $method->getId() === $methodId; - })) > 0; - - $carrierCodeToMethodCode = empty($methodId) ? [] : explode('_', $methodId); - - if (!$isMethodAvailable || count($carrierCodeToMethodCode) !== 2) { - $shippingAddress->setShippingMethod(''); - } else { - $shippingAddress->setShippingMethod($methodId)->setCollectShippingRates(true)->collectShippingRates(); - - $this->shippingInformationManagement->saveAddressInformation( - $quote->getId(), - $this->shippingInformationFactory->create() - ->setShippingAddress($shippingAddress) - ->setShippingCarrierCode($carrierCodeToMethodCode[0]) - ->setShippingMethodCode($carrierCodeToMethodCode[1]) - ); - } - return $quote; - } - public function updateQuoteBeforePaymentAuth(Quote $quote, array $data): Quote { if (!$quote->isVirtual() && @@ -172,9 +113,7 @@ public function updateQuoteBeforePaymentAuth(Quote $quote, array $data): Quote $selectedMethod = $shippingAddress->getShippingMethod(); // If the shipping method is not set then the first method was displayed in the sheet and was not changed if (empty($selectedMethod)) { - $shippingMethods = $this->getAvailableShippingMethods($quote); - $methodId = empty($shippingMethods) ? null : $shippingMethods[0]->getId(); - $this->setShippingMethodInQuote($quote, $methodId, $shippingAddress); + $this->shippingMethodService->setFirstShippingMethodInQuote($quote, $shippingAddress); } $quote->setTotalsCollectedFlag(false); @@ -184,31 +123,6 @@ public function updateQuoteBeforePaymentAuth(Quote $quote, array $data): Quote return $quote; } - /** - * @param Quote $quote - * @return ExpressShippingMethod[] $shippingMethods - * @throws InputException - */ - public function getAvailableShippingMethods(Quote $quote): array - { - $shippingMethods = $this->shipmentEstimation->estimateByExtendedAddress( - $quote->getId(), - $quote->getShippingAddress() - ); - if (empty($shippingMethods)) { - return []; - } - $returnedShippingMethods = []; - foreach ($shippingMethods as $shippingMethod) { - if ($shippingMethod->getErrorMessage()) { - continue; - } - - $returnedShippingMethods[] = new ExpressShippingMethod($shippingMethod, $quote->getQuoteCurrencyCode()); - } - return $returnedShippingMethods; - } - /** * @param Address $quoteAddress * @param array $contact diff --git a/Service/Express/ExpressPaymentRequestMapper.php b/Service/Express/ExpressPaymentRequestMapper.php new file mode 100644 index 00000000..184bd630 --- /dev/null +++ b/Service/Express/ExpressPaymentRequestMapper.php @@ -0,0 +1,173 @@ +shippingMethodService = $shippingMethodService; + } + + /** + * @param Quote $quote + * @return array returns object that can be passed into the Rvvup Sdk for Express Payment + */ + public function map(Quote $quote): array + { + $total = $quote->getGrandTotal(); + $result = [ + 'methodOptions' => [ + 'APPLE_PAY' => $this->getApplePayOptions($quote) + ], + 'total' => [ + 'amount' => is_numeric($total) ? number_format((float)$total, 2, '.', '') : $total, + 'currency' => $quote->getQuoteCurrencyCode() + ], + 'billing' => $this->mapAddress($quote->getBillingAddress()), + 'shipping' => $this->mapShippingAddress($quote), + ]; + $result['shippingMethods'] = $this->getShippingMethods($quote, $result['shipping'] !== null); + + // If methods are empty, need to choose a new address in the express sheet + if (empty($result['shippingMethods'])) { + $result['shipping'] = null; + } + return $result; + } + + /** + * @param Quote $quote + * @param bool $hasShippingAddress + * @return array|null + */ + private function getShippingMethods(Quote $quote, bool $hasShippingAddress): ?array + { + if ($quote->isVirtual()) { + return null; + } + // If address is not present then shipping methods will appear after the address update + if (!$hasShippingAddress) { + return null; + } + $availableMethods = $this->shippingMethodService->getAvailableShippingMethods($quote); + $shippingMethods = $this->mapShippingMethods($availableMethods); + if (empty($shippingMethods)) { + return null; + } + + $selectedMethod = $quote->getShippingAddress()->getShippingMethod(); + if (empty($selectedMethod)) { + $shippingMethods[0]['selected'] = true; + } else { + $numShippingMethods = count($shippingMethods); + for ($i = 0; $i < $numShippingMethods; $i++) { + if ($shippingMethods[$i]['id'] === $selectedMethod) { + $shippingMethods[$i]['selected'] = true; + break; + } + } + } + return $shippingMethods; + } + + /** + * @param ShippingMethod[] $shippingMethods + * @return array + */ + public function mapShippingMethods(array $shippingMethods): array + { + return array_reduce($shippingMethods, function ($carry, $method) { + $carry[] = [ + 'id' => $method->getId(), + 'label' => $method->getLabel(), + 'amount' => ['amount' => $method->getAmount(), 'currency' => $method->getCurrency()], + ]; + return $carry; + }, []); + } + + /** + * @param Quote $quote + * @return array|array[] + */ + private function getApplePayOptions(Quote $quote): array + { + $options = [ + 'paymentRequest' => [ + 'requiredBillingContactFields' => ['postalAddress', 'name', 'email', 'phone'], + // Apple quirk - We need these "shipping" fields to fill the billing email and phone + 'requiredShippingContactFields' => ['email', 'phone'] + ], + ]; + if (!$quote->isVirtual()) { + $options['paymentRequest']['requiredShippingContactFields'] = ['postalAddress', 'name', 'email', 'phone']; + $options['paymentRequest']['shippingType'] = 'shipping'; + $options['paymentRequest']['shippingContactEditingMode'] = 'available'; + } + + return $options; + } + + /** + * @param Address $quoteAddress + * @return array[] + */ + private function mapAddress(Quote\Address $quoteAddress): ?array + { + // We ignore country code because it's always pre-selected by magento. + // We also ignore region, city, postcode because apple partially sets this, if you cancel the sheet after a + // address change. We only pre-fill the apple sheet when the user has actively entered the other fields. + if ((!empty($quoteAddress->getStreet()) && !empty($quoteAddress->getStreet()[0])) || + !empty($quoteAddress->getFirstname()) || + !empty($quoteAddress->getLastname()) || + !empty($quoteAddress->getEmail()) || + !empty($quoteAddress->getTelephone()) + ) { + return [ + 'address' => [ + 'addressLines' => $quoteAddress->getStreet(), + 'city' => $quoteAddress->getCity(), + 'countryCode' => $quoteAddress->getCountryId(), + 'postcode' => $quoteAddress->getPostcode(), + 'state' => $quoteAddress->getRegion() + ], + 'contact' => [ + 'givenName' => $quoteAddress->getFirstname(), + 'surname' => $quoteAddress->getLastname(), + 'email' => $quoteAddress->getEmail(), + 'phoneNumber' => $quoteAddress->getTelephone() + ] + ]; + } + + return null; + } + + /** + * @param Quote $quote + * @return void + */ + private function mapShippingAddress(Quote $quote): ?array + { + if ($quote->isVirtual()) { + return null; + } + $quoteShippingAddress = $quote->getShippingAddress(); + return $this->mapAddress($quoteShippingAddress); + } +} diff --git a/Service/Shipping/ShippingMethodService.php b/Service/Shipping/ShippingMethodService.php new file mode 100644 index 00000000..0645c069 --- /dev/null +++ b/Service/Shipping/ShippingMethodService.php @@ -0,0 +1,148 @@ +shipmentEstimation = $shipmentEstimation; + $this->quoteRepository = $quoteRepository; + $this->shippingInformationFactory = $shippingInformationFactory; + $this->shippingInformationManagement = $shippingInformationManagement; + } + + /** + * @param Quote $quote + * @param string|null $methodId + * @return Quote + * @throws InputException + */ + public function updateShippingMethod( + Quote $quote, + ?string $methodId + ): Quote { + $shippingAddress = $quote->getShippingAddress(); + $quote = $this->setShippingMethodInQuote($quote, $methodId, $shippingAddress)["quote"]; + + $quote->setTotalsCollectedFlag(false); + $quote->collectTotals(); + + $this->quoteRepository->save($quote); + + return $quote; + } + + /** + * @param Quote $quote + * @param Address $shippingAddress + * @return array returns a quote and availableShippingMethods + */ + public function setFirstShippingMethodInQuote( + Quote $quote, + Quote\Address $shippingAddress + ): array { + return $this->setShippingMethodInQuote($quote, null, $shippingAddress); + } + + /** + * @param Quote $quote + * @param string|null $methodId + * @param Address $shippingAddress + * @return array returns a quote and availableShippingMethods + */ + public function setShippingMethodInQuote( + Quote $quote, + ?string $methodId, + Quote\Address $shippingAddress + ): array { + $availableMethods = $this->getAvailableShippingMethods($quote); + if (empty($availableMethods)) { + $shippingAddress->setShippingMethod(''); + return ["quote" => $quote, "availableShippingMethods" => $availableMethods]; + } + if (empty($methodId)) { + $methodId = $availableMethods[0]->getId(); + } + + $isMethodAvailable = count(array_filter($availableMethods, function ($method) use ($methodId) { + return $method->getId() === $methodId; + })) > 0; + $carrierCodeToMethodCode = explode('_', $methodId); + + if (!$isMethodAvailable || count($carrierCodeToMethodCode) !== 2) { + $shippingAddress->setShippingMethod(''); + return ["quote" => $quote, "availableShippingMethods" => $availableMethods]; + } + + $shippingAddress->setShippingMethod($methodId)->setCollectShippingRates(true)->collectShippingRates(); + + $this->shippingInformationManagement->saveAddressInformation( + $quote->getId(), + $this->shippingInformationFactory->create() + ->setShippingAddress($shippingAddress) + ->setShippingCarrierCode($carrierCodeToMethodCode[0]) + ->setShippingMethodCode($carrierCodeToMethodCode[1]) + ); + + return ["quote" => $quote, "availableShippingMethods" => $availableMethods]; + } + + /** + * @param Quote $quote + * @return ShippingMethod[] $shippingMethods + */ + public function getAvailableShippingMethods(Quote $quote): array + { + $shippingMethods = $this->shipmentEstimation->estimateByExtendedAddress( + $quote->getId(), + $quote->getShippingAddress() + ); + if (empty($shippingMethods)) { + return []; + } + $returnedShippingMethods = []; + foreach ($shippingMethods as $shippingMethod) { + if ($shippingMethod->getErrorMessage()) { + continue; + } + + $returnedShippingMethods[] = new ShippingMethod($shippingMethod, $quote->getQuoteCurrencyCode()); + } + return $returnedShippingMethods; + } +}