PageRenderTime 49ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/magento/app/code/core/Mage/Paypal/Model/Express/Checkout.php

https://bitbucket.org/jit_bec/shopifine
PHP | 996 lines | 597 code | 91 blank | 308 comment | 116 complexity | 460fec8be683d9e5c2e859295ffb2e9f MD5 | raw file
Possible License(s): LGPL-3.0
  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category Mage
  22. * @package Mage_Paypal
  23. * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com)
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. */
  26. /**
  27. * Wrapper that performs Paypal Express and Checkout communication
  28. * Use current Paypal Express method instance
  29. */
  30. class Mage_Paypal_Model_Express_Checkout
  31. {
  32. /**
  33. * Cache ID prefix for "pal" lookup
  34. * @var string
  35. */
  36. const PAL_CACHE_ID = 'paypal_express_checkout_pal';
  37. /**
  38. * Keys for passthrough variables in sales/quote_payment and sales/order_payment
  39. * Uses additional_information as storage
  40. * @var string
  41. */
  42. const PAYMENT_INFO_TRANSPORT_TOKEN = 'paypal_express_checkout_token';
  43. const PAYMENT_INFO_TRANSPORT_SHIPPING_OVERRIDEN = 'paypal_express_checkout_shipping_overriden';
  44. const PAYMENT_INFO_TRANSPORT_SHIPPING_METHOD = 'paypal_express_checkout_shipping_method';
  45. const PAYMENT_INFO_TRANSPORT_PAYER_ID = 'paypal_express_checkout_payer_id';
  46. const PAYMENT_INFO_TRANSPORT_REDIRECT = 'paypal_express_checkout_redirect_required';
  47. const PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT = 'paypal_ec_create_ba';
  48. /**
  49. * @var Mage_Sales_Model_Quote
  50. */
  51. protected $_quote = null;
  52. /**
  53. * Config instance
  54. * @var Mage_Paypal_Model_Config
  55. */
  56. protected $_config = null;
  57. /**
  58. * API instance
  59. * @var Mage_Paypal_Model_Api_Nvp
  60. */
  61. protected $_api = null;
  62. /**
  63. * Api Model Type
  64. *
  65. * @var string
  66. */
  67. protected $_apiType = 'paypal/api_nvp';
  68. /**
  69. * Payment method type
  70. *
  71. * @var unknown_type
  72. */
  73. protected $_methodType = Mage_Paypal_Model_Config::METHOD_WPP_EXPRESS;
  74. /**
  75. * State helper variables
  76. * @var string
  77. */
  78. protected $_redirectUrl = '';
  79. protected $_pendingPaymentMessage = '';
  80. protected $_checkoutRedirectUrl = '';
  81. /**
  82. * @var Mage_Customer_Model_Session
  83. */
  84. protected $_customerSession;
  85. /**
  86. * Redirect urls supposed to be set to support giropay
  87. *
  88. * @var array
  89. */
  90. protected $_giropayUrls = array();
  91. /**
  92. * Create Billing Agreement flag
  93. *
  94. * @var bool
  95. */
  96. protected $_isBARequested = false;
  97. /**
  98. * Customer ID
  99. *
  100. * @var int
  101. */
  102. protected $_customerId = null;
  103. /**
  104. * Recurring payment profiles
  105. *
  106. * @var array
  107. */
  108. protected $_recurringPaymentProfiles = array();
  109. /**
  110. * Billing agreement that might be created during order placing
  111. *
  112. * @var Mage_Sales_Model_Billing_Agreement
  113. */
  114. protected $_billingAgreement = null;
  115. /**
  116. * Order
  117. *
  118. * @var Mage_Sales_Model_QuoteMage_Sales_Model_Quote
  119. */
  120. protected $_order = null;
  121. /**
  122. * Set quote and config instances
  123. * @param array $params
  124. */
  125. public function __construct($params = array())
  126. {
  127. if (isset($params['quote']) && $params['quote'] instanceof Mage_Sales_Model_Quote) {
  128. $this->_quote = $params['quote'];
  129. } else {
  130. throw new Exception('Quote instance is required.');
  131. }
  132. if (isset($params['config']) && $params['config'] instanceof Mage_Paypal_Model_Config) {
  133. $this->_config = $params['config'];
  134. } else {
  135. throw new Exception('Config instance is required.');
  136. }
  137. $this->_customerSession = Mage::getSingleton('customer/session');
  138. }
  139. /**
  140. * Checkout with PayPal image URL getter
  141. * Spares API calls of getting "pal" variable, by putting it into cache per store view
  142. * @return string
  143. */
  144. public function getCheckoutShortcutImageUrl()
  145. {
  146. // get "pal" thing from cache or lookup it via API
  147. $pal = null;
  148. if ($this->_config->areButtonsDynamic()) {
  149. $cacheId = self::PAL_CACHE_ID . Mage::app()->getStore()->getId();
  150. $pal = Mage::app()->loadCache($cacheId);
  151. if (-1 == $pal) {
  152. $pal = null;
  153. } elseif (!$pal) {
  154. $pal = null;
  155. $this->_getApi();
  156. try {
  157. $this->_api->callGetPalDetails();
  158. $pal = $this->_api->getPal();
  159. Mage::app()->saveCache($pal, $cacheId, array(Mage_Core_Model_Config::CACHE_TAG));
  160. } catch (Exception $e) {
  161. Mage::app()->saveCache(-1, $cacheId, array(Mage_Core_Model_Config::CACHE_TAG));
  162. Mage::logException($e);
  163. }
  164. }
  165. }
  166. return $this->_config->getExpressCheckoutShortcutImageUrl(
  167. Mage::app()->getLocale()->getLocaleCode(),
  168. $this->_quote->getBaseGrandTotal(),
  169. $pal
  170. );
  171. }
  172. /**
  173. * Setter that enables giropay redirects flow
  174. *
  175. * @param string $successUrl - payment success result
  176. * @param string $cancelUrl - payment cancellation result
  177. * @param string $pendingUrl - pending payment result
  178. * @return Mage_Paypal_Model_Express_Checkout
  179. */
  180. public function prepareGiropayUrls($successUrl, $cancelUrl, $pendingUrl)
  181. {
  182. $this->_giropayUrls = array($successUrl, $cancelUrl, $pendingUrl);
  183. return $this;
  184. }
  185. /**
  186. * Set create billing agreement flag
  187. *
  188. * @param bool $flag
  189. * @return Mage_Paypal_Model_Express_Checkout
  190. */
  191. public function setIsBillingAgreementRequested($flag)
  192. {
  193. $this->_isBARequested = $flag;
  194. return $this;
  195. }
  196. /**
  197. * Setter for customer Id
  198. *
  199. * @param int $id
  200. * @return Mage_Paypal_Model_Express_Checkout
  201. * @deprecated please use self::setCustomer
  202. */
  203. public function setCustomerId($id)
  204. {
  205. $this->_customerId = $id;
  206. return $this;
  207. }
  208. /**
  209. * Setter for customer
  210. *
  211. * @param Mage_Customer_Model_Customer $customer
  212. * @return Mage_Paypal_Model_Express_Checkout
  213. */
  214. public function setCustomer($customer)
  215. {
  216. $this->_quote->assignCustomer($customer);
  217. $this->_customerId = $customer->getId();
  218. return $this;
  219. }
  220. /**
  221. * Setter for customer with billing and shipping address changing ability
  222. *
  223. * @param Mage_Customer_Model_Customer $customer
  224. * @param Mage_Sales_Model_Quote_Address $billingAddress
  225. * @param Mage_Sales_Model_Quote_Address $shippingAddress
  226. * @return Mage_Paypal_Model_Express_Checkout
  227. */
  228. public function setCustomerWithAddressChange($customer, $billingAddress = null, $shippingAddress = null)
  229. {
  230. $this->_quote->assignCustomerWithAddressChange($customer, $billingAddress, $shippingAddress);
  231. $this->_customerId = $customer->getId();
  232. return $this;
  233. }
  234. /**
  235. * Reserve order ID for specified quote and start checkout on PayPal
  236. *
  237. * @param string $returnUrl
  238. * @param string $cancelUrl
  239. * @return mixed
  240. */
  241. public function start($returnUrl, $cancelUrl)
  242. {
  243. $this->_quote->collectTotals();
  244. if (!$this->_quote->getGrandTotal() && !$this->_quote->hasNominalItems()) {
  245. Mage::throwException(Mage::helper('paypal')->__('PayPal does not support processing orders with zero amount. To complete your purchase, proceed to the standard checkout process.'));
  246. }
  247. $this->_quote->reserveOrderId()->save();
  248. // prepare API
  249. $this->_getApi();
  250. $this->_api->setAmount($this->_quote->getBaseGrandTotal())
  251. ->setCurrencyCode($this->_quote->getBaseCurrencyCode())
  252. ->setInvNum($this->_quote->getReservedOrderId())
  253. ->setReturnUrl($returnUrl)
  254. ->setCancelUrl($cancelUrl)
  255. ->setSolutionType($this->_config->solutionType)
  256. ->setPaymentAction($this->_config->paymentAction)
  257. ;
  258. if ($this->_giropayUrls) {
  259. list($successUrl, $cancelUrl, $pendingUrl) = $this->_giropayUrls;
  260. $this->_api->addData(array(
  261. 'giropay_cancel_url' => $cancelUrl,
  262. 'giropay_success_url' => $successUrl,
  263. 'giropay_bank_txn_pending_url' => $pendingUrl,
  264. ));
  265. }
  266. $this->_setBillingAgreementRequest();
  267. if ($this->_config->requireBillingAddress == Mage_Paypal_Model_Config::REQUIRE_BILLING_ADDRESS_ALL) {
  268. $this->_api->setRequireBillingAddress(1);
  269. }
  270. // supress or export shipping address
  271. if ($this->_quote->getIsVirtual()) {
  272. if ($this->_config->requireBillingAddress == Mage_Paypal_Model_Config::REQUIRE_BILLING_ADDRESS_VIRTUAL) {
  273. $this->_api->setRequireBillingAddress(1);
  274. }
  275. $this->_api->setSuppressShipping(true);
  276. } else {
  277. $address = $this->_quote->getShippingAddress();
  278. $isOverriden = 0;
  279. if (true === $address->validate()) {
  280. $isOverriden = 1;
  281. $this->_api->setAddress($address);
  282. }
  283. $this->_quote->getPayment()->setAdditionalInformation(
  284. self::PAYMENT_INFO_TRANSPORT_SHIPPING_OVERRIDEN, $isOverriden
  285. );
  286. $this->_quote->getPayment()->save();
  287. }
  288. // add line items
  289. $paypalCart = Mage::getModel('paypal/cart', array($this->_quote));
  290. $this->_api->setPaypalCart($paypalCart)
  291. ->setIsLineItemsEnabled($this->_config->lineItemsEnabled)
  292. ;
  293. // add shipping options if needed and line items are available
  294. if ($this->_config->lineItemsEnabled && $this->_config->transferShippingOptions && $paypalCart->getItems()) {
  295. if (!$this->_quote->getIsVirtual() && !$this->_quote->hasNominalItems()) {
  296. if ($options = $this->_prepareShippingOptions($address, true)) {
  297. $this->_api->setShippingOptionsCallbackUrl(
  298. Mage::getUrl('*/*/shippingOptionsCallback', array('quote_id' => $this->_quote->getId()))
  299. )->setShippingOptions($options);
  300. }
  301. }
  302. }
  303. // add recurring payment profiles information
  304. if ($profiles = $this->_quote->prepareRecurringPaymentProfiles()) {
  305. foreach ($profiles as $profile) {
  306. $profile->setMethodCode(Mage_Paypal_Model_Config::METHOD_WPP_EXPRESS);
  307. if (!$profile->isValid()) {
  308. Mage::throwException($profile->getValidationErrors(true, true));
  309. }
  310. }
  311. $this->_api->addRecurringPaymentProfiles($profiles);
  312. }
  313. $this->_config->exportExpressCheckoutStyleSettings($this->_api);
  314. // call API and redirect with token
  315. $this->_api->callSetExpressCheckout();
  316. $token = $this->_api->getToken();
  317. $this->_redirectUrl = $this->_config->getExpressCheckoutStartUrl($token);
  318. $this->_quote->getPayment()->unsAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT);
  319. $this->_quote->getPayment()->save();
  320. return $token;
  321. }
  322. /**
  323. * Update quote when returned from PayPal
  324. * rewrite billing address by paypal
  325. * save old billing address for new customer
  326. * export shipping address in case address absence
  327. *
  328. * @param string $token
  329. */
  330. public function returnFromPaypal($token)
  331. {
  332. $this->_getApi();
  333. $this->_api->setToken($token)
  334. ->callGetExpressCheckoutDetails();
  335. $quote = $this->_quote;
  336. $this->_ignoreAddressValidation();
  337. // import billing address
  338. $billingAddress = $quote->getBillingAddress();
  339. $exportedBillingAddress = $this->_api->getExportedBillingAddress();
  340. $quote->setCustomerEmail($billingAddress->getEmail());
  341. $quote->setCustomerPrefix($billingAddress->getPrefix());
  342. $quote->setCustomerFirstname($billingAddress->getFirstname());
  343. $quote->setCustomerMiddlename($billingAddress->getMiddlename());
  344. $quote->setCustomerLastname($billingAddress->getLastname());
  345. $quote->setCustomerSuffix($billingAddress->getSuffix());
  346. $quote->setCustomerNote($exportedBillingAddress->getData('note'));
  347. $this->_setExportedAddressData($billingAddress, $exportedBillingAddress);
  348. // import shipping address
  349. $exportedShippingAddress = $this->_api->getExportedShippingAddress();
  350. if (!$quote->getIsVirtual()) {
  351. $shippingAddress = $quote->getShippingAddress();
  352. if ($shippingAddress) {
  353. if ($exportedShippingAddress) {
  354. $this->_setExportedAddressData($shippingAddress, $exportedShippingAddress);
  355. $shippingAddress->setCollectShippingRates(true);
  356. $shippingAddress->setSameAsBilling(0);
  357. }
  358. // import shipping method
  359. $code = '';
  360. if ($this->_api->getShippingRateCode()) {
  361. if ($code = $this->_matchShippingMethodCode($shippingAddress, $this->_api->getShippingRateCode())) {
  362. // possible bug of double collecting rates :-/
  363. $shippingAddress->setShippingMethod($code)->setCollectShippingRates(true);
  364. }
  365. }
  366. $quote->getPayment()->setAdditionalInformation(
  367. self::PAYMENT_INFO_TRANSPORT_SHIPPING_METHOD,
  368. $code
  369. );
  370. }
  371. }
  372. // import payment info
  373. $payment = $quote->getPayment();
  374. $payment->setMethod($this->_methodType);
  375. Mage::getSingleton('paypal/info')->importToPayment($this->_api, $payment);
  376. $payment->setAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_PAYER_ID, $this->_api->getPayerId())
  377. ->setAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_TOKEN, $token)
  378. ;
  379. $quote->collectTotals()->save();
  380. }
  381. /**
  382. * Check whether order review has enough data to initialize
  383. *
  384. * @param $token
  385. * @throws Mage_Core_Exception
  386. */
  387. public function prepareOrderReview($token = null)
  388. {
  389. $payment = $this->_quote->getPayment();
  390. if (!$payment || !$payment->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_PAYER_ID)) {
  391. Mage::throwException(Mage::helper('paypal')->__('Payer is not identified.'));
  392. }
  393. $this->_quote->setMayEditShippingAddress(
  394. 1 != $this->_quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_SHIPPING_OVERRIDEN)
  395. );
  396. $this->_quote->setMayEditShippingMethod(
  397. '' == $this->_quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_SHIPPING_METHOD)
  398. );
  399. $this->_ignoreAddressValidation();
  400. $this->_quote->collectTotals()->save();
  401. }
  402. /**
  403. * Return callback response with shipping options
  404. *
  405. * @param array $request
  406. * @return string
  407. */
  408. public function getShippingOptionsCallbackResponse(array $request)
  409. {
  410. // prepare debug data
  411. $logger = Mage::getModel('core/log_adapter', 'payment_' . $this->_methodType . '.log');
  412. $debugData = array('request' => $request, 'response' => array());
  413. try {
  414. // obtain addresses
  415. $this->_getApi();
  416. $address = $this->_api->prepareShippingOptionsCallbackAddress($request);
  417. $quoteAddress = $this->_quote->getShippingAddress();
  418. // compare addresses, calculate shipping rates and prepare response
  419. $options = array();
  420. if ($address && $quoteAddress && !$this->_quote->getIsVirtual()) {
  421. foreach ($address->getExportedKeys() as $key) {
  422. $quoteAddress->setDataUsingMethod($key, $address->getData($key));
  423. }
  424. $quoteAddress->setCollectShippingRates(true)->collectTotals();
  425. $options = $this->_prepareShippingOptions($quoteAddress, false, true);
  426. }
  427. $response = $this->_api->setShippingOptions($options)->formatShippingOptionsCallback();
  428. // log request and response
  429. $debugData['response'] = $response;
  430. $logger->log($debugData);
  431. return $response;
  432. } catch (Exception $e) {
  433. $logger->log($debugData);
  434. throw $e;
  435. }
  436. }
  437. /**
  438. * Set shipping method to quote, if needed
  439. * @param string $methodCode
  440. */
  441. public function updateShippingMethod($methodCode)
  442. {
  443. if (!$this->_quote->getIsVirtual() && $shippingAddress = $this->_quote->getShippingAddress()) {
  444. if ($methodCode != $shippingAddress->getShippingMethod()) {
  445. $this->_ignoreAddressValidation();
  446. $shippingAddress->setShippingMethod($methodCode)->setCollectShippingRates(true);
  447. $this->_quote->collectTotals();
  448. }
  449. }
  450. }
  451. /**
  452. * Update order data
  453. *
  454. * @param array $data
  455. */
  456. public function updateOrder($data)
  457. {
  458. /** @var $checkout Mage_Checkout_Model_Type_Onepage */
  459. $checkout = Mage::getModel('checkout/type_onepage');
  460. $this->_quote->setTotalsCollectedFlag(true);
  461. $checkout->setQuote($this->_quote);
  462. if (isset($data['billing'])) {
  463. if (isset($data['customer-email'])) {
  464. $data['billing']['email'] = $data['customer-email'];
  465. }
  466. $checkout->saveBilling($data['billing'], 0);
  467. }
  468. if (!$this->_quote->getIsVirtual() && isset($data['shipping'])) {
  469. $checkout->saveShipping($data['shipping'], 0);
  470. }
  471. if (isset($data['shipping_method'])) {
  472. $this->updateShippingMethod($data['shipping_method']);
  473. }
  474. $this->_quote->setTotalsCollectedFlag(false);
  475. $this->_quote->collectTotals();
  476. $this->_quote->setDataChanges(true);
  477. $this->_quote->save();
  478. }
  479. /**
  480. * Place the order and recurring payment profiles when customer returned from paypal
  481. * Until this moment all quote data must be valid
  482. *
  483. * @param string $token
  484. * @param string $shippingMethodCode
  485. */
  486. public function place($token, $shippingMethodCode = null)
  487. {
  488. if ($shippingMethodCode) {
  489. $this->updateShippingMethod($shippingMethodCode);
  490. }
  491. $isNewCustomer = false;
  492. switch ($this->getCheckoutMethod()) {
  493. case Mage_Checkout_Model_Type_Onepage::METHOD_GUEST:
  494. $this->_prepareGuestQuote();
  495. break;
  496. case Mage_Checkout_Model_Type_Onepage::METHOD_REGISTER:
  497. $this->_prepareNewCustomerQuote();
  498. $isNewCustomer = true;
  499. break;
  500. default:
  501. $this->_prepareCustomerQuote();
  502. break;
  503. }
  504. $this->_ignoreAddressValidation();
  505. $this->_quote->collectTotals();
  506. $service = Mage::getModel('sales/service_quote', $this->_quote);
  507. $service->submitAll();
  508. $this->_quote->save();
  509. if ($isNewCustomer) {
  510. try {
  511. $this->_involveNewCustomer();
  512. } catch (Exception $e) {
  513. Mage::logException($e);
  514. }
  515. }
  516. $this->_recurringPaymentProfiles = $service->getRecurringPaymentProfiles();
  517. // TODO: send recurring profile emails
  518. $order = $service->getOrder();
  519. if (!$order) {
  520. return;
  521. }
  522. $this->_billingAgreement = $order->getPayment()->getBillingAgreement();
  523. // commence redirecting to finish payment, if paypal requires it
  524. if ($order->getPayment()->getAdditionalInformation(
  525. Mage_Paypal_Model_Express_Checkout::PAYMENT_INFO_TRANSPORT_REDIRECT
  526. )) {
  527. $this->_redirectUrl = $this->_config->getExpressCheckoutCompleteUrl($token);
  528. }
  529. switch ($order->getState()) {
  530. // even after placement paypal can disallow to authorize/capture, but will wait until bank transfers money
  531. case Mage_Sales_Model_Order::STATE_PENDING_PAYMENT:
  532. // TODO
  533. break;
  534. // regular placement, when everything is ok
  535. case Mage_Sales_Model_Order::STATE_PROCESSING:
  536. case Mage_Sales_Model_Order::STATE_COMPLETE:
  537. case Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW:
  538. $order->sendNewOrderEmail();
  539. break;
  540. }
  541. $this->_order = $order;
  542. }
  543. /**
  544. * Make sure addresses will be saved without validation errors
  545. */
  546. private function _ignoreAddressValidation()
  547. {
  548. $this->_quote->getBillingAddress()->setShouldIgnoreValidation(true);
  549. if (!$this->_quote->getIsVirtual()) {
  550. $this->_quote->getShippingAddress()->setShouldIgnoreValidation(true);
  551. if (!$this->_config->requireBillingAddress && !$this->_quote->getBillingAddress()->getEmail()) {
  552. $this->_quote->getBillingAddress()->setSameAsBilling(1);
  553. }
  554. }
  555. }
  556. /**
  557. * Determine whether redirect somewhere specifically is required
  558. *
  559. * @return string
  560. */
  561. public function getRedirectUrl()
  562. {
  563. return $this->_redirectUrl;
  564. }
  565. /**
  566. * Return recurring payment profiles
  567. *
  568. * @return array
  569. */
  570. public function getRecurringPaymentProfiles()
  571. {
  572. return $this->_recurringPaymentProfiles;
  573. }
  574. /**
  575. * Get created billing agreement
  576. *
  577. * @return Mage_Sales_Model_Billing_Agreement|null
  578. */
  579. public function getBillingAgreement()
  580. {
  581. return $this->_billingAgreement;
  582. }
  583. /**
  584. * Return order
  585. *
  586. * @return Mage_Sales_Model_Order
  587. */
  588. public function getOrder()
  589. {
  590. return $this->_order;
  591. }
  592. /**
  593. * Get checkout method
  594. *
  595. * @return string
  596. */
  597. public function getCheckoutMethod()
  598. {
  599. if ($this->getCustomerSession()->isLoggedIn()) {
  600. return Mage_Checkout_Model_Type_Onepage::METHOD_CUSTOMER;
  601. }
  602. if (!$this->_quote->getCheckoutMethod()) {
  603. if (Mage::helper('checkout')->isAllowedGuestCheckout($this->_quote)) {
  604. $this->_quote->setCheckoutMethod(Mage_Checkout_Model_Type_Onepage::METHOD_GUEST);
  605. } else {
  606. $this->_quote->setCheckoutMethod(Mage_Checkout_Model_Type_Onepage::METHOD_REGISTER);
  607. }
  608. }
  609. return $this->_quote->getCheckoutMethod();
  610. }
  611. /**
  612. * Sets address data from exported address
  613. *
  614. * @param Mage_Sales_Model_Quote_Address $address
  615. * @param array $exportedAddress
  616. */
  617. protected function _setExportedAddressData($address, $exportedAddress)
  618. {
  619. foreach ($exportedAddress->getExportedKeys() as $key) {
  620. $oldData = $address->getDataUsingMethod($key);
  621. $isEmpty = null;
  622. if (is_array($oldData)) {
  623. foreach($oldData as $val) {
  624. if(!empty($val)) {
  625. $isEmpty = false;
  626. break;
  627. }
  628. $isEmpty = true;
  629. }
  630. }
  631. if (empty($oldData) || $isEmpty === true) {
  632. $address->setDataUsingMethod($key, $exportedAddress->getData($key));
  633. }
  634. }
  635. }
  636. /**
  637. * Set create billing agreement flag to api call
  638. *
  639. * @return Mage_Paypal_Model_Express_Checkout
  640. */
  641. protected function _setBillingAgreementRequest()
  642. {
  643. if (!$this->_customerId || $this->_quote->hasNominalItems()) {
  644. return $this;
  645. }
  646. $isRequested = $this->_isBARequested || $this->_quote->getPayment()
  647. ->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT);
  648. if (!($this->_config->allow_ba_signup == Mage_Paypal_Model_Config::EC_BA_SIGNUP_AUTO
  649. || $isRequested && $this->_config->shouldAskToCreateBillingAgreement())) {
  650. return $this;
  651. }
  652. if (!Mage::getModel('sales/billing_agreement')->needToCreateForCustomer($this->_customerId)) {
  653. return $this;
  654. }
  655. $this->_api->setBillingType($this->_api->getBillingAgreementType());
  656. return $this;
  657. }
  658. /**
  659. * @return Mage_Paypal_Model_Api_Nvp
  660. */
  661. protected function _getApi()
  662. {
  663. if (null === $this->_api) {
  664. $this->_api = Mage::getModel($this->_apiType)->setConfigObject($this->_config);
  665. }
  666. return $this->_api;
  667. }
  668. /**
  669. * Attempt to collect address shipping rates and return them for further usage in instant update API
  670. * Returns empty array if it was impossible to obtain any shipping rate
  671. * If there are shipping rates obtained, the method must return one of them as default.
  672. *
  673. * @param Mage_Sales_Model_Quote_Address $address
  674. * @param bool $mayReturnEmpty
  675. * @return array|false
  676. */
  677. protected function _prepareShippingOptions(
  678. Mage_Sales_Model_Quote_Address $address,
  679. $mayReturnEmpty = false, $calculateTax = false
  680. ) {
  681. $options = array(); $i = 0; $iMin = false; $min = false;
  682. $userSelectedOption = null;
  683. foreach ($address->getGroupedAllShippingRates() as $group) {
  684. foreach ($group as $rate) {
  685. $amount = (float)$rate->getPrice();
  686. if ($rate->getErrorMessage()) {
  687. continue;
  688. }
  689. $isDefault = $address->getShippingMethod() === $rate->getCode();
  690. $amountExclTax = Mage::helper('tax')->getShippingPrice($amount, false, $address);
  691. $amountInclTax = Mage::helper('tax')->getShippingPrice($amount, true, $address);
  692. $options[$i] = new Varien_Object(array(
  693. 'is_default' => $isDefault,
  694. 'name' => trim("{$rate->getCarrierTitle()} - {$rate->getMethodTitle()}", ' -'),
  695. 'code' => $rate->getCode(),
  696. 'amount' => $amountExclTax,
  697. ));
  698. if ($calculateTax) {
  699. $options[$i]->setTaxAmount(
  700. $amountInclTax - $amountExclTax
  701. + $address->getTaxAmount() - $address->getShippingTaxAmount()
  702. );
  703. }
  704. if ($isDefault) {
  705. $userSelectedOption = $options[$i];
  706. }
  707. if (false === $min || $amountInclTax < $min) {
  708. $min = $amountInclTax;
  709. $iMin = $i;
  710. }
  711. $i++;
  712. }
  713. }
  714. if ($mayReturnEmpty && is_null($userSelectedOption)) {
  715. $options[] = new Varien_Object(array(
  716. 'is_default' => true,
  717. 'name' => Mage::helper('paypal')->__('N/A'),
  718. 'code' => 'no_rate',
  719. 'amount' => 0.00,
  720. ));
  721. if ($calculateTax) {
  722. $options[$i]->setTaxAmount($address->getTaxAmount());
  723. }
  724. } elseif (is_null($userSelectedOption) && isset($options[$iMin])) {
  725. $options[$iMin]->setIsDefault(true);
  726. }
  727. // Magento will transfer only first 10 cheapest shipping options if there are more than 10 available.
  728. if (count($options) > 10) {
  729. usort($options, array(get_class($this),'cmpShippingOptions'));
  730. array_splice($options, 10);
  731. // User selected option will be always included in options list
  732. if (!is_null($userSelectedOption) && !in_array($userSelectedOption, $options)) {
  733. $options[9] = $userSelectedOption;
  734. }
  735. }
  736. return $options;
  737. }
  738. /**
  739. * Compare two shipping options based on their amounts
  740. *
  741. * This function is used as a callback comparison function in shipping options sorting process
  742. * @see self::_prepareShippingOptions()
  743. *
  744. * @param Varien_Object $option1
  745. * @param Varien_Object $option2
  746. * @return integer
  747. */
  748. protected static function cmpShippingOptions(Varien_Object $option1, Varien_Object $option2)
  749. {
  750. if ($option1->getAmount() == $option2->getAmount()) {
  751. return 0;
  752. }
  753. return ($option1->getAmount() < $option2->getAmount()) ? -1 : 1;
  754. }
  755. /**
  756. * Try to find whether the code provided by PayPal corresponds to any of possible shipping rates
  757. * This method was created only because PayPal has issues with returning the selected code.
  758. * 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
  759. * before collecting shipping rates
  760. *
  761. * @param Mage_Sales_Model_Quote_Address $address
  762. * @param string $selectedCode
  763. * @return string
  764. */
  765. protected function _matchShippingMethodCode(Mage_Sales_Model_Quote_Address $address, $selectedCode)
  766. {
  767. $options = $this->_prepareShippingOptions($address, false);
  768. foreach ($options as $option) {
  769. if ($selectedCode === $option['code'] // the proper case as outlined in documentation
  770. || $selectedCode === $option['name'] // workaround: PayPal may return name instead of the code
  771. // workaround: PayPal may concatenate code and name, and return it instead of the code:
  772. || $selectedCode === "{$option['code']} {$option['name']}"
  773. ) {
  774. return $option['code'];
  775. }
  776. }
  777. return '';
  778. }
  779. /**
  780. * Prepare quote for guest checkout order submit
  781. *
  782. * @return Mage_Paypal_Model_Express_Checkout
  783. */
  784. protected function _prepareGuestQuote()
  785. {
  786. $quote = $this->_quote;
  787. $quote->setCustomerId(null)
  788. ->setCustomerEmail($quote->getBillingAddress()->getEmail())
  789. ->setCustomerIsGuest(true)
  790. ->setCustomerGroupId(Mage_Customer_Model_Group::NOT_LOGGED_IN_ID);
  791. return $this;
  792. }
  793. /**
  794. * Prepare quote for customer registration and customer order submit
  795. * and restore magento customer data from quote
  796. *
  797. * @return Mage_Paypal_Model_Express_Checkout
  798. */
  799. protected function _prepareNewCustomerQuote()
  800. {
  801. $quote = $this->_quote;
  802. $billing = $quote->getBillingAddress();
  803. $shipping = $quote->isVirtual() ? null : $quote->getShippingAddress();
  804. $customer = $quote->getCustomer();
  805. /** @var $customer Mage_Customer_Model_Customer */
  806. $customerBilling = $billing->exportCustomerAddress();
  807. $customer->addAddress($customerBilling);
  808. $billing->setCustomerAddress($customerBilling);
  809. $customerBilling->setIsDefaultBilling(true);
  810. if ($shipping && !$shipping->getSameAsBilling()) {
  811. $customerShipping = $shipping->exportCustomerAddress();
  812. $customer->addAddress($customerShipping);
  813. $shipping->setCustomerAddress($customerShipping);
  814. $customerShipping->setIsDefaultShipping(true);
  815. } elseif ($shipping) {
  816. $customerBilling->setIsDefaultShipping(true);
  817. }
  818. /**
  819. * @todo integration with dynamica attributes customer_dob, customer_taxvat, customer_gender
  820. */
  821. if ($quote->getCustomerDob() && !$billing->getCustomerDob()) {
  822. $billing->setCustomerDob($quote->getCustomerDob());
  823. }
  824. if ($quote->getCustomerTaxvat() && !$billing->getCustomerTaxvat()) {
  825. $billing->setCustomerTaxvat($quote->getCustomerTaxvat());
  826. }
  827. if ($quote->getCustomerGender() && !$billing->getCustomerGender()) {
  828. $billing->setCustomerGender($quote->getCustomerGender());
  829. }
  830. Mage::helper('core')->copyFieldset('checkout_onepage_billing', 'to_customer', $billing, $customer);
  831. $customer->setEmail($quote->getCustomerEmail());
  832. $customer->setPrefix($quote->getCustomerPrefix());
  833. $customer->setFirstname($quote->getCustomerFirstname());
  834. $customer->setMiddlename($quote->getCustomerMiddlename());
  835. $customer->setLastname($quote->getCustomerLastname());
  836. $customer->setSuffix($quote->getCustomerSuffix());
  837. $customer->setPassword($customer->decryptPassword($quote->getPasswordHash()));
  838. $customer->setPasswordHash($customer->hashPassword($customer->getPassword()));
  839. $customer->save();
  840. $quote->setCustomer($customer);
  841. return $this;
  842. }
  843. /**
  844. * Prepare quote for customer order submit
  845. *
  846. * @return Mage_Paypal_Model_Express_Checkout
  847. */
  848. protected function _prepareCustomerQuote()
  849. {
  850. $quote = $this->_quote;
  851. $billing = $quote->getBillingAddress();
  852. $shipping = $quote->isVirtual() ? null : $quote->getShippingAddress();
  853. $customer = $this->getCustomerSession()->getCustomer();
  854. if (!$billing->getCustomerId() || $billing->getSaveInAddressBook()) {
  855. $customerBilling = $billing->exportCustomerAddress();
  856. $customer->addAddress($customerBilling);
  857. $billing->setCustomerAddress($customerBilling);
  858. }
  859. if ($shipping && ((!$shipping->getCustomerId() && !$shipping->getSameAsBilling())
  860. || (!$shipping->getSameAsBilling() && $shipping->getSaveInAddressBook()))) {
  861. $customerShipping = $shipping->exportCustomerAddress();
  862. $customer->addAddress($customerShipping);
  863. $shipping->setCustomerAddress($customerShipping);
  864. }
  865. if (isset($customerBilling) && !$customer->getDefaultBilling()) {
  866. $customerBilling->setIsDefaultBilling(true);
  867. }
  868. if ($shipping && isset($customerBilling) && !$customer->getDefaultShipping() && $shipping->getSameAsBilling()) {
  869. $customerBilling->setIsDefaultShipping(true);
  870. } elseif ($shipping && isset($customerShipping) && !$customer->getDefaultShipping()) {
  871. $customerShipping->setIsDefaultShipping(true);
  872. }
  873. $quote->setCustomer($customer);
  874. return $this;
  875. }
  876. /**
  877. * Involve new customer to system
  878. *
  879. * @return Mage_Paypal_Model_Express_Checkout
  880. */
  881. protected function _involveNewCustomer()
  882. {
  883. $customer = $this->_quote->getCustomer();
  884. if ($customer->isConfirmationRequired()) {
  885. $customer->sendNewAccountEmail('confirmation');
  886. $url = Mage::helper('customer')->getEmailConfirmationUrl($customer->getEmail());
  887. $this->getCustomerSession()->addSuccess(
  888. Mage::helper('customer')->__('Account confirmation is required. Please, check your e-mail for confirmation link. To resend confirmation email please <a href="%s">click here</a>.', $url)
  889. );
  890. } else {
  891. $customer->sendNewAccountEmail();
  892. $this->getCustomerSession()->loginById($customer->getId());
  893. }
  894. return $this;
  895. }
  896. /**
  897. * Get customer session object
  898. *
  899. * @return Mage_Customer_Model_Session
  900. */
  901. public function getCustomerSession()
  902. {
  903. return $this->_customerSession;
  904. }
  905. }