Pro.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  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\AbstractApi;
  8. use Magento\Sales\Api\TransactionRepositoryInterface;
  9. use Magento\Paypal\Model\Info;
  10. /**
  11. * PayPal Website Payments Pro implementation for payment method instances
  12. * This model was created because right now PayPal Direct and PayPal Express payment methods cannot have same abstract
  13. */
  14. class Pro
  15. {
  16. /**
  17. * Possible payment review actions (for FMF only)
  18. */
  19. const PAYMENT_REVIEW_ACCEPT = 'accept';
  20. const PAYMENT_REVIEW_DENY = 'deny';
  21. /**
  22. * Config instance
  23. *
  24. * @var \Magento\Paypal\Model\Config
  25. */
  26. protected $_config;
  27. /**
  28. * API instance
  29. *
  30. * @var \Magento\Paypal\Model\Api\Nvp
  31. */
  32. protected $_api;
  33. /**
  34. * PayPal info object
  35. *
  36. * @var \Magento\Paypal\Model\Info
  37. */
  38. protected $_infoInstance;
  39. /**
  40. * API model type
  41. *
  42. * @var string
  43. */
  44. protected $_apiType = \Magento\Paypal\Model\Api\Nvp::class;
  45. /**
  46. * Config model type
  47. *
  48. * @var string
  49. */
  50. protected $_configType = \Magento\Paypal\Model\Config::class;
  51. /**
  52. * @var \Magento\Paypal\Model\Config\Factory
  53. */
  54. protected $_configFactory;
  55. /**
  56. * @var \Magento\Paypal\Model\Api\Type\Factory
  57. */
  58. protected $_apiFactory;
  59. /**
  60. * @var \Magento\Paypal\Model\InfoFactory
  61. */
  62. protected $_infoFactory;
  63. /**
  64. * @var TransactionRepositoryInterface
  65. */
  66. protected $transactionRepository;
  67. /**
  68. * @param \Magento\Paypal\Model\Config\Factory $configFactory
  69. * @param \Magento\Paypal\Model\Api\Type\Factory $apiFactory
  70. * @param \Magento\Paypal\Model\InfoFactory $infoFactory
  71. * @param TransactionRepositoryInterface $transactionRepository
  72. */
  73. public function __construct(
  74. \Magento\Paypal\Model\Config\Factory $configFactory,
  75. \Magento\Paypal\Model\Api\Type\Factory $apiFactory,
  76. \Magento\Paypal\Model\InfoFactory $infoFactory,
  77. TransactionRepositoryInterface $transactionRepository
  78. ) {
  79. $this->_configFactory = $configFactory;
  80. $this->_apiFactory = $apiFactory;
  81. $this->_infoFactory = $infoFactory;
  82. $this->transactionRepository = $transactionRepository;
  83. }
  84. /**
  85. * Payment method code setter. Also instantiates/updates config
  86. *
  87. * @param string $code
  88. * @param int|null $storeId
  89. * @return $this
  90. */
  91. public function setMethod($code, $storeId = null)
  92. {
  93. if (null === $this->_config) {
  94. $params = [$code];
  95. if (null !== $storeId) {
  96. $params[] = $storeId;
  97. }
  98. $this->_config = $this->_configFactory->create($this->_configType, ['params' => $params]);
  99. } else {
  100. $this->_config->setMethod($code);
  101. if (null !== $storeId) {
  102. $this->_config->setStoreId($storeId);
  103. }
  104. }
  105. return $this;
  106. }
  107. /**
  108. * Config instance setter
  109. *
  110. * @param \Magento\Paypal\Model\Config $instance
  111. * @param int|null $storeId
  112. * @return $this
  113. */
  114. public function setConfig(\Magento\Paypal\Model\Config $instance, $storeId = null)
  115. {
  116. $this->_config = $instance;
  117. if (null !== $storeId) {
  118. $this->_config->setStoreId($storeId);
  119. }
  120. return $this;
  121. }
  122. /**
  123. * Config instance getter
  124. *
  125. * @return \Magento\Paypal\Model\Config
  126. */
  127. public function getConfig()
  128. {
  129. return $this->_config;
  130. }
  131. /**
  132. * API instance getter
  133. * Sets current store id to current config instance and passes it to API
  134. *
  135. * @return \Magento\Paypal\Model\Api\Nvp
  136. */
  137. public function getApi()
  138. {
  139. if (null === $this->_api) {
  140. $this->_api = $this->_apiFactory->create($this->_apiType);
  141. }
  142. $this->_api->setConfigObject($this->_config);
  143. return $this->_api;
  144. }
  145. /**
  146. * Destroy existing NVP Api object
  147. *
  148. * @return $this
  149. */
  150. public function resetApi()
  151. {
  152. $this->_api = null;
  153. return $this;
  154. }
  155. /**
  156. * Instantiate and return info model
  157. *
  158. * @return \Magento\Paypal\Model\Info
  159. */
  160. public function getInfo()
  161. {
  162. if (null === $this->_infoInstance) {
  163. $this->_infoInstance = $this->_infoFactory->create();
  164. }
  165. return $this->_infoInstance;
  166. }
  167. /**
  168. * Transfer transaction/payment information from API instance to order payment
  169. *
  170. * @param \Magento\Framework\DataObject|AbstractApi $from
  171. * @param \Magento\Payment\Model\InfoInterface $to
  172. * @return $this
  173. */
  174. public function importPaymentInfo(\Magento\Framework\DataObject $from, \Magento\Payment\Model\InfoInterface $to)
  175. {
  176. // update PayPal-specific payment information in the payment object
  177. $this->getInfo()->importToPayment($from, $to);
  178. /**
  179. * Detect payment review and/or frauds
  180. * PayPal pro API returns fraud results only in the payment call response
  181. */
  182. if ($from->getDataUsingMethod(\Magento\Paypal\Model\Info::IS_FRAUD)) {
  183. $to->setIsTransactionPending(true);
  184. $to->setIsFraudDetected(true);
  185. } elseif (Info::isPaymentReviewRequired($to)) {
  186. $to->setIsTransactionPending(true);
  187. }
  188. // give generic info about transaction state
  189. if (Info::isPaymentSuccessful($to)) {
  190. $to->setIsTransactionApproved(true);
  191. } elseif (Info::isPaymentFailed($to)) {
  192. $to->setIsTransactionDenied(true);
  193. }
  194. return $this;
  195. }
  196. /**
  197. * Void transaction
  198. *
  199. * @param \Magento\Framework\DataObject $payment
  200. * @return void
  201. * @throws \Magento\Framework\Exception\LocalizedException
  202. */
  203. public function void(\Magento\Framework\DataObject $payment)
  204. {
  205. $authTransactionId = $this->_getParentTransactionId($payment);
  206. if ($authTransactionId) {
  207. $api = $this->getApi();
  208. $api->setPayment($payment)->setAuthorizationId($authTransactionId)->callDoVoid();
  209. $this->importPaymentInfo($api, $payment);
  210. } else {
  211. throw new \Magento\Framework\Exception\LocalizedException(
  212. __('You need an authorization transaction to void.')
  213. );
  214. }
  215. }
  216. /**
  217. * Attempt to capture payment
  218. * Will return false if the payment is not supposed to be captured
  219. *
  220. * @param \Magento\Framework\DataObject $payment
  221. * @param float $amount
  222. * @return false|null
  223. */
  224. public function capture(\Magento\Framework\DataObject $payment, $amount)
  225. {
  226. $authTransactionId = $this->_getParentTransactionId($payment);
  227. if (!$authTransactionId) {
  228. return false;
  229. }
  230. $api = $this->getApi()
  231. ->setAuthorizationId($authTransactionId)
  232. ->setIsCaptureComplete($payment->isCaptureFinal($amount))
  233. ->setAmount($amount);
  234. $order = $payment->getOrder();
  235. $orderIncrementId = $order->getIncrementId();
  236. $api->setCurrencyCode($order->getBaseCurrencyCode())
  237. ->setInvNum($orderIncrementId)
  238. ->setCustref($orderIncrementId)
  239. ->setPonum($order->getId());
  240. // TODO: pass 'NOTE' to API
  241. $api->callDoCapture();
  242. $this->_importCaptureResultToPayment($api, $payment);
  243. }
  244. /**
  245. * Refund a capture transaction
  246. *
  247. * @param \Magento\Framework\DataObject $payment
  248. * @param float $amount
  249. * @return void
  250. * @throws \Magento\Framework\Exception\LocalizedException
  251. */
  252. public function refund(\Magento\Framework\DataObject $payment, $amount)
  253. {
  254. $captureTxnId = $this->_getParentTransactionId($payment);
  255. if ($captureTxnId) {
  256. $api = $this->getApi();
  257. $order = $payment->getOrder();
  258. $api->setPayment(
  259. $payment
  260. )->setTransactionId(
  261. $captureTxnId
  262. )->setAmount(
  263. $amount
  264. )->setCurrencyCode(
  265. $order->getBaseCurrencyCode()
  266. );
  267. $canRefundMore = $payment->getCreditmemo()->getInvoice()->canRefund();
  268. $isFullRefund = !$canRefundMore &&
  269. 0 == (double)$order->getBaseTotalOnlineRefunded() + (double)$order->getBaseTotalOfflineRefunded();
  270. $api->setRefundType(
  271. $isFullRefund
  272. ? \Magento\Paypal\Model\Config::REFUND_TYPE_FULL
  273. : \Magento\Paypal\Model\Config::REFUND_TYPE_PARTIAL
  274. );
  275. $api->callRefundTransaction();
  276. $this->_importRefundResultToPayment($api, $payment, $canRefundMore);
  277. } else {
  278. throw new \Magento\Framework\Exception\LocalizedException(
  279. __('We can\'t issue a refund transaction because there is no capture transaction.')
  280. );
  281. }
  282. }
  283. /**
  284. * Cancel payment
  285. *
  286. * @param \Magento\Framework\DataObject $payment
  287. * @return void
  288. */
  289. public function cancel(\Magento\Framework\DataObject $payment)
  290. {
  291. if (!$payment->getOrder()->getInvoiceCollection()->count()) {
  292. $this->void($payment);
  293. }
  294. }
  295. /**
  296. * Check whether can do payment review
  297. *
  298. * @param \Magento\Payment\Model\InfoInterface $payment
  299. * @return bool
  300. */
  301. public function canReviewPayment(\Magento\Payment\Model\InfoInterface $payment)
  302. {
  303. $pendingReason = $payment->getAdditionalInformation(\Magento\Paypal\Model\Info::PENDING_REASON_GLOBAL);
  304. return $this->_isPaymentReviewRequired(
  305. $payment
  306. ) && $pendingReason != \Magento\Paypal\Model\Info::PAYMENTSTATUS_REVIEW;
  307. }
  308. /**
  309. * Check whether payment review is required
  310. *
  311. * @param \Magento\Payment\Model\InfoInterface $payment
  312. * @return bool
  313. */
  314. protected function _isPaymentReviewRequired(\Magento\Payment\Model\InfoInterface $payment)
  315. {
  316. return Info::isPaymentReviewRequired($payment);
  317. }
  318. /**
  319. * Perform the payment review
  320. *
  321. * @param \Magento\Payment\Model\InfoInterface $payment
  322. * @param string $action
  323. * @return bool
  324. */
  325. public function reviewPayment(\Magento\Payment\Model\InfoInterface $payment, $action)
  326. {
  327. $api = $this->getApi()->setTransactionId($payment->getLastTransId());
  328. // check whether the review is still needed
  329. $api->callGetTransactionDetails();
  330. $this->importPaymentInfo($api, $payment);
  331. if (!Info::isPaymentReviewRequired($payment)) {
  332. return false;
  333. }
  334. // perform the review action
  335. $api->setAction($action)->callManagePendingTransactionStatus();
  336. $api->callGetTransactionDetails();
  337. $this->importPaymentInfo($api, $payment);
  338. return true;
  339. }
  340. /**
  341. * Fetch transaction details info
  342. *
  343. * @param \Magento\Payment\Model\InfoInterface $payment
  344. * @param string $transactionId
  345. * @return array
  346. */
  347. public function fetchTransactionInfo(\Magento\Payment\Model\InfoInterface $payment, $transactionId)
  348. {
  349. $api = $this->getApi()->setTransactionId($transactionId)->setRawResponseNeeded(true);
  350. $api->callGetTransactionDetails();
  351. $this->importPaymentInfo($api, $payment);
  352. $data = $api->getRawSuccessResponseData();
  353. return $data ? $data : [];
  354. }
  355. /**
  356. * Import capture results to payment
  357. *
  358. * @param \Magento\Paypal\Model\Api\Nvp $api
  359. * @param \Magento\Sales\Model\Order\Payment $payment
  360. * @return void
  361. */
  362. protected function _importCaptureResultToPayment($api, $payment)
  363. {
  364. $payment->setTransactionId($api->getTransactionId())->setIsTransactionClosed(false);
  365. $this->importPaymentInfo($api, $payment);
  366. }
  367. /**
  368. * Import refund results to payment
  369. *
  370. * @param \Magento\Paypal\Model\Api\Nvp $api
  371. * @param \Magento\Sales\Model\Order\Payment $payment
  372. * @param bool $canRefundMore
  373. * @return void
  374. */
  375. protected function _importRefundResultToPayment($api, $payment, $canRefundMore)
  376. {
  377. $payment->setTransactionId(
  378. $api->getRefundTransactionId()
  379. )->setIsTransactionClosed(
  380. 1 // refund initiated by merchant
  381. )->setShouldCloseParentTransaction(
  382. !$canRefundMore
  383. );
  384. $this->importPaymentInfo($api, $payment);
  385. }
  386. /**
  387. * Parent transaction id getter
  388. *
  389. * @param \Magento\Framework\DataObject $payment
  390. * @return string
  391. */
  392. protected function _getParentTransactionId(\Magento\Framework\DataObject $payment)
  393. {
  394. return $payment->getParentTransactionId();
  395. }
  396. }