Express.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Paypal\Model;
  7. use Magento\Paypal\Model\Api\Nvp;
  8. use Magento\Paypal\Model\Api\ProcessableException as ApiProcessableException;
  9. use Magento\Paypal\Model\Express\Checkout as ExpressCheckout;
  10. use Magento\Quote\Api\Data\PaymentInterface;
  11. use Magento\Sales\Api\Data\OrderPaymentInterface;
  12. use Magento\Sales\Model\Order;
  13. use Magento\Sales\Model\Order\Payment;
  14. use Magento\Sales\Model\Order\Payment\Transaction;
  15. use Magento\Quote\Model\Quote;
  16. use Magento\Store\Model\ScopeInterface;
  17. /**
  18. * PayPal Express Module
  19. * @method \Magento\Quote\Api\Data\PaymentMethodExtensionInterface getExtensionAttributes()
  20. * @SuppressWarnings(PHPMD.TooManyFields)
  21. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  22. */
  23. class Express extends \Magento\Payment\Model\Method\AbstractMethod
  24. {
  25. /**
  26. * @var string
  27. */
  28. protected $_code = \Magento\Paypal\Model\Config::METHOD_WPP_EXPRESS;
  29. /**
  30. * @var string
  31. */
  32. protected $_formBlockType = \Magento\Paypal\Block\Express\Form::class;
  33. /**
  34. * @var string
  35. */
  36. protected $_infoBlockType = \Magento\Paypal\Block\Payment\Info::class;
  37. /**
  38. * Availability option
  39. *
  40. * @var bool
  41. */
  42. protected $_isGateway = true;
  43. /**
  44. * Availability option
  45. *
  46. * @var bool
  47. */
  48. protected $_canOrder = true;
  49. /**
  50. * Availability option
  51. *
  52. * @var bool
  53. */
  54. protected $_canAuthorize = true;
  55. /**
  56. * Availability option
  57. *
  58. * @var bool
  59. */
  60. protected $_canCapture = true;
  61. /**
  62. * Availability option
  63. *
  64. * @var bool
  65. */
  66. protected $_canCapturePartial = true;
  67. /**
  68. * Availability option
  69. *
  70. * @var bool
  71. */
  72. protected $_canRefund = true;
  73. /**
  74. * Availability option
  75. *
  76. * @var bool
  77. */
  78. protected $_canRefundInvoicePartial = true;
  79. /**
  80. * Availability option
  81. *
  82. * @var bool
  83. */
  84. protected $_canVoid = true;
  85. /**
  86. * Availability option
  87. *
  88. * @var bool
  89. */
  90. protected $_canUseInternal = false;
  91. /**
  92. * Availability option
  93. *
  94. * @var bool
  95. */
  96. protected $_canUseCheckout = true;
  97. /**
  98. * Availability option
  99. *
  100. * @var bool
  101. */
  102. protected $_canFetchTransactionInfo = true;
  103. /**
  104. * Availability option
  105. *
  106. * @var bool
  107. */
  108. protected $_canReviewPayment = true;
  109. /**
  110. * Website Payments Pro instance
  111. *
  112. * @var \Magento\Paypal\Model\Pro
  113. */
  114. protected $_pro;
  115. /**
  116. * Payment additional information key for payment action
  117. *
  118. * @var string
  119. */
  120. protected $_isOrderPaymentActionKey = 'is_order_action';
  121. /**
  122. * Payment additional information key for number of used authorizations
  123. *
  124. * @var string
  125. */
  126. protected $_authorizationCountKey = 'authorization_count';
  127. /**
  128. * @var \Magento\Store\Model\StoreManagerInterface
  129. */
  130. protected $_storeManager;
  131. /**
  132. * @var \Magento\Framework\UrlInterface
  133. */
  134. protected $_urlBuilder;
  135. /**
  136. * @var \Magento\Paypal\Model\CartFactory
  137. */
  138. protected $_cartFactory;
  139. /**
  140. * @var \Magento\Checkout\Model\Session
  141. */
  142. protected $_checkoutSession;
  143. /**
  144. * @var \Magento\Framework\Exception\LocalizedExceptionFactory
  145. */
  146. protected $_exception;
  147. /**
  148. * @var \Magento\Sales\Api\TransactionRepositoryInterface
  149. */
  150. protected $transactionRepository;
  151. /**
  152. * @var Transaction\BuilderInterface
  153. */
  154. protected $transactionBuilder;
  155. /**
  156. * @param \Magento\Framework\Model\Context $context
  157. * @param \Magento\Framework\Registry $registry
  158. * @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory
  159. * @param \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory
  160. * @param \Magento\Payment\Helper\Data $paymentData
  161. * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
  162. * @param \Magento\Payment\Model\Method\Logger $logger
  163. * @param ProFactory $proFactory
  164. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  165. * @param \Magento\Framework\UrlInterface $urlBuilder
  166. * @param CartFactory $cartFactory
  167. * @param \Magento\Checkout\Model\Session $checkoutSession
  168. * @param \Magento\Framework\Exception\LocalizedExceptionFactory $exception
  169. * @param \Magento\Sales\Api\TransactionRepositoryInterface $transactionRepository
  170. * @param Transaction\BuilderInterface $transactionBuilder
  171. * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
  172. * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
  173. * @param array $data
  174. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  175. */
  176. public function __construct(
  177. \Magento\Framework\Model\Context $context,
  178. \Magento\Framework\Registry $registry,
  179. \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory,
  180. \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory,
  181. \Magento\Payment\Helper\Data $paymentData,
  182. \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
  183. \Magento\Payment\Model\Method\Logger $logger,
  184. ProFactory $proFactory,
  185. \Magento\Store\Model\StoreManagerInterface $storeManager,
  186. \Magento\Framework\UrlInterface $urlBuilder,
  187. \Magento\Paypal\Model\CartFactory $cartFactory,
  188. \Magento\Checkout\Model\Session $checkoutSession,
  189. \Magento\Framework\Exception\LocalizedExceptionFactory $exception,
  190. \Magento\Sales\Api\TransactionRepositoryInterface $transactionRepository,
  191. \Magento\Sales\Model\Order\Payment\Transaction\BuilderInterface $transactionBuilder,
  192. \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
  193. \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
  194. array $data = []
  195. ) {
  196. parent::__construct(
  197. $context,
  198. $registry,
  199. $extensionFactory,
  200. $customAttributeFactory,
  201. $paymentData,
  202. $scopeConfig,
  203. $logger,
  204. $resource,
  205. $resourceCollection,
  206. $data
  207. );
  208. $this->_storeManager = $storeManager;
  209. $this->_urlBuilder = $urlBuilder;
  210. $this->_cartFactory = $cartFactory;
  211. $this->_checkoutSession = $checkoutSession;
  212. $this->_exception = $exception;
  213. $this->transactionRepository = $transactionRepository;
  214. $this->transactionBuilder = $transactionBuilder;
  215. $proInstance = array_shift($data);
  216. if ($proInstance && $proInstance instanceof \Magento\Paypal\Model\Pro) {
  217. $this->_pro = $proInstance;
  218. } else {
  219. $this->_pro = $proFactory->create();
  220. }
  221. $this->_pro->setMethod($this->_code);
  222. $this->_setApiProcessableErrors();
  223. }
  224. /**
  225. * Set processable error codes to API model
  226. *
  227. * @return \Magento\Paypal\Model\Api\Nvp
  228. */
  229. protected function _setApiProcessableErrors()
  230. {
  231. return $this->_pro->getApi()->setProcessableErrors(
  232. [
  233. ApiProcessableException::API_INTERNAL_ERROR,
  234. ApiProcessableException::API_UNABLE_PROCESS_PAYMENT_ERROR_CODE,
  235. ApiProcessableException::API_DO_EXPRESS_CHECKOUT_FAIL,
  236. ApiProcessableException::API_UNABLE_TRANSACTION_COMPLETE,
  237. ApiProcessableException::API_TRANSACTION_EXPIRED,
  238. ApiProcessableException::API_MAX_PAYMENT_ATTEMPTS_EXCEEDED,
  239. ApiProcessableException::API_COUNTRY_FILTER_DECLINE,
  240. ApiProcessableException::API_MAXIMUM_AMOUNT_FILTER_DECLINE,
  241. ApiProcessableException::API_OTHER_FILTER_DECLINE,
  242. ApiProcessableException::API_ADDRESS_MATCH_FAIL,
  243. ]
  244. );
  245. }
  246. /**
  247. * Store setter
  248. *
  249. * Also updates store ID in config object
  250. *
  251. * @param \Magento\Store\Model\Store|int $store
  252. * @return $this
  253. */
  254. public function setStore($store)
  255. {
  256. $this->setData('store', $store);
  257. if (null === $store) {
  258. $store = $this->_storeManager->getStore()->getId();
  259. }
  260. $this->_pro->getConfig()->setStoreId(is_object($store) ? $store->getId() : $store);
  261. return $this;
  262. }
  263. /**
  264. * Can be used in regular checkout
  265. *
  266. * @return bool
  267. */
  268. public function canUseCheckout()
  269. {
  270. if ($this->_scopeConfig->isSetFlag(
  271. 'payment/hosted_pro/active',
  272. \Magento\Store\Model\ScopeInterface::SCOPE_STORE
  273. ) && !$this->_scopeConfig->isSetFlag(
  274. 'payment/hosted_pro/display_ec',
  275. \Magento\Store\Model\ScopeInterface::SCOPE_STORE
  276. )
  277. ) {
  278. return false;
  279. }
  280. return parent::canUseCheckout();
  281. }
  282. /**
  283. * Whether method is available for specified currency
  284. *
  285. * @param string $currencyCode
  286. * @return bool
  287. */
  288. public function canUseForCurrency($currencyCode)
  289. {
  290. return $this->_pro->getConfig()->isCurrencyCodeSupported($currencyCode);
  291. }
  292. /**
  293. * Payment action getter compatible with payment model
  294. *
  295. * @see \Magento\Sales\Model\Payment::place()
  296. * @return string
  297. */
  298. public function getConfigPaymentAction()
  299. {
  300. return $this->_pro->getConfig()->getPaymentAction();
  301. }
  302. /**
  303. * Check whether payment method can be used
  304. *
  305. * @param \Magento\Quote\Api\Data\CartInterface|Quote|null $quote
  306. * @return bool
  307. */
  308. public function isAvailable(\Magento\Quote\Api\Data\CartInterface $quote = null)
  309. {
  310. return parent::isAvailable($quote) && $this->_pro->getConfig()->isMethodAvailable();
  311. }
  312. /**
  313. * Custom getter for payment configuration
  314. *
  315. * @param string $field
  316. * @param int|null $storeId
  317. * @return mixed
  318. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  319. */
  320. public function getConfigData($field, $storeId = null)
  321. {
  322. if ('order_place_redirect_url' === $field) {
  323. return $this->getOrderPlaceRedirectUrl();
  324. }
  325. return $this->_pro->getConfig()->getValue($field);
  326. }
  327. /**
  328. * Order payment
  329. *
  330. * @param \Magento\Framework\DataObject|\Magento\Payment\Model\InfoInterface|Payment $payment
  331. * @param float $amount
  332. * @return $this
  333. */
  334. public function order(\Magento\Payment\Model\InfoInterface $payment, $amount)
  335. {
  336. $paypalTransactionData = $this->_checkoutSession->getPaypalTransactionData();
  337. if (!is_array($paypalTransactionData)) {
  338. $this->_placeOrder($payment, $amount);
  339. } else {
  340. $this->_importToPayment($this->_pro->getApi()->setData($paypalTransactionData), $payment);
  341. }
  342. $payment->setAdditionalInformation($this->_isOrderPaymentActionKey, true);
  343. if ($payment->getIsFraudDetected()) {
  344. return $this;
  345. }
  346. $payment->getOrder()->setActionFlag(Order::ACTION_FLAG_INVOICE, false);
  347. return $this;
  348. }
  349. /**
  350. * Authorize payment
  351. *
  352. * @param \Magento\Framework\DataObject|\Magento\Payment\Model\InfoInterface|Payment $payment
  353. * @param float $amount
  354. * @return $this
  355. */
  356. public function authorize(\Magento\Payment\Model\InfoInterface $payment, $amount)
  357. {
  358. return $this->_placeOrder($payment, $amount);
  359. }
  360. /**
  361. * Void payment
  362. *
  363. * @param \Magento\Framework\DataObject|\Magento\Payment\Model\InfoInterface|Payment $payment
  364. * @return $this
  365. * @throws \Magento\Framework\Exception\LocalizedException
  366. */
  367. public function void(\Magento\Payment\Model\InfoInterface $payment)
  368. {
  369. //Switching to order transaction if needed
  370. if ($payment->getAdditionalInformation(
  371. $this->_isOrderPaymentActionKey
  372. ) && !$payment->getVoidOnlyAuthorization()
  373. ) {
  374. $orderTransaction = $this->getOrderTransaction($payment);
  375. if ($orderTransaction) {
  376. $payment->setParentTransactionId($orderTransaction->getTxnId());
  377. $payment->setTransactionId($orderTransaction->getTxnId() . '-void');
  378. }
  379. }
  380. $this->_pro->void($payment);
  381. return $this;
  382. }
  383. /**
  384. * Capture payment
  385. *
  386. * @param \Magento\Framework\DataObject|\Magento\Payment\Model\InfoInterface|Payment $payment
  387. * @param float $amount
  388. * @return $this
  389. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  390. * @SuppressWarnings(PHPMD.NPathComplexity)
  391. */
  392. public function capture(\Magento\Payment\Model\InfoInterface $payment, $amount)
  393. {
  394. $authorizationTransaction = $payment->getAuthorizationTransaction();
  395. $authorizationPeriod = abs((int)$this->getConfigData('authorization_honor_period'));
  396. $maxAuthorizationNumber = abs((int)$this->getConfigData('child_authorization_number'));
  397. $order = $payment->getOrder();
  398. $isAuthorizationCreated = false;
  399. if ($payment->getAdditionalInformation($this->_isOrderPaymentActionKey)) {
  400. $voided = false;
  401. if (!$authorizationTransaction->getIsClosed() && $this->_isTransactionExpired(
  402. $authorizationTransaction,
  403. $authorizationPeriod
  404. )
  405. ) {
  406. //Save payment state and configure payment object for voiding
  407. $isCaptureFinal = $payment->getShouldCloseParentTransaction();
  408. $payment->setShouldCloseParentTransaction(false);
  409. $payment->setParentTransactionId($authorizationTransaction->getTxnId());
  410. $payment->unsTransactionId();
  411. $payment->setVoidOnlyAuthorization(true);
  412. $payment->void(new \Magento\Framework\DataObject());
  413. //Revert payment state after voiding
  414. $payment->unsAuthorizationTransaction();
  415. $payment->unsTransactionId();
  416. $payment->setShouldCloseParentTransaction($isCaptureFinal);
  417. $voided = true;
  418. }
  419. if ($authorizationTransaction->getIsClosed() || $voided) {
  420. if ($payment->getAdditionalInformation($this->_authorizationCountKey) > $maxAuthorizationNumber - 1) {
  421. $this->_exception->create(
  422. ['phrase' => __('The maximum number of child authorizations is reached.')]
  423. );
  424. }
  425. $api = $this->_callDoAuthorize($amount, $payment, $authorizationTransaction->getParentTxnId());
  426. //Adding authorization transaction
  427. $this->_pro->importPaymentInfo($api, $payment);
  428. $payment->setTransactionId($api->getTransactionId());
  429. $payment->setParentTransactionId($authorizationTransaction->getParentTxnId());
  430. $payment->setIsTransactionClosed(false);
  431. $formatedPrice = $order->getBaseCurrency()->formatTxt($amount);
  432. if ($payment->getIsTransactionPending()) {
  433. $message = __(
  434. 'We\'ll authorize the amount of %1 as soon as the payment gateway approves it.',
  435. $formatedPrice
  436. );
  437. } else {
  438. $message = __('The authorized amount is %1.', $formatedPrice);
  439. }
  440. $transaction = $this->transactionBuilder->setPayment($payment)
  441. ->setOrder($order)
  442. ->setTransactionId($payment->getTransactionId())
  443. ->setFailSafe(true)
  444. ->build(Transaction::TYPE_AUTH);
  445. $payment->addTransactionCommentsToOrder($transaction, $message);
  446. $payment->setParentTransactionId($api->getTransactionId());
  447. $isAuthorizationCreated = true;
  448. }
  449. //close order transaction if needed
  450. if ($payment->getShouldCloseParentTransaction()) {
  451. $orderTransaction = $this->getOrderTransaction($payment);
  452. if ($orderTransaction) {
  453. $orderTransaction->setIsClosed(true);
  454. $order->addRelatedObject($orderTransaction);
  455. }
  456. }
  457. }
  458. if (false === $this->_pro->capture($payment, $amount)) {
  459. $this->_placeOrder($payment, $amount);
  460. }
  461. if ($isAuthorizationCreated && isset($transaction)) {
  462. $transaction->setIsClosed(true);
  463. }
  464. return $this;
  465. }
  466. /**
  467. * Refund capture
  468. *
  469. * @param \Magento\Framework\DataObject|\Magento\Payment\Model\InfoInterface|Payment $payment
  470. * @param float $amount
  471. * @return $this
  472. * @throws \Magento\Framework\Exception\LocalizedException
  473. */
  474. public function refund(\Magento\Payment\Model\InfoInterface $payment, $amount)
  475. {
  476. $this->_pro->refund($payment, $amount);
  477. return $this;
  478. }
  479. /**
  480. * Cancel payment
  481. *
  482. * @param \Magento\Framework\DataObject|\Magento\Payment\Model\InfoInterface|Payment $payment
  483. * @return $this
  484. */
  485. public function cancel(\Magento\Payment\Model\InfoInterface $payment)
  486. {
  487. $this->void($payment);
  488. return $this;
  489. }
  490. /**
  491. * Whether payment can be reviewed
  492. *
  493. * @return bool
  494. */
  495. public function canReviewPayment()
  496. {
  497. return parent::canReviewPayment() && $this->_pro->canReviewPayment($this->getInfoInstance());
  498. }
  499. /**
  500. * Attempt to accept a pending payment
  501. *
  502. * @param \Magento\Payment\Model\Info|Payment $payment
  503. * @return bool
  504. */
  505. public function acceptPayment(\Magento\Payment\Model\InfoInterface $payment)
  506. {
  507. parent::acceptPayment($payment);
  508. return $this->_pro->reviewPayment($payment, \Magento\Paypal\Model\Pro::PAYMENT_REVIEW_ACCEPT);
  509. }
  510. /**
  511. * Attempt to deny a pending payment
  512. *
  513. * @param \Magento\Payment\Model\InfoInterface|Payment $payment
  514. * @return bool
  515. */
  516. public function denyPayment(\Magento\Payment\Model\InfoInterface $payment)
  517. {
  518. parent::denyPayment($payment);
  519. return $this->_pro->reviewPayment($payment, \Magento\Paypal\Model\Pro::PAYMENT_REVIEW_DENY);
  520. }
  521. /**
  522. * Checkout redirect URL getter for onepage checkout (hardcode)
  523. *
  524. * @see \Magento\Checkout\Controller\Onepage::savePaymentAction()
  525. * @see Quote\Payment::getCheckoutRedirectUrl()
  526. * @return string
  527. */
  528. public function getCheckoutRedirectUrl()
  529. {
  530. return $this->_urlBuilder->getUrl('paypal/express/start');
  531. }
  532. /**
  533. * Fetch transaction details info
  534. *
  535. * @param \Magento\Payment\Model\InfoInterface $payment
  536. * @param string $transactionId
  537. * @return array
  538. */
  539. public function fetchTransactionInfo(\Magento\Payment\Model\InfoInterface $payment, $transactionId)
  540. {
  541. return $this->_pro->fetchTransactionInfo($payment, $transactionId);
  542. }
  543. /**
  544. * Returns api instance
  545. *
  546. * @return Api\Nvp
  547. */
  548. public function getApi()
  549. {
  550. return $this->_pro->getApi();
  551. }
  552. /**
  553. * Assign data to info model instance
  554. *
  555. * @param array|\Magento\Framework\DataObject $data
  556. * @return \Magento\Payment\Model\Info
  557. * @throws \Magento\Framework\Exception\LocalizedException
  558. */
  559. public function assignData(\Magento\Framework\DataObject $data)
  560. {
  561. parent::assignData($data);
  562. $additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA);
  563. if (!is_array($additionalData)) {
  564. return $this;
  565. }
  566. foreach ($additionalData as $key => $value) {
  567. // Skip extension attributes
  568. if ($key === \Magento\Framework\Api\ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY) {
  569. continue;
  570. }
  571. $this->getInfoInstance()->setAdditionalInformation($key, $value);
  572. }
  573. return $this;
  574. }
  575. /**
  576. * Place an order with authorization or capture action
  577. *
  578. * @param Payment $payment
  579. * @param float $amount
  580. * @return $this
  581. */
  582. protected function _placeOrder(Payment $payment, $amount)
  583. {
  584. $order = $payment->getOrder();
  585. // prepare api call
  586. $token = $payment->getAdditionalInformation(ExpressCheckout::PAYMENT_INFO_TRANSPORT_TOKEN);
  587. $cart = $this->_cartFactory->create(['salesModel' => $order]);
  588. $api = $this->getApi()->setToken(
  589. $token
  590. )->setPayerId(
  591. $payment->getAdditionalInformation(ExpressCheckout::PAYMENT_INFO_TRANSPORT_PAYER_ID)
  592. )->setAmount(
  593. $amount
  594. )->setPaymentAction(
  595. $this->_pro->getConfig()->getValue('paymentAction')
  596. )->setNotifyUrl(
  597. $this->_urlBuilder->getUrl('paypal/ipn/')
  598. )->setInvNum(
  599. $order->getIncrementId()
  600. )->setCurrencyCode(
  601. $order->getBaseCurrencyCode()
  602. )->setPaypalCart(
  603. $cart
  604. )->setIsLineItemsEnabled(
  605. $this->_pro->getConfig()->getValue('lineItemsEnabled')
  606. );
  607. if ($order->getIsVirtual()) {
  608. $api->setAddress($order->getBillingAddress())->setSuppressShipping(true);
  609. } else {
  610. $api->setAddress($order->getShippingAddress());
  611. $api->setBillingAddress($order->getBillingAddress());
  612. }
  613. // call api and get details from it
  614. $api->callDoExpressCheckoutPayment();
  615. $this->_importToPayment($api, $payment);
  616. return $this;
  617. }
  618. /**
  619. * Import payment info to payment
  620. *
  621. * @param Nvp $api
  622. * @param Payment $payment
  623. * @return void
  624. */
  625. protected function _importToPayment($api, $payment)
  626. {
  627. $payment->setTransactionId(
  628. $api->getTransactionId()
  629. )->setIsTransactionClosed(
  630. 0
  631. )->setAdditionalInformation(
  632. ExpressCheckout::PAYMENT_INFO_TRANSPORT_REDIRECT,
  633. $api->getRedirectRequired()
  634. );
  635. if ($api->getBillingAgreementId()) {
  636. $payment->setBillingAgreementData(
  637. [
  638. 'billing_agreement_id' => $api->getBillingAgreementId(),
  639. 'method_code' => \Magento\Paypal\Model\Config::METHOD_BILLING_AGREEMENT,
  640. ]
  641. );
  642. }
  643. $this->_pro->importPaymentInfo($api, $payment);
  644. }
  645. /**
  646. * Check void availability
  647. *
  648. * @return bool
  649. * @throws \Magento\Framework\Exception\LocalizedException
  650. * @internal param \Magento\Framework\DataObject $payment
  651. */
  652. public function canVoid()
  653. {
  654. $info = $this->getInfoInstance();
  655. if ($info->getAdditionalInformation($this->_isOrderPaymentActionKey)) {
  656. $orderTransaction = $this->getOrderTransaction($info);
  657. if ($orderTransaction) {
  658. $info->setParentTransactionId($orderTransaction->getTxnId());
  659. }
  660. }
  661. return $this->_canVoid;
  662. }
  663. /**
  664. * Check capture availability
  665. *
  666. * @return bool
  667. */
  668. public function canCapture()
  669. {
  670. $payment = $this->getInfoInstance();
  671. $this->_pro->getConfig()->setStoreId($payment->getOrder()->getStore()->getId());
  672. if ($payment->getAdditionalInformation($this->_isOrderPaymentActionKey)) {
  673. $orderTransaction = $this->getOrderTransaction($payment);
  674. if ($orderTransaction->getIsClosed()) {
  675. return false;
  676. }
  677. $orderValidPeriod = abs((int)$this->getConfigData('order_valid_period'));
  678. $dateCompass = new \DateTime($orderTransaction->getCreatedAt());
  679. $dateCompass->modify('+' . $orderValidPeriod . ' days');
  680. $currentDate = new \DateTime();
  681. if ($currentDate > $dateCompass || $orderValidPeriod == 0) {
  682. return false;
  683. }
  684. }
  685. return $this->_canCapture;
  686. }
  687. /**
  688. * Call DoAuthorize
  689. *
  690. * @param int $amount
  691. * @param \Magento\Framework\DataObject $payment
  692. * @param string $parentTransactionId
  693. * @return \Magento\Paypal\Model\Api\AbstractApi
  694. */
  695. protected function _callDoAuthorize($amount, $payment, $parentTransactionId)
  696. {
  697. $apiData = $this->_pro->getApi()->getData();
  698. foreach ($apiData as $k => $v) {
  699. if (is_object($v)) {
  700. unset($apiData[$k]);
  701. }
  702. }
  703. $this->_checkoutSession->setPaypalTransactionData($apiData);
  704. $this->_pro->resetApi();
  705. $api = $this->_setApiProcessableErrors()
  706. ->setAmount($amount)
  707. ->setCurrencyCode($payment->getOrder()->getBaseCurrencyCode())
  708. ->setTransactionId($parentTransactionId)
  709. ->callDoAuthorization();
  710. $payment->setAdditionalInformation(
  711. $this->_authorizationCountKey,
  712. $payment->getAdditionalInformation($this->_authorizationCountKey) + 1
  713. );
  714. return $api;
  715. }
  716. /**
  717. * Check transaction for expiration in PST
  718. *
  719. * @param Transaction $transaction
  720. * @param int $period
  721. * @return bool
  722. */
  723. protected function _isTransactionExpired(Transaction $transaction, $period)
  724. {
  725. $period = (int)$period;
  726. if (0 == $period) {
  727. return true;
  728. }
  729. $transactionClosingDate = new \DateTime($transaction->getCreatedAt(), new \DateTimeZone('GMT'));
  730. $transactionClosingDate->setTimezone(new \DateTimeZone('US/Pacific'));
  731. /**
  732. * 11:49:00 PayPal transactions closing time
  733. */
  734. $transactionClosingDate->setTime(11, 49, 00);
  735. $transactionClosingDate->modify('+' . $period . ' days');
  736. $currentTime = new \DateTime(null, new \DateTimeZone('US/Pacific'));
  737. if ($currentTime > $transactionClosingDate) {
  738. return true;
  739. }
  740. return false;
  741. }
  742. /**
  743. * Is active
  744. *
  745. * @param int|null $storeId
  746. * @return bool
  747. */
  748. public function isActive($storeId = null)
  749. {
  750. $pathStandardExpress = 'payment/' . Config::METHOD_WPS_EXPRESS . '/active';
  751. $pathPaypalExpress = 'payment/' . Config::METHOD_WPP_EXPRESS . '/active';
  752. return parent::isActive($storeId)
  753. || (bool)(int)$this->_scopeConfig->getValue($pathStandardExpress, ScopeInterface::SCOPE_STORE, $storeId)
  754. || (bool)(int)$this->_scopeConfig->getValue($pathPaypalExpress, ScopeInterface::SCOPE_STORE, $storeId);
  755. }
  756. /**
  757. * Get transaction with type order
  758. *
  759. * @param OrderPaymentInterface $payment
  760. * @return false|\Magento\Sales\Api\Data\TransactionInterface
  761. */
  762. protected function getOrderTransaction($payment)
  763. {
  764. return $this->transactionRepository->getByTransactionType(
  765. Transaction::TYPE_ORDER,
  766. $payment->getId(),
  767. $payment->getOrder()->getId()
  768. );
  769. }
  770. }