Authorization.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. <?php
  2. /**
  3. * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License").
  6. * You may not use this file except in compliance with the License.
  7. * A copy of the License is located at
  8. *
  9. * http://aws.amazon.com/apache2.0
  10. *
  11. * or in the "license" file accompanying this file. This file is distributed
  12. * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
  13. * express or implied. See the License for the specific language governing
  14. * permissions and limitations under the License.
  15. */
  16. namespace Amazon\Payment\Model\PaymentManagement;
  17. use Amazon\Core\Client\ClientFactoryInterface;
  18. use Amazon\Payment\Api\Data\PendingAuthorizationInterface;
  19. use Amazon\Payment\Api\Data\PendingAuthorizationInterfaceFactory;
  20. use Amazon\Payment\Model\PaymentManagement;
  21. use Amazon\Payment\Domain\AmazonAuthorizationDetailsResponseFactory;
  22. use Amazon\Payment\Domain\AmazonGetOrderDetailsResponseFactory;
  23. use Amazon\Payment\Domain\AmazonOrderStatus;
  24. use Amazon\Payment\Domain\Details\AmazonAuthorizationDetails;
  25. use Amazon\Payment\Domain\Details\AmazonOrderDetails;
  26. use Amazon\Payment\Domain\Validator\AmazonAuthorization;
  27. use Amazon\Payment\Exception\SoftDeclineException;
  28. use Exception;
  29. use Magento\Backend\Model\UrlInterface;
  30. use Magento\Framework\Api\SearchCriteriaBuilderFactory;
  31. use Magento\Framework\Event\ManagerInterface;
  32. use Magento\Framework\Notification\NotifierInterface;
  33. use Magento\Sales\Api\Data\OrderInterface;
  34. use Magento\Sales\Api\Data\OrderPaymentInterface;
  35. use Magento\Sales\Api\Data\TransactionInterface;
  36. use Magento\Sales\Api\InvoiceRepositoryInterface;
  37. use Magento\Sales\Api\OrderPaymentRepositoryInterface;
  38. use Magento\Sales\Api\OrderRepositoryInterface;
  39. use Magento\Sales\Model\Order\Payment\Transaction;
  40. use Magento\Store\Model\StoreManagerInterface;
  41. use Psr\Log\LoggerInterface;
  42. use Amazon\Payment\Exception\TransactionTimeoutException;
  43. /**
  44. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  45. */
  46. class Authorization extends AbstractOperation
  47. {
  48. /**
  49. * @var PendingAuthorizationInterfaceFactory
  50. */
  51. private $pendingAuthorizationFactory;
  52. /**
  53. * @var ClientFactoryInterface
  54. */
  55. private $clientFactory;
  56. /**
  57. * @var AmazonAuthorizationDetailsResponseFactory
  58. */
  59. private $amazonAuthorizationDetailsResponseFactory;
  60. /**
  61. * @var AmazonAuthorization
  62. */
  63. private $amazonAuthorizationValidator;
  64. /**
  65. * @var OrderPaymentRepositoryInterface
  66. */
  67. private $orderPaymentRepository;
  68. /**
  69. * @var OrderRepositoryInterface
  70. */
  71. private $orderRepository;
  72. /**
  73. * @var ManagerInterface
  74. */
  75. private $eventManager;
  76. /**
  77. * @var AmazonGetOrderDetailsResponseFactory
  78. */
  79. private $amazonGetOrderDetailsResponseFactory;
  80. /**
  81. * @var StoreManagerInterface
  82. */
  83. private $storeManager;
  84. /**
  85. * @var PaymentManagement
  86. */
  87. private $paymentManagement;
  88. /**
  89. * @var LoggerInterface
  90. */
  91. private $logger;
  92. /**
  93. * @var bool
  94. */
  95. private $throwExceptions = false;
  96. /**
  97. * Authorization constructor.
  98. *
  99. * @param NotifierInterface $notifier
  100. * @param UrlInterface $urlBuilder
  101. * @param SearchCriteriaBuilderFactory $searchCriteriaBuilderFactory
  102. * @param InvoiceRepositoryInterface $invoiceRepository
  103. * @param ClientFactoryInterface $clientFactory
  104. * @param PendingAuthorizationInterfaceFactory $pendingAuthorizationFactory
  105. * @param AmazonAuthorizationDetailsResponseFactory $amazonAuthorizationDetailsResponseFactory
  106. * @param AmazonAuthorization $amazonAuthorizationValidator
  107. * @param OrderPaymentRepositoryInterface $orderPaymentRepository
  108. * @param OrderRepositoryInterface $orderRepository
  109. * @param ManagerInterface $eventManager
  110. * @param AmazonGetOrderDetailsResponseFactory $amazonGetOrderDetailsResponseFactory
  111. * @param StoreManagerInterface $storeManager
  112. * @param PaymentManagement $paymentManagement
  113. * @param LoggerInterface $logger
  114. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  115. */
  116. public function __construct(
  117. NotifierInterface $notifier,
  118. UrlInterface $urlBuilder,
  119. SearchCriteriaBuilderFactory $searchCriteriaBuilderFactory,
  120. InvoiceRepositoryInterface $invoiceRepository,
  121. ClientFactoryInterface $clientFactory,
  122. PendingAuthorizationInterfaceFactory $pendingAuthorizationFactory,
  123. AmazonAuthorizationDetailsResponseFactory $amazonAuthorizationDetailsResponseFactory,
  124. AmazonAuthorization $amazonAuthorizationValidator,
  125. OrderPaymentRepositoryInterface $orderPaymentRepository,
  126. OrderRepositoryInterface $orderRepository,
  127. ManagerInterface $eventManager,
  128. AmazonGetOrderDetailsResponseFactory $amazonGetOrderDetailsResponseFactory,
  129. StoreManagerInterface $storeManager,
  130. PaymentManagement $paymentManagement,
  131. LoggerInterface $logger
  132. ) {
  133. $this->clientFactory = $clientFactory;
  134. $this->pendingAuthorizationFactory = $pendingAuthorizationFactory;
  135. $this->amazonAuthorizationDetailsResponseFactory = $amazonAuthorizationDetailsResponseFactory;
  136. $this->amazonAuthorizationValidator = $amazonAuthorizationValidator;
  137. $this->orderPaymentRepository = $orderPaymentRepository;
  138. $this->orderRepository = $orderRepository;
  139. $this->eventManager = $eventManager;
  140. $this->amazonGetOrderDetailsResponseFactory = $amazonGetOrderDetailsResponseFactory;
  141. $this->storeManager = $storeManager;
  142. $this->paymentManagement = $paymentManagement;
  143. $this->logger = $logger;
  144. parent::__construct($notifier, $urlBuilder, $searchCriteriaBuilderFactory, $invoiceRepository);
  145. }
  146. /**
  147. * {@inheritdoc}
  148. */
  149. public function setThrowExceptions($throwExceptions)
  150. {
  151. $this->throwExceptions = $throwExceptions;
  152. return $this;
  153. }
  154. /**
  155. * {@inheritdoc}
  156. */
  157. public function updateAuthorization(
  158. $pendingAuthorizationId,
  159. AmazonAuthorizationDetails $authorizationDetails = null,
  160. AmazonOrderDetails $orderDetails = null
  161. ) {
  162. try {
  163. $pendingAuthorization = $this->pendingAuthorizationFactory->create();
  164. $pendingAuthorization->getResource()->beginTransaction();
  165. $pendingAuthorization->setLockOnLoad(true);
  166. $pendingAuthorization->load($pendingAuthorizationId);
  167. if ($pendingAuthorization->getOrderId()) {
  168. if ($pendingAuthorization->isProcessed()) {
  169. $this->processNewAuthorization($pendingAuthorization, $orderDetails);
  170. } else {
  171. $this->processUpdateAuthorization($pendingAuthorization, $authorizationDetails);
  172. }
  173. }
  174. $pendingAuthorization->getResource()->commit();
  175. } catch (Exception $e) {
  176. $this->logger->error($e);
  177. $pendingAuthorization->getResource()->rollBack();
  178. if ($this->throwExceptions) {
  179. throw $e;
  180. }
  181. }
  182. }
  183. /**
  184. * Processes Authorization during cron
  185. *
  186. * @param PendingAuthorizationInterface $pendingAuthorization
  187. * @param AmazonAuthorizationDetails|null $authorizationDetails
  188. * @throws TransactionTimeoutException
  189. * @throws \Magento\Framework\Exception\NoSuchEntityException
  190. */
  191. protected function processUpdateAuthorization(
  192. PendingAuthorizationInterface $pendingAuthorization,
  193. AmazonAuthorizationDetails $authorizationDetails = null
  194. ) {
  195. $order = $this->orderRepository->get($pendingAuthorization->getOrderId());
  196. $payment = $this->orderPaymentRepository->get($pendingAuthorization->getPaymentId());
  197. $order->setPayment($payment);
  198. $order->setData(OrderInterface::PAYMENT, $payment);
  199. $storeId = $order->getStoreId();
  200. $this->storeManager->setCurrentStore($storeId);
  201. $authorizationId = $pendingAuthorization->getAuthorizationId();
  202. if (null === $authorizationDetails) {
  203. $responseParser = $this->clientFactory->create($storeId)->getAuthorizationDetails(
  204. [
  205. 'amazon_authorization_id' => $authorizationId
  206. ]
  207. );
  208. $response = $this->amazonAuthorizationDetailsResponseFactory->create(['response' => $responseParser]);
  209. $authorizationDetails = $response->getDetails();
  210. }
  211. $capture = $authorizationDetails->hasCapture();
  212. $validation = $this->amazonAuthorizationValidator->validate($authorizationDetails);
  213. if (isset($validation['result'])) {
  214. if ($validation['result'] && !$authorizationDetails->isPending()) {
  215. $this->completePendingAuthorization($order, $payment, $pendingAuthorization, $capture);
  216. } else {
  217. if (!$validation['result']) {
  218. switch ($validation['reason']) {
  219. case 'timeout':
  220. throw new TransactionTimeoutException(
  221. __('Amazon authorize invalid state : Transaction timed out.')
  222. );
  223. break;
  224. case 'hard_decline':
  225. $this->hardDeclinePendingAuthorization($order, $payment, $pendingAuthorization, $capture);
  226. break;
  227. case 'soft_decline':
  228. $this->softDeclinePendingAuthorization($order, $payment, $pendingAuthorization, $capture);
  229. break;
  230. }
  231. }
  232. }
  233. }
  234. }
  235. /**
  236. * Updates pending authorization during cron job
  237. *
  238. * @param OrderInterface $order
  239. * @param OrderPaymentInterface $payment
  240. * @param PendingAuthorizationInterface $pendingAuthorization
  241. * @param $capture
  242. * @param TransactionInterface|null $newTransaction
  243. * @throws Exception
  244. * @throws \Magento\Framework\Exception\NoSuchEntityException
  245. */
  246. protected function completePendingAuthorization(
  247. OrderInterface $order,
  248. OrderPaymentInterface $payment,
  249. PendingAuthorizationInterface $pendingAuthorization,
  250. $capture,
  251. TransactionInterface $newTransaction = null
  252. ) {
  253. $transactionId = ($capture) ? $pendingAuthorization->getCaptureId()
  254. : $pendingAuthorization->getAuthorizationId();
  255. $this->setProcessing($order);
  256. if ($capture) {
  257. $invoice = $this->getInvoiceAndSetPaid($transactionId, $order);
  258. if (!$newTransaction) {
  259. $this->paymentManagement->closeTransaction($transactionId, $payment, $order);
  260. } else {
  261. $invoice->setTransactionId($newTransaction->getTxnId());
  262. }
  263. $formattedAmount = $order->getBaseCurrency()->formatTxt($invoice->getBaseGrandTotal());
  264. $message = __('Captured amount of %1 online', $formattedAmount);
  265. $payment->setDataUsingMethod(
  266. 'base_amount_paid_online',
  267. $payment->formatAmount($invoice->getBaseGrandTotal())
  268. );
  269. } else {
  270. $formattedAmount = $order->getBaseCurrency()->formatTxt($payment->getBaseAmountAuthorized());
  271. $message = __('Authorized amount of %1 online', $formattedAmount);
  272. }
  273. $transaction = ($newTransaction) ?: $this->paymentManagement->getTransaction($transactionId, $payment, $order);
  274. $payment->addTransactionCommentsToOrder($transaction, $message);
  275. $pendingAuthorization->delete();
  276. $order->save();
  277. }
  278. /**
  279. * Handles authorization soft decline during cron
  280. *
  281. * @param OrderInterface $order
  282. * @param OrderPaymentInterface $payment
  283. * @param PendingAuthorizationInterface $pendingAuthorization
  284. * @param $capture
  285. * @throws Exception
  286. * @throws \Magento\Framework\Exception\NoSuchEntityException
  287. */
  288. protected function softDeclinePendingAuthorization(
  289. OrderInterface $order,
  290. OrderPaymentInterface $payment,
  291. PendingAuthorizationInterface $pendingAuthorization,
  292. $capture
  293. ) {
  294. $transactionId = ($capture) ? $pendingAuthorization->getCaptureId()
  295. : $pendingAuthorization->getAuthorizationId();
  296. if ($capture) {
  297. $invoice = $this->getInvoice($transactionId, $order);
  298. $this->setPaymentReview($order);
  299. $formattedAmount = $order->getBaseCurrency()->formatTxt($invoice->getBaseGrandTotal());
  300. } else {
  301. $formattedAmount = $order->getBaseCurrency()->formatTxt($payment->getBaseAmountAuthorized());
  302. }
  303. $message = __('Declined amount of %1 online', $formattedAmount);
  304. $transaction = $this->paymentManagement->getTransaction($transactionId, $payment, $order);
  305. $payment->addTransactionCommentsToOrder($transaction, $message);
  306. $this->paymentManagement->closeTransaction($transactionId, $payment, $order);
  307. $pendingAuthorization->setProcessed(true);
  308. $pendingAuthorization->save();
  309. $order->save();
  310. $this->eventManager->dispatch(
  311. 'amazon_payment_pending_authorization_soft_decline_after',
  312. [
  313. 'order' => $order,
  314. 'pendingAuthorization' => $pendingAuthorization,
  315. ]
  316. );
  317. }
  318. /**
  319. * Handles hard decline during cron
  320. * @param OrderInterface $order
  321. * @param OrderPaymentInterface $payment
  322. * @param PendingAuthorizationInterface $pendingAuthorization
  323. * @param $capture
  324. * @throws Exception
  325. * @throws \Magento\Framework\Exception\NoSuchEntityException
  326. */
  327. protected function hardDeclinePendingAuthorization(
  328. OrderInterface $order,
  329. OrderPaymentInterface $payment,
  330. PendingAuthorizationInterface $pendingAuthorization,
  331. $capture
  332. ) {
  333. $transactionId = ($capture) ? $pendingAuthorization->getCaptureId()
  334. : $pendingAuthorization->getAuthorizationId();
  335. if ($capture) {
  336. $invoice = $this->getInvoiceAndSetCancelled($transactionId, $order);
  337. $formattedAmount = $order->getBaseCurrency()->formatTxt($invoice->getBaseGrandTotal());
  338. $this->addCaptureDeclinedNotice($order);
  339. } else {
  340. $formattedAmount = $order->getBaseCurrency()->formatTxt($payment->getBaseAmountAuthorized());
  341. }
  342. $message = __('Declined amount of %1 online', $formattedAmount);
  343. $this->setOnHold($order);
  344. $transaction = $this->paymentManagement->getTransaction($transactionId, $payment, $order);
  345. $payment->addTransactionCommentsToOrder($transaction, $message);
  346. $this->paymentManagement->closeTransaction($transactionId, $payment, $order);
  347. $pendingAuthorization->delete();
  348. $order->save();
  349. $this->eventManager->dispatch(
  350. 'amazon_payment_pending_authorization_hard_decline_after',
  351. [
  352. 'order' => $order,
  353. 'pendingAuthorization' => $pendingAuthorization,
  354. ]
  355. );
  356. }
  357. /**
  358. * Processes new authorization during cron
  359. *
  360. * @param PendingAuthorizationInterface $pendingAuthorization
  361. * @param AmazonOrderDetails|null $orderDetails
  362. * @throws \Magento\Framework\Exception\NoSuchEntityException
  363. */
  364. protected function processNewAuthorization(
  365. PendingAuthorizationInterface $pendingAuthorization,
  366. AmazonOrderDetails $orderDetails = null
  367. ) {
  368. $order = $this->orderRepository->get($pendingAuthorization->getOrderId());
  369. $payment = $this->orderPaymentRepository->get($pendingAuthorization->getPaymentId());
  370. $order->setPayment($payment);
  371. $order->setData(OrderInterface::PAYMENT, $payment);
  372. $storeId = $order->getStoreId();
  373. $this->storeManager->setCurrentStore($storeId);
  374. if (null === $orderDetails) {
  375. $responseParser = $this->clientFactory->create($storeId)->getOrderReferenceDetails(
  376. [
  377. 'amazon_order_reference_id' => $order->getExtensionAttributes()->getAmazonOrderReferenceId()
  378. ]
  379. );
  380. $response = $this->amazonGetOrderDetailsResponseFactory->create(['response' => $responseParser]);
  381. $orderDetails = $response->getDetails();
  382. }
  383. if (AmazonOrderStatus::STATE_OPEN == $orderDetails->getStatus()->getState()) {
  384. $capture = $pendingAuthorization->isCapture();
  385. if ($capture) {
  386. $this->requestNewAuthorizationAndCapture($order, $payment, $pendingAuthorization);
  387. } else {
  388. $this->requestNewAuthorization($order, $payment, $pendingAuthorization);
  389. }
  390. }
  391. }
  392. /**
  393. * Attempts to request new authorization during cron for pending authorization items.
  394. *
  395. * @param OrderInterface $order
  396. * @param OrderPaymentInterface $payment
  397. * @param PendingAuthorizationInterface $pendingAuthorization
  398. * @throws \Magento\Framework\Exception\NoSuchEntityException
  399. */
  400. protected function requestNewAuthorization(
  401. OrderInterface $order,
  402. OrderPaymentInterface $payment,
  403. PendingAuthorizationInterface $pendingAuthorization
  404. ) {
  405. $capture = false;
  406. try {
  407. $baseAmount = $payment->formatAmount($payment->getBaseAmountAuthorized());
  408. $method = $payment->getMethodInstance();
  409. $method->setStore($order->getStoreId());
  410. $method->authorizeInCron($payment, $baseAmount, $capture);
  411. $transaction = $payment->addTransaction(Transaction::TYPE_AUTH);
  412. $this->completePendingAuthorization(
  413. $order,
  414. $payment,
  415. $pendingAuthorization,
  416. $capture,
  417. $transaction
  418. );
  419. } catch (SoftDeclineException $e) {
  420. $this->softDeclinePendingAuthorization($order, $payment, $pendingAuthorization, $capture);
  421. } catch (\Exception $e) {
  422. $this->hardDeclinePendingAuthorization($order, $payment, $pendingAuthorization, $capture);
  423. }
  424. }
  425. /**
  426. * Attempts to authorize and capture a pending transaction during cron.
  427. *
  428. * @param OrderInterface $order
  429. * @param OrderPaymentInterface $payment
  430. * @param PendingAuthorizationInterface $pendingAuthorization
  431. * @throws Exception
  432. * @throws \Magento\Framework\Exception\NoSuchEntityException
  433. */
  434. protected function requestNewAuthorizationAndCapture(
  435. OrderInterface $order,
  436. OrderPaymentInterface $payment,
  437. PendingAuthorizationInterface $pendingAuthorization
  438. ) {
  439. $capture = true;
  440. try {
  441. $invoice = $this->getInvoice($pendingAuthorization->getCaptureId(), $order);
  442. $baseAmount = $payment->formatAmount($invoice->getBaseGrandTotal());
  443. $method = $payment->getMethodInstance();
  444. $method->setStore($order->getStoreId());
  445. $method->authorizeInCron($payment, $baseAmount, $capture);
  446. $transaction = $payment->addTransaction(Transaction::TYPE_CAPTURE, $invoice, true);
  447. $this->completePendingAuthorization(
  448. $order,
  449. $payment,
  450. $pendingAuthorization,
  451. $capture,
  452. $transaction
  453. );
  454. } catch (SoftDeclineException $e) {
  455. $this->softDeclinePendingAuthorization($order, $payment, $pendingAuthorization, $capture);
  456. } catch (\Exception $e) {
  457. $this->hardDeclinePendingAuthorization($order, $payment, $pendingAuthorization, $capture);
  458. }
  459. }
  460. }