File: /var/www/vhost/disk-apps/magento.bikenow.co/vendor/magento/module-paypal/Model/Express/Checkout.php
<?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 {
// phpcs:ignore Magento2.Exceptions.DirectThrow
throw new \Exception('Config instance is required.');
}
if (isset($params['quote']) && $params['quote'] instanceof \Magento\Quote\Model\Quote) {
$this->_quote = $params['quote'];
} else {
// phpcs:ignore Magento2.Exceptions.DirectThrow
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, suffix
$shippingAddress->setPrefix(null);
$shippingAddress->setMiddlename(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, [$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 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)
{
$address->collectShippingRates();
$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;
}
}