1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- declare(strict_types=1);
- namespace Magento\Paypal\Model\Express;
- use Magento\Customer\Api\Data\CustomerInterface as CustomerDataObject;
- use Magento\Customer\Model\AccountManagement;
- use Magento\Framework\DataObject;
- use Magento\Paypal\Model\Cart as PaypalCart;
- use Magento\Paypal\Model\Config as PaypalConfig;
- use Magento\Quote\Model\Quote\Address;
- use Magento\Sales\Model\Order\Email\Sender\OrderSender;
- /**
- * Wrapper that performs Paypal Express and Checkout communication
- *
- * @SuppressWarnings(PHPMD.TooManyFields)
- * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
- * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
- */
- class Checkout
- {
- /**
- * Cache ID prefix for "pal" lookup
- *
- * @var string
- */
- const PAL_CACHE_ID = 'paypal_express_checkout_pal';
- /**
- * Keys for passthrough variables in sales/quote_payment and sales/order_payment
- * Uses additional_information as storage
- */
- const PAYMENT_INFO_TRANSPORT_TOKEN = 'paypal_express_checkout_token';
- const PAYMENT_INFO_TRANSPORT_SHIPPING_OVERRIDDEN = 'paypal_express_checkout_shipping_overridden';
- const PAYMENT_INFO_TRANSPORT_SHIPPING_METHOD = 'paypal_express_checkout_shipping_method';
- const PAYMENT_INFO_TRANSPORT_PAYER_ID = 'paypal_express_checkout_payer_id';
- const PAYMENT_INFO_TRANSPORT_REDIRECT = 'paypal_express_checkout_redirect_required';
- const PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT = 'paypal_ec_create_ba';
- /**
- * Flag which says that was used PayPal Express Checkout button for checkout
- * Uses additional_information as storage
- * @var string
- */
- const PAYMENT_INFO_BUTTON = 'button';
- /**
- * @var \Magento\Quote\Model\Quote
- */
- protected $_quote;
- /**
- * Config instance
- *
- * @var PaypalConfig
- */
- protected $_config;
- /**
- * API instance
- *
- * @var \Magento\Paypal\Model\Api\Nvp
- */
- protected $_api;
- /**
- * Api Model Type
- *
- * @var string
- */
- protected $_apiType = \Magento\Paypal\Model\Api\Nvp::class;
- /**
- * Payment method type
- *
- * @var string
- */
- protected $_methodType = PaypalConfig::METHOD_WPP_EXPRESS;
- /**
- * State helper variable
- *
- * @var string
- */
- protected $_redirectUrl = '';
- /**
- * State helper variable
- *
- * @var string
- */
- protected $_pendingPaymentMessage = '';
- /**
- * State helper variable
- *
- * @var string
- */
- protected $_checkoutRedirectUrl = '';
- /**
- * @var \Magento\Customer\Model\Session
- */
- protected $_customerSession;
- /**
- * Redirect urls supposed to be set to support giropay
- *
- * @var array
- */
- protected $_giropayUrls = [];
- /**
- * Create Billing Agreement flag
- *
- * @var bool
- */
- protected $_isBARequested = false;
- /**
- * Flag for Bill Me Later mode
- *
- * @var bool
- */
- protected $_isBml = false;
- /**
- * Customer ID
- *
- * @var int
- */
- protected $_customerId;
- /**
- * Billing agreement that might be created during order placing
- *
- * @var \Magento\Paypal\Model\Billing\Agreement
- */
- protected $_billingAgreement;
- /**
- * Order
- *
- * @var \Magento\Sales\Model\Order
- */
- protected $_order;
- /**
- * @var \Magento\Framework\App\Cache\Type\Config
- */
- protected $_configCacheType;
- /**
- * Checkout data
- *
- * @var \Magento\Checkout\Helper\Data
- */
- protected $_checkoutData;
- /**
- * Tax data
- *
- * @var \Magento\Tax\Helper\Data
- */
- protected $_taxData;
- /**
- * Customer data
- *
- * @var \Magento\Customer\Model\Url
- */
- protected $_customerUrl;
- /**
- * @var \Psr\Log\LoggerInterface
- */
- protected $_logger;
- /**
- * @var \Magento\Framework\Locale\ResolverInterface
- */
- protected $_localeResolver;
- /**
- * @var \Magento\Paypal\Model\Info
- */
- protected $_paypalInfo;
- /**
- * @var \Magento\Store\Model\StoreManagerInterface
- */
- protected $_storeManager;
- /**
- * @var \Magento\Framework\UrlInterface
- */
- protected $_coreUrl;
- /**
- * @var \Magento\Paypal\Model\CartFactory
- */
- protected $_cartFactory;
- /**
- * @var \Magento\Checkout\Model\Type\OnepageFactory
- */
- protected $_checkoutOnepageFactory;
- /**
- * @var \Magento\Paypal\Model\Billing\AgreementFactory
- */
- protected $_agreementFactory;
- /**
- * @var \Magento\Paypal\Model\Api\Type\Factory
- */
- protected $_apiTypeFactory;
- /**
- * @var \Magento\Framework\DataObject\Copy
- */
- protected $_objectCopyService;
- /**
- * @var \Magento\Checkout\Model\Session
- */
- protected $_checkoutSession;
- /**
- * @var \Magento\Customer\Api\CustomerRepositoryInterface
- */
- protected $_customerRepository;
- /**
- * @var \Magento\Customer\Model\AccountManagement
- */
- protected $_accountManagement;
- /**
- * @var \Magento\Framework\Encryption\EncryptorInterface
- */
- protected $_encryptor;
- /**
- * @var \Magento\Framework\Message\ManagerInterface
- */
- protected $_messageManager;
- /**
- * @var OrderSender
- */
- protected $orderSender;
- /**
- * @var \Magento\Quote\Api\CartRepositoryInterface
- */
- protected $quoteRepository;
- /**
- * @var \Magento\Quote\Api\CartManagementInterface
- */
- protected $quoteManagement;
- /**
- * @var \Magento\Quote\Model\Quote\TotalsCollector
- */
- protected $totalsCollector;
- /**
- * @param \Psr\Log\LoggerInterface $logger
- * @param \Magento\Customer\Model\Url $customerUrl
- * @param \Magento\Tax\Helper\Data $taxData
- * @param \Magento\Checkout\Helper\Data $checkoutData
- * @param \Magento\Customer\Model\Session $customerSession
- * @param \Magento\Framework\App\Cache\Type\Config $configCacheType
- * @param \Magento\Framework\Locale\ResolverInterface $localeResolver
- * @param \Magento\Paypal\Model\Info $paypalInfo
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Framework\UrlInterface $coreUrl
- * @param \Magento\Paypal\Model\CartFactory $cartFactory
- * @param \Magento\Checkout\Model\Type\OnepageFactory $onepageFactory
- * @param \Magento\Quote\Api\CartManagementInterface $quoteManagement
- * @param \Magento\Paypal\Model\Billing\AgreementFactory $agreementFactory
- * @param \Magento\Paypal\Model\Api\Type\Factory $apiTypeFactory
- * @param DataObject\Copy $objectCopyService
- * @param \Magento\Checkout\Model\Session $checkoutSession
- * @param \Magento\Framework\Encryption\EncryptorInterface $encryptor
- * @param \Magento\Framework\Message\ManagerInterface $messageManager
- * @param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository
- * @param AccountManagement $accountManagement
- * @param OrderSender $orderSender
- * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository
- * @param \Magento\Quote\Model\Quote\TotalsCollector $totalsCollector
- * @param array $params
- * @throws \Exception
- * @SuppressWarnings(PHPMD.ExcessiveParameterList)
- */
- public function __construct(
- \Psr\Log\LoggerInterface $logger,
- \Magento\Customer\Model\Url $customerUrl,
- \Magento\Tax\Helper\Data $taxData,
- \Magento\Checkout\Helper\Data $checkoutData,
- \Magento\Customer\Model\Session $customerSession,
- \Magento\Framework\App\Cache\Type\Config $configCacheType,
- \Magento\Framework\Locale\ResolverInterface $localeResolver,
- \Magento\Paypal\Model\Info $paypalInfo,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Framework\UrlInterface $coreUrl,
- \Magento\Paypal\Model\CartFactory $cartFactory,
- \Magento\Checkout\Model\Type\OnepageFactory $onepageFactory,
- \Magento\Quote\Api\CartManagementInterface $quoteManagement,
- \Magento\Paypal\Model\Billing\AgreementFactory $agreementFactory,
- \Magento\Paypal\Model\Api\Type\Factory $apiTypeFactory,
- \Magento\Framework\DataObject\Copy $objectCopyService,
- \Magento\Checkout\Model\Session $checkoutSession,
- \Magento\Framework\Encryption\EncryptorInterface $encryptor,
- \Magento\Framework\Message\ManagerInterface $messageManager,
- \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository,
- AccountManagement $accountManagement,
- OrderSender $orderSender,
- \Magento\Quote\Api\CartRepositoryInterface $quoteRepository,
- \Magento\Quote\Model\Quote\TotalsCollector $totalsCollector,
- $params = []
- ) {
- $this->quoteManagement = $quoteManagement;
- $this->_customerUrl = $customerUrl;
- $this->_taxData = $taxData;
- $this->_checkoutData = $checkoutData;
- $this->_configCacheType = $configCacheType;
- $this->_logger = $logger;
- $this->_localeResolver = $localeResolver;
- $this->_paypalInfo = $paypalInfo;
- $this->_storeManager = $storeManager;
- $this->_coreUrl = $coreUrl;
- $this->_cartFactory = $cartFactory;
- $this->_checkoutOnepageFactory = $onepageFactory;
- $this->_agreementFactory = $agreementFactory;
- $this->_apiTypeFactory = $apiTypeFactory;
- $this->_objectCopyService = $objectCopyService;
- $this->_checkoutSession = $checkoutSession;
- $this->_customerRepository = $customerRepository;
- $this->_encryptor = $encryptor;
- $this->_messageManager = $messageManager;
- $this->orderSender = $orderSender;
- $this->_accountManagement = $accountManagement;
- $this->quoteRepository = $quoteRepository;
- $this->totalsCollector = $totalsCollector;
- $this->_customerSession = isset($params['session'])
- && $params['session'] instanceof \Magento\Customer\Model\Session ? $params['session'] : $customerSession;
- if (isset($params['config']) && $params['config'] instanceof PaypalConfig) {
- $this->_config = $params['config'];
- } else {
- throw new \Exception('Config instance is required.');
- }
- if (isset($params['quote']) && $params['quote'] instanceof \Magento\Quote\Model\Quote) {
- $this->_quote = $params['quote'];
- } else {
- throw new \Exception('Quote instance is required.');
- }
- }
- /**
- * Checkout with PayPal image URL getter
- *
- * Spares API calls of getting "pal" variable, by putting it into cache per store view
- *
- * @return string
- */
- public function getCheckoutShortcutImageUrl()
- {
- // get "pal" thing from cache or lookup it via API
- $pal = null;
- if ($this->_config->areButtonsDynamic()) {
- $cacheId = self::PAL_CACHE_ID . $this->_storeManager->getStore()->getId();
- $pal = $this->_configCacheType->load($cacheId);
- if (self::PAL_CACHE_ID == $pal) {
- $pal = null;
- } elseif (!$pal) {
- $pal = null;
- try {
- $this->_getApi()->callGetPalDetails();
- $pal = $this->_getApi()->getPal();
- $this->_configCacheType->save($pal, $cacheId);
- } catch (\Exception $e) {
- $this->_configCacheType->save(self::PAL_CACHE_ID, $cacheId);
- $this->_logger->critical($e);
- }
- }
- }
- return $this->_config->getExpressCheckoutShortcutImageUrl(
- $this->_localeResolver->getLocale(),
- $this->_quote->getBaseGrandTotal(),
- $pal
- );
- }
- /**
- * Setter that enables giropay redirects flow
- *
- * @param string $successUrl - payment success result
- * @param string $cancelUrl - payment cancellation result
- * @param string $pendingUrl - pending payment result
- * @return $this
- */
- public function prepareGiropayUrls($successUrl, $cancelUrl, $pendingUrl)
- {
- $this->_giropayUrls = [$successUrl, $cancelUrl, $pendingUrl];
- return $this;
- }
- /**
- * Set create billing agreement flag
- *
- * @param bool $flag
- * @return $this
- */
- public function setIsBillingAgreementRequested($flag)
- {
- $this->_isBARequested = $flag;
- return $this;
- }
- /**
- * Set flag that forces to use BillMeLater
- *
- * @param bool $isBml
- * @return $this
- */
- public function setIsBml($isBml)
- {
- $this->_isBml = $isBml;
- return $this;
- }
- /**
- * Setter for customer
- *
- * @param CustomerDataObject $customerData
- * @return $this
- */
- public function setCustomerData(CustomerDataObject $customerData)
- {
- $this->_quote->assignCustomer($customerData);
- $this->_customerId = $customerData->getId();
- return $this;
- }
- /**
- * Setter for customer with billing and shipping address changing ability
- *
- * @param CustomerDataObject $customerData
- * @param Address|null $billingAddress
- * @param Address|null $shippingAddress
- * @return $this
- */
- public function setCustomerWithAddressChange(
- CustomerDataObject $customerData,
- $billingAddress = null,
- $shippingAddress = null
- ) {
- $this->_quote->assignCustomerWithAddressChange($customerData, $billingAddress, $shippingAddress);
- $this->_customerId = $customerData->getId();
- return $this;
- }
- /**
- * Reserve order ID for specified quote and start checkout on PayPal
- *
- * @param string $returnUrl
- * @param string $cancelUrl
- * @param bool|null $button
- * @return string
- * @throws \Magento\Framework\Exception\LocalizedException
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- */
- public function start($returnUrl, $cancelUrl, $button = null)
- {
- $this->_quote->collectTotals();
- if (!$this->_quote->getGrandTotal()) {
- throw new \Magento\Framework\Exception\LocalizedException(
- __(
- 'PayPal can\'t process orders with a zero balance due. '
- . 'To finish your purchase, please go through the standard checkout process.'
- )
- );
- }
- $this->_quote->reserveOrderId();
- $this->quoteRepository->save($this->_quote);
- // prepare API
- $solutionType = $this->_config->getMerchantCountry() == 'DE'
- ? \Magento\Paypal\Model\Config::EC_SOLUTION_TYPE_MARK
- : $this->_config->getValue('solutionType');
- $totalAmount = round($this->_quote->getBaseGrandTotal(), 2);
- $this->_getApi()->setAmount($totalAmount)
- ->setCurrencyCode($this->_quote->getBaseCurrencyCode())
- ->setInvNum($this->_quote->getReservedOrderId())
- ->setReturnUrl($returnUrl)
- ->setCancelUrl($cancelUrl)
- ->setSolutionType($solutionType)
- ->setPaymentAction($this->_config->getValue('paymentAction'));
- if ($this->_giropayUrls) {
- list($successUrl, $cancelUrl, $pendingUrl) = $this->_giropayUrls;
- $this->_getApi()->addData(
- [
- 'giropay_cancel_url' => $cancelUrl,
- 'giropay_success_url' => $successUrl,
- 'giropay_bank_txn_pending_url' => $pendingUrl,
- ]
- );
- }
- if ($this->_isBml) {
- $this->_getApi()->setFundingSource('BML');
- }
- $this->_setBillingAgreementRequest();
- if ($this->_config->getValue('requireBillingAddress') == PaypalConfig::REQUIRE_BILLING_ADDRESS_ALL) {
- $this->_getApi()->setRequireBillingAddress(1);
- }
- // suppress or export shipping address
- $address = null;
- if ($this->_quote->getIsVirtual()) {
- if ($this->_config->getValue('requireBillingAddress')
- == PaypalConfig::REQUIRE_BILLING_ADDRESS_VIRTUAL
- ) {
- $this->_getApi()->setRequireBillingAddress(1);
- }
- $this->_getApi()->setSuppressShipping(true);
- } else {
- $this->_getApi()->setBillingAddress($this->_quote->getBillingAddress());
- $address = $this->_quote->getShippingAddress();
- $isOverridden = 0;
- if (true === $address->validate()) {
- $isOverridden = 1;
- $this->_getApi()->setAddress($address);
- }
- $this->_quote->getPayment()->setAdditionalInformation(
- self::PAYMENT_INFO_TRANSPORT_SHIPPING_OVERRIDDEN,
- $isOverridden
- );
- $this->_quote->getPayment()->save();
- }
- /** @var $cart \Magento\Payment\Model\Cart */
- $cart = $this->_cartFactory->create(['salesModel' => $this->_quote]);
- $this->_getApi()->setPaypalCart($cart);
- if (!$this->_taxData->getConfig()->priceIncludesTax()) {
- $this->setShippingOptions($cart, $address);
- }
- $this->_config->exportExpressCheckoutStyleSettings($this->_getApi());
- /* Temporary solution. @TODO: do not pass quote into Nvp model */
- $this->_getApi()->setQuote($this->_quote);
- $this->_getApi()->callSetExpressCheckout();
- $token = $this->_getApi()->getToken();
- $this->_setRedirectUrl($button, $token);
- $payment = $this->_quote->getPayment();
- $payment->unsAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT);
- // Set flag that we came from Express Checkout button
- if (!empty($button)) {
- $payment->setAdditionalInformation(self::PAYMENT_INFO_BUTTON, 1);
- } elseif ($payment->hasAdditionalInformation(self::PAYMENT_INFO_BUTTON)) {
- $payment->unsAdditionalInformation(self::PAYMENT_INFO_BUTTON);
- }
- $payment->save();
- return $token;
- }
- /**
- * Check whether system can skip order review page before placing order
- *
- * @return bool
- */
- public function canSkipOrderReviewStep()
- {
- $isOnepageCheckout = !$this->_quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_BUTTON);
- return $this->_config->isOrderReviewStepDisabled() && $isOnepageCheckout;
- }
- /**
- * Update quote when returned from PayPal
- *
- * Rewrite billing address by paypal, save old billing address for new customer, and
- * export shipping address in case address absence
- *
- * @param string $token
- * @param string|null $payerIdentifier
- * @return void
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- */
- public function returnFromPaypal($token, string $payerIdentifier = null)
- {
- $this->_getApi()
- ->setToken($token)
- ->callGetExpressCheckoutDetails();
- $quote = $this->_quote;
- $this->ignoreAddressValidation();
- // check if we came from the Express Checkout button
- $isButton = (bool)$quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_BUTTON);
- // import shipping address
- $exportedShippingAddress = $this->_getApi()->getExportedShippingAddress();
- if (!$quote->getIsVirtual()) {
- $shippingAddress = $quote->getShippingAddress();
- if ($shippingAddress) {
- if ($exportedShippingAddress && $isButton) {
- $this->_setExportedAddressData($shippingAddress, $exportedShippingAddress);
- // PayPal doesn't provide detailed shipping info: prefix, middlename, lastname, suffix
- $shippingAddress->setPrefix(null);
- $shippingAddress->setMiddlename(null);
- $shippingAddress->setLastname(null);
- $shippingAddress->setSuffix(null);
- $shippingAddress->setCollectShippingRates(true);
- $shippingAddress->setSameAsBilling(0);
- }
- // import shipping method
- $code = '';
- if ($this->_getApi()->getShippingRateCode()) {
- $code = $this->_matchShippingMethodCode($shippingAddress, $this->_getApi()->getShippingRateCode());
- if ($code) {
- // possible bug of double collecting rates :-/
- $shippingAddress->setShippingMethod($code)->setCollectShippingRates(true);
- }
- }
- $quote->getPayment()->setAdditionalInformation(
- self::PAYMENT_INFO_TRANSPORT_SHIPPING_METHOD,
- $code
- );
- }
- }
- // import billing address
- $requireBillingAddress = (int)$this->_config->getValue(
- 'requireBillingAddress'
- ) === \Magento\Paypal\Model\Config::REQUIRE_BILLING_ADDRESS_ALL;
- if ($isButton && !$requireBillingAddress && !$quote->isVirtual()) {
- $billingAddress = clone $shippingAddress;
- $billingAddress->unsAddressId()->unsAddressType()->setCustomerAddressId(null);
- $data = $billingAddress->getData();
- $data['save_in_address_book'] = 0;
- $quote->getBillingAddress()->addData($data);
- $quote->getShippingAddress()->setSameAsBilling(1);
- } else {
- $billingAddress = $quote->getBillingAddress()->setCustomerAddressId(null);
- }
- $exportedBillingAddress = $this->_getApi()->getExportedBillingAddress();
- // Since country is required field for billing and shipping address,
- // we consider the address information to be empty if country is empty.
- $isEmptyAddress = ($billingAddress->getCountryId() === null);
- if ($requireBillingAddress || $isEmptyAddress) {
- $this->_setExportedAddressData($billingAddress, $exportedBillingAddress);
- }
- $billingAddress->setCustomerNote($exportedBillingAddress->getData('note'));
- $quote->setBillingAddress($billingAddress);
- $quote->setCheckoutMethod($this->getCheckoutMethod());
- // import payment info
- $payment = $quote->getPayment();
- $payment->setMethod($this->_methodType);
- $this->_paypalInfo->importToPayment($this->_getApi(), $payment);
- $payerId = $payerIdentifier ? : $this->_getApi()->getPayerId();
- $payment->setAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_PAYER_ID, $payerId)
- ->setAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_TOKEN, $token);
- $quote->collectTotals();
- $this->quoteRepository->save($quote);
- }
- /**
- * Check whether order review has enough data to initialize
- *
- * @param string|null $token
- * @return void
- * @throws \Magento\Framework\Exception\LocalizedException
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
- */
- public function prepareOrderReview($token = null)
- {
- $payment = $this->_quote->getPayment();
- if (!$payment || !$payment->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_PAYER_ID)) {
- throw new \Magento\Framework\Exception\LocalizedException(__('A payer is not identified.'));
- }
- $this->_quote->setMayEditShippingAddress(
- 1 != $this->_quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_SHIPPING_OVERRIDDEN)
- );
- $this->_quote->setMayEditShippingMethod(
- '' == $this->_quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_SHIPPING_METHOD)
- );
- $this->ignoreAddressValidation();
- $this->_quote->collectTotals();
- $this->quoteRepository->save($this->_quote);
- }
- /**
- * Return callback response with shipping options
- *
- * @param array $request
- * @return string
- * @throws \Exception
- */
- public function getShippingOptionsCallbackResponse(array $request)
- {
- $debugData = ['request' => $request, 'response' => []];
- try {
- // obtain addresses
- $address = $this->_getApi()->prepareShippingOptionsCallbackAddress($request);
- $quoteAddress = $this->_quote->getShippingAddress();
- // compare addresses, calculate shipping rates and prepare response
- $options = [];
- if ($address && $quoteAddress && !$this->_quote->getIsVirtual()) {
- foreach ($address->getExportedKeys() as $key) {
- $quoteAddress->setDataUsingMethod($key, $address->getData($key));
- }
- $quoteAddress->setCollectShippingRates(true);
- $this->totalsCollector->collectAddressTotals($this->_quote, $quoteAddress);
- $options = $this->_prepareShippingOptions($quoteAddress, false, true);
- }
- $response = $this->_getApi()->setShippingOptions($options)->formatShippingOptionsCallback();
- // log request and response
- $debugData['response'] = $response;
- $this->_logger->debug(var_export($debugData, true));
- return $response;
- } catch (\Exception $e) {
- $this->_logger->debug(var_export($debugData, true));
- throw $e;
- }
- }
- /**
- * Set shipping method to quote, if needed
- *
- * @param string $methodCode
- * @return void
- */
- public function updateShippingMethod($methodCode)
- {
- $shippingAddress = $this->_quote->getShippingAddress();
- if (!$this->_quote->getIsVirtual() && $shippingAddress) {
- if ($methodCode != $shippingAddress->getShippingMethod()) {
- $this->ignoreAddressValidation();
- $shippingAddress->setShippingMethod($methodCode)->setCollectShippingRates(true);
- $cartExtension = $this->_quote->getExtensionAttributes();
- if ($cartExtension && $cartExtension->getShippingAssignments()) {
- $cartExtension->getShippingAssignments()[0]
- ->getShipping()
- ->setMethod($methodCode);
- }
- $this->_quote->collectTotals();
- $this->quoteRepository->save($this->_quote);
- }
- }
- }
- /**
- * Place the order when customer returned from PayPal until this moment all quote data must be valid.
- *
- * @param string $token
- * @param string|null $shippingMethodCode
- * @return void
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- */
- public function place($token, $shippingMethodCode = null)
- {
- if ($shippingMethodCode) {
- $this->updateShippingMethod($shippingMethodCode);
- }
- if ($this->getCheckoutMethod() == \Magento\Checkout\Model\Type\Onepage::METHOD_GUEST) {
- $this->prepareGuestQuote();
- }
- $this->ignoreAddressValidation();
- $this->_quote->collectTotals();
- $order = $this->quoteManagement->submit($this->_quote);
- if (!$order) {
- return;
- }
- // commence redirecting to finish payment, if paypal requires it
- if ($order->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_REDIRECT)) {
- $this->_redirectUrl = $this->_config->getExpressCheckoutCompleteUrl($token);
- }
- switch ($order->getState()) {
- // even after placement paypal can disallow to authorize/capture, but will wait until bank transfers money
- case \Magento\Sales\Model\Order::STATE_PENDING_PAYMENT:
- // TODO
- break;
- // regular placement, when everything is ok
- case \Magento\Sales\Model\Order::STATE_PROCESSING:
- case \Magento\Sales\Model\Order::STATE_COMPLETE:
- case \Magento\Sales\Model\Order::STATE_PAYMENT_REVIEW:
- try {
- if (!$order->getEmailSent()) {
- $this->orderSender->send($order);
- }
- } catch (\Exception $e) {
- $this->_logger->critical($e);
- }
- $this->_checkoutSession->start();
- break;
- default:
- break;
- }
- $this->_order = $order;
- }
- /**
- * Make sure addresses will be saved without validation errors
- *
- * @return void
- */
- private function ignoreAddressValidation()
- {
- $this->_quote->getBillingAddress()->setShouldIgnoreValidation(true);
- if (!$this->_quote->getIsVirtual()) {
- $this->_quote->getShippingAddress()->setShouldIgnoreValidation(true);
- if (!$this->_config->getValue('requireBillingAddress')
- && !$this->_quote->getBillingAddress()->getEmail()
- ) {
- $this->_quote->getBillingAddress()->setSameAsBilling(1);
- }
- }
- }
- /**
- * Determine whether redirect somewhere specifically is required
- *
- * @return string
- */
- public function getRedirectUrl()
- {
- return $this->_redirectUrl;
- }
- /**
- * Get created billing agreement
- *
- * @return \Magento\Paypal\Model\Billing\Agreement|null
- */
- public function getBillingAgreement()
- {
- return $this->_billingAgreement;
- }
- /**
- * Return order
- *
- * @return \Magento\Sales\Model\Order
- */
- public function getOrder()
- {
- return $this->_order;
- }
- /**
- * Get checkout method
- *
- * @return string
- */
- public function getCheckoutMethod()
- {
- if ($this->getCustomerSession()->isLoggedIn()) {
- return \Magento\Checkout\Model\Type\Onepage::METHOD_CUSTOMER;
- }
- if (!$this->_quote->getCheckoutMethod()) {
- if ($this->_checkoutData->isAllowedGuestCheckout($this->_quote)) {
- $this->_quote->setCheckoutMethod(\Magento\Checkout\Model\Type\Onepage::METHOD_GUEST);
- } else {
- $this->_quote->setCheckoutMethod(\Magento\Checkout\Model\Type\Onepage::METHOD_REGISTER);
- }
- }
- return $this->_quote->getCheckoutMethod();
- }
- /**
- * Sets address data from exported address
- *
- * @param Address $address
- * @param array $exportedAddress
- * @return void
- */
- protected function _setExportedAddressData($address, $exportedAddress)
- {
- foreach ($exportedAddress->getExportedKeys() as $key) {
- $data = $exportedAddress->getData($key);
- if (!empty($data)) {
- $address->setDataUsingMethod($key, $data);
- }
- }
- }
- /**
- * Set create billing agreement flag to api call
- *
- * @return $this
- */
- protected function _setBillingAgreementRequest()
- {
- if (!$this->_customerId) {
- return $this;
- }
- $isRequested = $this->_isBARequested || $this->_quote->getPayment()
- ->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT);
- if (!($this->_config->getValue('allow_ba_signup') == PaypalConfig::EC_BA_SIGNUP_AUTO
- || $isRequested && $this->_config->shouldAskToCreateBillingAgreement())
- ) {
- return $this;
- }
- if (!$this->_agreementFactory->create()->needToCreateForCustomer($this->_customerId)) {
- return $this;
- }
- $this->_getApi()->setBillingType($this->_getApi()->getBillingAgreementType());
- return $this;
- }
- /**
- * Get api
- *
- * @return \Magento\Paypal\Model\Api\Nvp
- */
- protected function _getApi()
- {
- if (null === $this->_api) {
- $this->_api = $this->_apiTypeFactory->create($this->_apiType)->setConfigObject($this->_config);
- }
- return $this->_api;
- }
- /**
- * Attempt to collect address shipping rates and return them for further usage in instant update API
- *
- * Returns empty array if it was impossible to obtain any shipping rate and
- * if there are shipping rates obtained, the method must return one of them as default.
- *
- * @param Address $address
- * @param bool $mayReturnEmpty
- * @param bool $calculateTax
- * @return array|false
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- */
- protected function _prepareShippingOptions(Address $address, $mayReturnEmpty = false, $calculateTax = false)
- {
- $options = [];
- $i = 0;
- $iMin = false;
- $min = false;
- $userSelectedOption = null;
- foreach ($address->getGroupedAllShippingRates() as $group) {
- foreach ($group as $rate) {
- $amount = (double)$rate->getPrice();
- if ($rate->getErrorMessage()) {
- continue;
- }
- $isDefault = $address->getShippingMethod() === $rate->getCode();
- $amountExclTax = $this->_taxData->getShippingPrice($amount, false, $address);
- $amountInclTax = $this->_taxData->getShippingPrice($amount, true, $address);
- $options[$i] = new \Magento\Framework\DataObject(
- [
- 'is_default' => $isDefault,
- 'name' => trim("{$rate->getCarrierTitle()} - {$rate->getMethodTitle()}", ' -'),
- 'code' => $rate->getCode(),
- 'amount' => $amountExclTax,
- ]
- );
- if ($calculateTax) {
- $options[$i]->setTaxAmount(
- $amountInclTax - $amountExclTax + $address->getTaxAmount() - $address->getShippingTaxAmount()
- );
- }
- if ($isDefault) {
- $userSelectedOption = $options[$i];
- }
- if (false === $min || $amountInclTax < $min) {
- $min = $amountInclTax;
- $iMin = $i;
- }
- $i++;
- }
- }
- if ($mayReturnEmpty && $userSelectedOption === null) {
- $options[] = new \Magento\Framework\DataObject(
- [
- 'is_default' => true,
- 'name' => __('N/A'),
- 'code' => 'no_rate',
- 'amount' => 0.00,
- ]
- );
- if ($calculateTax) {
- $options[$i]->setTaxAmount($address->getTaxAmount());
- }
- } elseif ($userSelectedOption === null && isset($options[$iMin])) {
- $options[$iMin]->setIsDefault(true);
- }
- // Magento will transfer only first 10 cheapest shipping options if there are more than 10 available.
- if (count($options) > 10) {
- usort($options, [get_class($this), 'cmpShippingOptions']);
- array_splice($options, 10);
- // User selected option will be always included in options list
- if ($userSelectedOption !== null && !in_array($userSelectedOption, $options)) {
- $options[9] = $userSelectedOption;
- }
- }
- return $options;
- }
- /**
- * Compare two shipping options based on their amounts
- *
- * This function is used as a callback comparison function in shipping options sorting process
- *
- * @see self::_prepareShippingOptions()
- * @param \Magento\Framework\DataObject $option1
- * @param \Magento\Framework\DataObject $option2
- * @return int
- */
- protected static function cmpShippingOptions(DataObject $option1, DataObject $option2)
- {
- return $option1->getAmount() <=> $option2->getAmount();
- }
- /**
- * Try to find whether the code provided by PayPal corresponds to any of possible shipping rates
- *
- * This method was created only because PayPal has issues with returning the selected code.
- * If in future the issue is fixed, we don't need to attempt to match it. It would be enough to set the method code
- * before collecting shipping rates
- *
- * @param Address $address
- * @param string $selectedCode
- * @return string
- */
- protected function _matchShippingMethodCode(Address $address, $selectedCode)
- {
- $options = $this->_prepareShippingOptions($address, false);
- foreach ($options as $option) {
- if ($selectedCode === $option['code'] // the proper case as outlined in documentation
- || $selectedCode === $option['name'] // workaround: PayPal may return name instead of the code
- // workaround: PayPal may concatenate code and name, and return it instead of the code:
- || $selectedCode === "{$option['code']} {$option['name']}"
- ) {
- return $option['code'];
- }
- }
- return '';
- }
- /**
- * Create payment redirect url
- *
- * @param bool|null $button
- * @param string $token
- * @return void
- */
- protected function _setRedirectUrl($button, $token)
- {
- $this->_redirectUrl = ($button && !$this->_taxData->getConfig()->priceIncludesTax())
- ? $this->_config->getExpressCheckoutStartUrl($token)
- : $this->_config->getPayPalBasicStartUrl($token);
- }
- /**
- * Get customer session object
- *
- * @return \Magento\Customer\Model\Session
- */
- public function getCustomerSession()
- {
- return $this->_customerSession;
- }
- /**
- * Set shipping options to api
- *
- * @param \Magento\Paypal\Model\Cart $cart
- * @param \Magento\Quote\Model\Quote\Address|null $address
- * @return void
- */
- private function setShippingOptions(PaypalCart $cart, Address $address = null)
- {
- // for included tax always disable line items (related to paypal amount rounding problem)
- $this->_getApi()->setIsLineItemsEnabled($this->_config->getValue(PaypalConfig::TRANSFER_CART_LINE_ITEMS));
- // add shipping options if needed and line items are available
- $cartItems = $cart->getAllItems();
- if ($this->_config->getValue(PaypalConfig::TRANSFER_CART_LINE_ITEMS)
- && $this->_config->getValue(PaypalConfig::TRANSFER_SHIPPING_OPTIONS)
- && !empty($cartItems)
- ) {
- if (!$this->_quote->getIsVirtual()) {
- $options = $this->_prepareShippingOptions($address, true);
- if ($options) {
- $this->_getApi()->setShippingOptionsCallbackUrl(
- $this->_coreUrl->getUrl(
- '*/*/shippingOptionsCallback',
- ['quote_id' => $this->_quote->getId()]
- )
- )->setShippingOptions($options);
- }
- }
- }
- }
- /**
- * Prepare quote for guest checkout order submit
- *
- * @return $this
- */
- protected function prepareGuestQuote()
- {
- $quote = $this->_quote;
- $quote->setCustomerId(null)
- ->setCustomerEmail($quote->getBillingAddress()->getEmail())
- ->setCustomerIsGuest(true)
- ->setCustomerGroupId(\Magento\Customer\Model\Group::NOT_LOGGED_IN_ID);
- return $this;
- }
- }
|