PageRenderTime 68ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://bitbucket.org/MXWest/magento-ce-1.5.1.0
PHP | 715 lines | 396 code | 68 blank | 251 comment | 69 complexity | 77147b557f29ab7c88309c1d1715e522 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0, WTFPL
  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) 2010 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. * Redirect urls supposed to be set to support giropay
  83. *
  84. * @var array
  85. */
  86. protected $_giropayUrls = array();
  87. /**
  88. * Create Billing Agreement flag
  89. *
  90. * @var bool
  91. */
  92. protected $_isBARequested = false;
  93. /**
  94. * Customer ID
  95. *
  96. * @var int
  97. */
  98. protected $_customerId = null;
  99. /**
  100. * Recurring payment profiles
  101. *
  102. * @var array
  103. */
  104. protected $_recurringPaymentProfiles = array();
  105. /**
  106. * Billing agreement that might be created during order placing
  107. *
  108. * @var Mage_Sales_Model_Billing_Agreement
  109. */
  110. protected $_billingAgreement = null;
  111. /**
  112. * Order
  113. *
  114. * @var Mage_Sales_Model_QuoteMage_Sales_Model_Quote
  115. */
  116. protected $_order = null;
  117. /**
  118. * Set quote and config instances
  119. * @param array $params
  120. */
  121. public function __construct($params = array())
  122. {
  123. if (isset($params['quote']) && $params['quote'] instanceof Mage_Sales_Model_Quote) {
  124. $this->_quote = $params['quote'];
  125. } else {
  126. throw new Exception('Quote instance is required.');
  127. }
  128. if (isset($params['config']) && $params['config'] instanceof Mage_Paypal_Model_Config) {
  129. $this->_config = $params['config'];
  130. } else {
  131. throw new Exception('Config instance is required.');
  132. }
  133. }
  134. /**
  135. * Checkout with PayPal image URL getter
  136. * Spares API calls of getting "pal" variable, by putting it into cache per store view
  137. * @return string
  138. */
  139. public function getCheckoutShortcutImageUrl()
  140. {
  141. // get "pal" thing from cache or lookup it via API
  142. $pal = null;
  143. if ($this->_config->areButtonsDynamic()) {
  144. $cacheId = self::PAL_CACHE_ID . Mage::app()->getStore()->getId();
  145. $pal = Mage::app()->loadCache($cacheId);
  146. if (-1 == $pal) {
  147. $pal = null;
  148. } elseif (!$pal) {
  149. $pal = null;
  150. $this->_getApi();
  151. try {
  152. $this->_api->callGetPalDetails();
  153. $pal = $this->_api->getPal();
  154. Mage::app()->saveCache($pal, $cacheId, array(Mage_Core_Model_Config::CACHE_TAG));
  155. } catch (Exception $e) {
  156. Mage::app()->saveCache(-1, $cacheId, array(Mage_Core_Model_Config::CACHE_TAG));
  157. Mage::logException($e);
  158. }
  159. }
  160. }
  161. return $this->_config->getExpressCheckoutShortcutImageUrl(
  162. Mage::app()->getLocale()->getLocaleCode(),
  163. $this->_quote->getBaseGrandTotal(),
  164. $pal
  165. );
  166. }
  167. /**
  168. * Setter that enables giropay redirects flow
  169. *
  170. * @param string $successUrl - payment success result
  171. * @param string $cancelUrl - payment cancellation result
  172. * @param string $pendingUrl - pending payment result
  173. */
  174. public function prepareGiropayUrls($successUrl, $cancelUrl, $pendingUrl)
  175. {
  176. $this->_giropayUrls = array($successUrl, $cancelUrl, $pendingUrl);
  177. return $this;
  178. }
  179. /**
  180. * Set create billing agreement flag
  181. *
  182. * @param bool $flag
  183. * @return Mage_Paypal_Model_Express_Checkout
  184. */
  185. public function setIsBillingAgreementRequested($flag)
  186. {
  187. $this->_isBARequested = $flag;
  188. return $this;
  189. }
  190. /**
  191. * Setter for customer Id
  192. *
  193. * @param int $id
  194. * @return Mage_Paypal_Model_Express_Checkout
  195. * @deprecated please use self::setCustomer
  196. */
  197. public function setCustomerId($id)
  198. {
  199. $this->_customerId = $id;
  200. return $this;
  201. }
  202. /**
  203. * Setter for customer
  204. *
  205. * @param Mage_Customer_Model_Customer $customer
  206. * @return Mage_Paypal_Model_Express_Checkout
  207. */
  208. public function setCustomer($customer)
  209. {
  210. $this->_quote->assignCustomer($customer);
  211. $this->_customerId = $customer->getId();
  212. return $this;
  213. }
  214. /**
  215. * Setter for customer with billing and shipping address changing ability
  216. *
  217. * @param Mage_Customer_Model_Customer $customer
  218. * @param Mage_Sales_Model_Quote_Address $billingAddress
  219. * @param Mage_Sales_Model_Quote_Address $shippingAddress
  220. * @return Mage_Paypal_Model_Express_Checkout
  221. */
  222. public function setCustomerWithAddressChange($customer, $billingAddress = null, $shippingAddress = null)
  223. {
  224. $this->_quote->assignCustomerWithAddressChange($customer, $billingAddress, $shippingAddress);
  225. $this->_customerId = $customer->getId();
  226. return $this;
  227. }
  228. /**
  229. * Reserve order ID for specified quote and start checkout on PayPal
  230. * @return string
  231. */
  232. public function start($returnUrl, $cancelUrl)
  233. {
  234. $this->_quote->collectTotals();
  235. if (!$this->_quote->getGrandTotal() && !$this->_quote->hasNominalItems()) {
  236. Mage::throwException(Mage::helper('paypal')->__('PayPal does not support processing orders with zero amount. To complete your purchase, proceed to the standard checkout process.'));
  237. }
  238. $this->_quote->reserveOrderId()->save();
  239. // prepare API
  240. $this->_getApi();
  241. $this->_api->setAmount($this->_quote->getBaseGrandTotal())
  242. ->setCurrencyCode($this->_quote->getBaseCurrencyCode())
  243. ->setInvNum($this->_quote->getReservedOrderId())
  244. ->setReturnUrl($returnUrl)
  245. ->setCancelUrl($cancelUrl)
  246. ->setSolutionType($this->_config->solutionType)
  247. ->setPaymentAction($this->_config->paymentAction)
  248. ;
  249. if ($this->_giropayUrls) {
  250. list($successUrl, $cancelUrl, $pendingUrl) = $this->_giropayUrls;
  251. $this->_api->addData(array(
  252. 'giropay_cancel_url' => $cancelUrl,
  253. 'giropay_success_url' => $successUrl,
  254. 'giropay_bank_txn_pending_url' => $pendingUrl,
  255. ));
  256. }
  257. $this->_setBillingAgreementRequest();
  258. // supress or export shipping address
  259. if ($this->_quote->getIsVirtual()) {
  260. $this->_api->setSuppressShipping(true);
  261. } else {
  262. $address = $this->_quote->getShippingAddress();
  263. $isOverriden = 0;
  264. if (true === $address->validate()) {
  265. $isOverriden = 1;
  266. $this->_api->setAddress($address);
  267. }
  268. $this->_quote->getPayment()->setAdditionalInformation(
  269. self::PAYMENT_INFO_TRANSPORT_SHIPPING_OVERRIDEN, $isOverriden
  270. );
  271. $this->_quote->getPayment()->save();
  272. }
  273. // add line items
  274. $paypalCart = Mage::getModel('paypal/cart', array($this->_quote));
  275. $this->_api->setPaypalCart($paypalCart)
  276. ->setIsLineItemsEnabled($this->_config->lineItemsEnabled)
  277. ;
  278. // add shipping options if needed and line items are available
  279. if ($this->_config->lineItemsEnabled && $this->_config->transferShippingOptions && $paypalCart->getItems()) {
  280. if (!$this->_quote->getIsVirtual() && !$this->_quote->hasNominalItems()) {
  281. if ($options = $this->_prepareShippingOptions($address, true)) {
  282. $this->_api->setShippingOptionsCallbackUrl(
  283. Mage::getUrl('*/*/shippingOptionsCallback', array('quote_id' => $this->_quote->getId()))
  284. )->setShippingOptions($options);
  285. }
  286. }
  287. }
  288. // add recurring payment profiles information
  289. if ($profiles = $this->_quote->prepareRecurringPaymentProfiles()) {
  290. foreach ($profiles as $profile) {
  291. $profile->setMethodCode(Mage_Paypal_Model_Config::METHOD_WPP_EXPRESS);
  292. if (!$profile->isValid()) {
  293. Mage::throwException($profile->getValidationErrors(true, true));
  294. }
  295. }
  296. $this->_api->addRecurringPaymentProfiles($profiles);
  297. }
  298. $this->_config->exportExpressCheckoutStyleSettings($this->_api);
  299. // call API and redirect with token
  300. $this->_api->callSetExpressCheckout();
  301. $token = $this->_api->getToken();
  302. $this->_redirectUrl = $this->_config->getExpressCheckoutStartUrl($token);
  303. $this->_quote->getPayment()->unsAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT);
  304. $this->_quote->getPayment()->save();
  305. return $token;
  306. }
  307. /**
  308. * Update quote when returned from PayPal
  309. * @param string $token
  310. */
  311. public function returnFromPaypal($token)
  312. {
  313. $this->_getApi();
  314. $this->_api->setToken($token)
  315. ->callGetExpressCheckoutDetails();
  316. // import billing address
  317. $billingAddress = $this->_quote->getBillingAddress();
  318. $exportedBillingAddress = $this->_api->getExportedBillingAddress();
  319. foreach ($exportedBillingAddress->getExportedKeys() as $key) {
  320. $billingAddress->setDataUsingMethod($key, $exportedBillingAddress->getData($key));
  321. }
  322. // import shipping address
  323. $exportedShippingAddress = $this->_api->getExportedShippingAddress();
  324. if (!$this->_quote->getIsVirtual()) {
  325. $shippingAddress = $this->_quote->getShippingAddress();
  326. if ($shippingAddress) {
  327. if ($exportedShippingAddress) {
  328. foreach ($exportedShippingAddress->getExportedKeys() as $key) {
  329. $shippingAddress->setDataUsingMethod($key, $exportedShippingAddress->getData($key));
  330. }
  331. $shippingAddress->setCollectShippingRates(true);
  332. }
  333. // import shipping method
  334. $code = '';
  335. if ($this->_api->getShippingRateCode()) {
  336. if ($code = $this->_matchShippingMethodCode($shippingAddress, $this->_api->getShippingRateCode())) {
  337. // possible bug of double collecting rates :-/
  338. $shippingAddress->setShippingMethod($code)->setCollectShippingRates(true);
  339. }
  340. }
  341. $this->_quote->getPayment()->setAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_SHIPPING_METHOD, $code);
  342. }
  343. }
  344. $this->_ignoreAddressValidation();
  345. // import payment info
  346. $payment = $this->_quote->getPayment();
  347. $payment->setMethod($this->_methodType);
  348. Mage::getSingleton('paypal/info')->importToPayment($this->_api, $payment);
  349. $payment->setAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_PAYER_ID, $this->_api->getPayerId())
  350. ->setAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_TOKEN, $token)
  351. ;
  352. $this->_quote->collectTotals()->save();
  353. }
  354. /**
  355. * Check whether order review has enough data to initialize
  356. *
  357. * @param $token
  358. * @throws Mage_Core_Exception
  359. */
  360. public function prepareOrderReview($token = null)
  361. {
  362. $payment = $this->_quote->getPayment();
  363. if (!$payment || !$payment->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_PAYER_ID)) {
  364. Mage::throwException(Mage::helper('paypal')->__('Payer is not identified.'));
  365. }
  366. $this->_quote->setMayEditShippingAddress(
  367. 1 != $this->_quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_SHIPPING_OVERRIDEN)
  368. );
  369. $this->_quote->setMayEditShippingMethod(
  370. '' == $this->_quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_SHIPPING_METHOD)
  371. );
  372. $this->_ignoreAddressValidation();
  373. $this->_quote->collectTotals()->save();
  374. }
  375. /**
  376. * Return callback response with shipping options
  377. *
  378. * @param array $request
  379. * @return string
  380. */
  381. public function getShippingOptionsCallbackResponse(array $request)
  382. {
  383. // prepare debug data
  384. $logger = Mage::getModel('core/log_adapter', 'payment_' . $this->_methodType . '.log');
  385. $debugData = array('request' => $request, 'response' => array());
  386. try {
  387. // obtain addresses
  388. $this->_getApi();
  389. $address = $this->_api->prepareShippingOptionsCallbackAddress($request);
  390. $quoteAddress = $this->_quote->getShippingAddress();
  391. // compare addresses, calculate shipping rates and prepare response
  392. $options = array();
  393. if ($address && $quoteAddress && !$this->_quote->getIsVirtual()) {
  394. foreach ($address->getExportedKeys() as $key) {
  395. $quoteAddress->setDataUsingMethod($key, $address->getData($key));
  396. }
  397. $quoteAddress->setCollectShippingRates(true)->collectTotals();
  398. $options = $this->_prepareShippingOptions($quoteAddress, false);
  399. }
  400. $response = $this->_api->setShippingOptions($options)->formatShippingOptionsCallback();
  401. // log request and response
  402. $debugData['response'] = $response;
  403. $logger->log($debugData);
  404. return $response;
  405. } catch (Exception $e) {
  406. $logger->log($debugData);
  407. throw $e;
  408. }
  409. }
  410. /**
  411. * Set shipping method to quote, if needed
  412. * @param string $methodCode
  413. */
  414. public function updateShippingMethod($methodCode)
  415. {
  416. if (!$this->_quote->getIsVirtual() && $shippingAddress = $this->_quote->getShippingAddress()) {
  417. if ($methodCode != $shippingAddress->getShippingMethod()) {
  418. $this->_ignoreAddressValidation();
  419. $shippingAddress->setShippingMethod($methodCode)->setCollectShippingRates(true);
  420. $this->_quote->collectTotals()->save();
  421. }
  422. }
  423. }
  424. /**
  425. * Place the order and recurring payment profiles when customer returned from paypal
  426. * Until this moment all quote data must be valid
  427. *
  428. * @param string $token
  429. * @param string $shippingMethodCode
  430. */
  431. public function place($token, $shippingMethodCode = null)
  432. {
  433. if ($shippingMethodCode) {
  434. $this->updateShippingMethod($shippingMethodCode);
  435. }
  436. if (!$this->_quote->getCustomerId()) {
  437. $this->_quote->setCustomerIsGuest(true)
  438. ->setCustomerGroupId(Mage_Customer_Model_Group::NOT_LOGGED_IN_ID)
  439. ->setCustomerEmail($this->_quote->getBillingAddress()->getEmail());
  440. }
  441. $this->_ignoreAddressValidation();
  442. $this->_quote->collectTotals();
  443. $service = Mage::getModel('sales/service_quote', $this->_quote);
  444. $service->submitAll();
  445. $this->_quote->save();
  446. $this->_recurringPaymentProfiles = $service->getRecurringPaymentProfiles();
  447. // TODO: send recurring profile emails
  448. $order = $service->getOrder();
  449. if (!$order) {
  450. return;
  451. }
  452. $this->_billingAgreement = $order->getPayment()->getBillingAgreement();
  453. // commence redirecting to finish payment, if paypal requires it
  454. if ($order->getPayment()->getAdditionalInformation(Mage_Paypal_Model_Express_Checkout::PAYMENT_INFO_TRANSPORT_REDIRECT)) {
  455. $this->_redirectUrl = $this->_config->getExpressCheckoutCompleteUrl($token);
  456. }
  457. switch ($order->getState()) {
  458. // even after placement paypal can disallow to authorize/capture, but will wait until bank transfers money
  459. case Mage_Sales_Model_Order::STATE_PENDING_PAYMENT:
  460. // TODO
  461. break;
  462. // regular placement, when everything is ok
  463. case Mage_Sales_Model_Order::STATE_PROCESSING:
  464. case Mage_Sales_Model_Order::STATE_COMPLETE:
  465. case Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW:
  466. $order->sendNewOrderEmail();
  467. break;
  468. }
  469. $this->_order = $order;
  470. }
  471. /**
  472. * Make sure addresses will be saved without validation errors
  473. */
  474. private function _ignoreAddressValidation()
  475. {
  476. $this->_quote->getBillingAddress()->setShouldIgnoreValidation(true);
  477. if (!$this->_quote->getIsVirtual()) {
  478. $this->_quote->getShippingAddress()->setShouldIgnoreValidation(true);
  479. }
  480. }
  481. /**
  482. * Determine whether redirect somewhere specifically is required
  483. *
  484. * @param string $action
  485. * @return string
  486. */
  487. public function getRedirectUrl()
  488. {
  489. return $this->_redirectUrl;
  490. }
  491. /**
  492. * Return recurring payment profiles
  493. *
  494. * @return array
  495. */
  496. public function getRecurringPaymentProfiles()
  497. {
  498. return $this->_recurringPaymentProfiles;
  499. }
  500. /**
  501. * Get created billing agreement
  502. *
  503. * @return Mage_Sales_Model_Billing_Agreement|null
  504. */
  505. public function getBillingAgreement()
  506. {
  507. return $this->_billingAgreement;
  508. }
  509. /**
  510. * Return order
  511. *
  512. * @return Mage_Sales_Model_Order
  513. */
  514. public function getOrder()
  515. {
  516. return $this->_order;
  517. }
  518. /**
  519. * Set create billing agreement flag to api call
  520. *
  521. * @return Mage_Paypal_Model_Express_Checkout
  522. */
  523. protected function _setBillingAgreementRequest()
  524. {
  525. if (!$this->_customerId || $this->_quote->hasNominalItems()) {
  526. return $this;
  527. }
  528. $isRequested = $this->_isBARequested || $this->_quote->getPayment()
  529. ->getAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT);
  530. if (!($this->_config->allow_ba_signup == Mage_Paypal_Model_Config::EC_BA_SIGNUP_AUTO
  531. || $isRequested && $this->_config->shouldAskToCreateBillingAgreement())) {
  532. return $this;
  533. }
  534. if (!Mage::getModel('sales/billing_agreement')->needToCreateForCustomer($this->_customerId)) {
  535. return $this;
  536. }
  537. $this->_api->setBillingType($this->_api->getBillingAgreementType());
  538. return $this;
  539. }
  540. /**
  541. * @return Mage_Paypal_Model_Api_Nvp
  542. */
  543. protected function _getApi()
  544. {
  545. if (null === $this->_api) {
  546. $this->_api = Mage::getModel($this->_apiType)->setConfigObject($this->_config);
  547. }
  548. return $this->_api;
  549. }
  550. /**
  551. * Attempt to collect address shipping rates and return them for further usage in instant update API
  552. * Returns empty array if it was impossible to obtain any shipping rate
  553. * If there are shipping rates obtained, the method must return one of them as default.
  554. *
  555. * @param Mage_Sales_Model_Quote_Address $address
  556. * @param bool $mayReturnEmpty
  557. * @return array|false
  558. */
  559. protected function _prepareShippingOptions(Mage_Sales_Model_Quote_Address $address, $mayReturnEmpty = false)
  560. {
  561. $options = array(); $i = 0; $iMin = false; $min = false;
  562. $userSelectedOption = null;
  563. foreach ($address->getGroupedAllShippingRates() as $group) {
  564. foreach ($group as $rate) {
  565. $amount = (float)$rate->getPrice();
  566. if ($rate->getErrorMessage()) {
  567. continue;
  568. }
  569. $isDefault = $address->getShippingMethod() === $rate->getCode();
  570. $options[$i] = new Varien_Object(array(
  571. 'is_default' => $isDefault,
  572. 'name' => trim("{$rate->getCarrierTitle()} - {$rate->getMethodTitle()}", ' -'),
  573. 'code' => $rate->getCode(),
  574. 'amount' => $amount,
  575. ));
  576. if ($isDefault) {
  577. $userSelectedOption = $options[$i];
  578. }
  579. if (false === $min || $amount < $min) {
  580. $min = $amount; $iMin = $i;
  581. }
  582. $i++;
  583. }
  584. }
  585. if ($mayReturnEmpty && is_null($userSelectedOption)) {
  586. $options[] = new Varien_Object(array(
  587. 'is_default' => true,
  588. 'name' => Mage::helper('paypal')->__('N/A'),
  589. 'code' => 'no_rate',
  590. 'amount' => 0.00,
  591. ));
  592. } elseif (is_null($userSelectedOption) && isset($options[$iMin])) {
  593. $options[$iMin]->setIsDefault(true);
  594. }
  595. // Magento will transfer only first 10 cheapest shipping options if there are more than 10 available.
  596. if (count($options) > 10) {
  597. usort($options, array(get_class($this),'cmpShippingOptions'));
  598. array_splice($options, 10);
  599. // User selected option will be always included in options list
  600. if (!is_null($userSelectedOption) && !in_array($userSelectedOption, $options)) {
  601. $options[9] = $userSelectedOption;
  602. }
  603. }
  604. return $options;
  605. }
  606. /**
  607. * Compare two shipping options based on their amounts
  608. *
  609. * This function is used as a callback comparison function in shipping options sorting process
  610. * @see self::_prepareShippingOptions()
  611. *
  612. * @param Varien_Object $option1
  613. * @param Varien_Object $option2
  614. * @return integer
  615. */
  616. protected static function cmpShippingOptions(Varien_Object $option1, Varien_Object $option2)
  617. {
  618. if ($option1->getAmount() == $option2->getAmount()) {
  619. return 0;
  620. }
  621. return ($option1->getAmount() < $option2->getAmount()) ? -1 : 1;
  622. }
  623. /**
  624. * Try to find whether the code provided by PayPal corresponds to any of possible shipping rates
  625. * This method was created only because PayPal has issues with returning the selected code.
  626. * 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
  627. * before collecting shipping rates
  628. *
  629. * @param Mage_Sales_Model_Quote_Address $address
  630. * @param string $selectedCode
  631. * @return string
  632. */
  633. protected function _matchShippingMethodCode(Mage_Sales_Model_Quote_Address $address, $selectedCode)
  634. {
  635. $options = $this->_prepareShippingOptions($address, false);
  636. foreach ($options as $option) {
  637. if ($selectedCode === $option['code'] // the proper case as outlined in documentation
  638. || $selectedCode === $option['name'] // workaround: PayPal may return name instead of the code
  639. // workaround: PayPal may concatenate code and name, and return it instead of the code:
  640. || $selectedCode === "{$option['code']} {$option['name']}"
  641. ) {
  642. return $option['code'];
  643. }
  644. }
  645. return '';
  646. }
  647. }