Directpost.php 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. declare(strict_types=1);
  7. namespace Magento\Authorizenet\Model;
  8. use Magento\Framework\App\ObjectManager;
  9. use Magento\Payment\Model\Method\ConfigInterface;
  10. use Magento\Payment\Model\Method\TransparentInterface;
  11. /**
  12. * Authorize.net DirectPost payment method model.
  13. * @SuppressWarnings(PHPMD.TooManyFields)
  14. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  15. * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
  16. * @deprecated 100.3.1 Authorize.net is removing all support for this payment method
  17. */
  18. class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements TransparentInterface, ConfigInterface
  19. {
  20. const METHOD_CODE = 'authorizenet_directpost';
  21. /**
  22. * @var string
  23. */
  24. protected $_formBlockType = \Magento\Payment\Block\Transparent\Info::class;
  25. /**
  26. * @var string
  27. */
  28. protected $_infoBlockType = \Magento\Authorizenet\Block\Adminhtml\Order\View\Info\PaymentDetails::class;
  29. /**
  30. * Payment Method feature
  31. *
  32. * @var bool
  33. */
  34. protected $_isGateway = true;
  35. /**
  36. * Payment Method feature
  37. *
  38. * @var bool
  39. */
  40. protected $_canAuthorize = true;
  41. /**
  42. * Payment Method feature
  43. *
  44. * @var bool
  45. */
  46. protected $_canCapture = true;
  47. /**
  48. * Payment Method feature
  49. *
  50. * @var bool
  51. */
  52. protected $_canRefund = true;
  53. /**
  54. * Payment Method feature
  55. *
  56. * @var bool
  57. */
  58. protected $_canRefundInvoicePartial = true;
  59. /**
  60. * Payment Method feature
  61. *
  62. * @var bool
  63. */
  64. protected $_canVoid = true;
  65. /**
  66. * Payment Method feature
  67. *
  68. * @var bool
  69. */
  70. protected $_canFetchTransactionInfo = true;
  71. /**
  72. * Payment Method feature
  73. *
  74. * @var bool
  75. */
  76. protected $_isInitializeNeeded = true;
  77. /**
  78. * @var \Magento\Store\Model\StoreManagerInterface
  79. */
  80. protected $storeManager;
  81. /**
  82. * @var \Magento\Quote\Api\CartRepositoryInterface
  83. */
  84. protected $quoteRepository;
  85. /**
  86. * @var \Magento\Authorizenet\Model\Directpost\Response
  87. */
  88. protected $response;
  89. /**
  90. * @var \Magento\Sales\Model\Order\Email\Sender\OrderSender
  91. */
  92. protected $orderSender;
  93. /**
  94. * Order factory
  95. *
  96. * @var \Magento\Sales\Model\OrderFactory
  97. */
  98. protected $orderFactory;
  99. /**
  100. * @var \Magento\Sales\Api\TransactionRepositoryInterface
  101. */
  102. protected $transactionRepository;
  103. /**
  104. * @var \Psr\Log\LoggerInterface
  105. */
  106. private $psrLogger;
  107. /**
  108. * @var \Magento\Sales\Api\PaymentFailuresInterface
  109. */
  110. private $paymentFailures;
  111. /**
  112. * @var \Magento\Sales\Model\Order
  113. */
  114. private $order;
  115. /**
  116. * @param \Magento\Framework\Model\Context $context
  117. * @param \Magento\Framework\Registry $registry
  118. * @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory
  119. * @param \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory
  120. * @param \Magento\Payment\Helper\Data $paymentData
  121. * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
  122. * @param \Magento\Payment\Model\Method\Logger $logger
  123. * @param \Magento\Framework\Module\ModuleListInterface $moduleList
  124. * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
  125. * @param \Magento\Authorizenet\Helper\Data $dataHelper
  126. * @param \Magento\Authorizenet\Model\Directpost\Request\Factory $requestFactory
  127. * @param \Magento\Authorizenet\Model\Directpost\Response\Factory $responseFactory
  128. * @param \Magento\Authorizenet\Model\TransactionService $transactionService
  129. * @param \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory
  130. * @param \Magento\Sales\Model\OrderFactory $orderFactory
  131. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  132. * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository
  133. * @param \Magento\Sales\Model\Order\Email\Sender\OrderSender $orderSender
  134. * @param \Magento\Sales\Api\TransactionRepositoryInterface $transactionRepository
  135. * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
  136. * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
  137. * @param array $data
  138. * @param \Magento\Sales\Api\PaymentFailuresInterface|null $paymentFailures
  139. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  140. */
  141. public function __construct(
  142. \Magento\Framework\Model\Context $context,
  143. \Magento\Framework\Registry $registry,
  144. \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory,
  145. \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory,
  146. \Magento\Payment\Helper\Data $paymentData,
  147. \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
  148. \Magento\Payment\Model\Method\Logger $logger,
  149. \Magento\Framework\Module\ModuleListInterface $moduleList,
  150. \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
  151. \Magento\Authorizenet\Helper\Data $dataHelper,
  152. \Magento\Authorizenet\Model\Directpost\Request\Factory $requestFactory,
  153. \Magento\Authorizenet\Model\Directpost\Response\Factory $responseFactory,
  154. \Magento\Authorizenet\Model\TransactionService $transactionService,
  155. \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory,
  156. \Magento\Sales\Model\OrderFactory $orderFactory,
  157. \Magento\Store\Model\StoreManagerInterface $storeManager,
  158. \Magento\Quote\Api\CartRepositoryInterface $quoteRepository,
  159. \Magento\Sales\Model\Order\Email\Sender\OrderSender $orderSender,
  160. \Magento\Sales\Api\TransactionRepositoryInterface $transactionRepository,
  161. \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
  162. \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
  163. array $data = [],
  164. \Magento\Sales\Api\PaymentFailuresInterface $paymentFailures = null
  165. ) {
  166. $this->orderFactory = $orderFactory;
  167. $this->storeManager = $storeManager;
  168. $this->quoteRepository = $quoteRepository;
  169. $this->response = $responseFactory->create();
  170. $this->orderSender = $orderSender;
  171. $this->transactionRepository = $transactionRepository;
  172. $this->_code = static::METHOD_CODE;
  173. $this->paymentFailures = $paymentFailures ? : ObjectManager::getInstance()
  174. ->get(\Magento\Sales\Api\PaymentFailuresInterface::class);
  175. parent::__construct(
  176. $context,
  177. $registry,
  178. $extensionFactory,
  179. $customAttributeFactory,
  180. $paymentData,
  181. $scopeConfig,
  182. $logger,
  183. $moduleList,
  184. $localeDate,
  185. $dataHelper,
  186. $requestFactory,
  187. $responseFactory,
  188. $transactionService,
  189. $httpClientFactory,
  190. $resource,
  191. $resourceCollection,
  192. $data
  193. );
  194. }
  195. /**
  196. * Set data helper
  197. *
  198. * @param \Magento\Authorizenet\Helper\Data $dataHelper
  199. * @return void
  200. */
  201. public function setDataHelper(\Magento\Authorizenet\Helper\Data $dataHelper)
  202. {
  203. $this->dataHelper = $dataHelper;
  204. }
  205. /**
  206. * Do not validate payment form using server methods
  207. *
  208. * @return bool
  209. */
  210. public function validate()
  211. {
  212. return true;
  213. }
  214. /**
  215. * Send authorize request to gateway
  216. *
  217. * @param \Magento\Framework\DataObject|\Magento\Payment\Model\InfoInterface $payment
  218. * @param float $amount
  219. * @return void
  220. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  221. */
  222. public function authorize(\Magento\Payment\Model\InfoInterface $payment, $amount)
  223. {
  224. $payment->setAdditionalInformation('payment_type', $this->getConfigData('payment_action'));
  225. }
  226. /**
  227. * Send capture request to gateway
  228. *
  229. * @param \Magento\Framework\DataObject|\Magento\Payment\Model\InfoInterface $payment
  230. * @param float $amount
  231. * @return $this
  232. * @throws \Magento\Framework\Exception\LocalizedException
  233. */
  234. public function capture(\Magento\Payment\Model\InfoInterface $payment, $amount)
  235. {
  236. if ($amount <= 0) {
  237. throw new \Magento\Framework\Exception\LocalizedException(__('Invalid amount for capture.'));
  238. }
  239. $payment->setAmount($amount);
  240. if ($payment->getParentTransactionId()) {
  241. $payment->setAnetTransType(self::REQUEST_TYPE_PRIOR_AUTH_CAPTURE);
  242. $payment->setXTransId($this->getRealParentTransactionId($payment));
  243. } else {
  244. $payment->setAnetTransType(self::REQUEST_TYPE_AUTH_CAPTURE);
  245. }
  246. $result = $this->getResponse();
  247. if (empty($result->getData())) {
  248. $request = $this->buildRequest($payment);
  249. $result = $this->postRequest($request);
  250. }
  251. return $this->processCapture($result, $payment);
  252. }
  253. /**
  254. * Process capture request
  255. *
  256. * @param \Magento\Authorizenet\Model\Directpost\Response $result
  257. * @param \Magento\Payment\Model\InfoInterface $payment
  258. * @return $this
  259. * @throws \Magento\Framework\Exception\LocalizedException
  260. */
  261. protected function processCapture($result, $payment)
  262. {
  263. switch ($result->getXResponseCode()) {
  264. case self::RESPONSE_CODE_APPROVED:
  265. case self::RESPONSE_CODE_HELD:
  266. if (in_array(
  267. $result->getXResponseReasonCode(),
  268. [
  269. self::RESPONSE_REASON_CODE_APPROVED,
  270. self::RESPONSE_REASON_CODE_PENDING_REVIEW,
  271. self::RESPONSE_REASON_CODE_PENDING_REVIEW_AUTHORIZED
  272. ]
  273. )
  274. ) {
  275. if (!$payment->getParentTransactionId()
  276. || $result->getXTransId() != $payment->getParentTransactionId()
  277. ) {
  278. $payment->setTransactionId($result->getXTransId());
  279. }
  280. $payment->setIsTransactionClosed(0)
  281. ->setTransactionAdditionalInfo(
  282. self::REAL_TRANSACTION_ID_KEY,
  283. $result->getXTransId()
  284. );
  285. return $this;
  286. }
  287. throw new \Magento\Framework\Exception\LocalizedException(
  288. $this->dataHelper->wrapGatewayError($result->getXResponseReasonText())
  289. );
  290. case self::RESPONSE_CODE_DECLINED:
  291. case self::RESPONSE_CODE_ERROR:
  292. throw new \Magento\Framework\Exception\LocalizedException(
  293. $this->dataHelper->wrapGatewayError($result->getXResponseReasonText())
  294. );
  295. default:
  296. throw new \Magento\Framework\Exception\LocalizedException(__('Payment capturing error.'));
  297. }
  298. }
  299. /**
  300. * Void the payment through gateway
  301. *
  302. * @param \Magento\Framework\DataObject|\Magento\Payment\Model\InfoInterface $payment
  303. * @return $this
  304. * @throws \Magento\Framework\Exception\LocalizedException
  305. */
  306. public function void(\Magento\Payment\Model\InfoInterface $payment)
  307. {
  308. if (!$payment->getParentTransactionId()) {
  309. throw new \Magento\Framework\Exception\LocalizedException(__('Invalid transaction ID.'));
  310. }
  311. $payment->setAnetTransType(self::REQUEST_TYPE_VOID);
  312. $payment->setXTransId($this->getRealParentTransactionId($payment));
  313. $request = $this->buildRequest($payment);
  314. $result = $this->postRequest($request);
  315. switch ($result->getXResponseCode()) {
  316. case self::RESPONSE_CODE_APPROVED:
  317. if ($result->getXResponseReasonCode() == self::RESPONSE_REASON_CODE_APPROVED) {
  318. if ($result->getXTransId() != $payment->getParentTransactionId()) {
  319. $payment->setTransactionId($result->getXTransId());
  320. }
  321. $payment->setIsTransactionClosed(1)
  322. ->setShouldCloseParentTransaction(1)
  323. ->setTransactionAdditionalInfo(self::REAL_TRANSACTION_ID_KEY, $result->getXTransId());
  324. return $this;
  325. }
  326. throw new \Magento\Framework\Exception\LocalizedException(
  327. $this->dataHelper->wrapGatewayError($result->getXResponseReasonText())
  328. );
  329. case self::RESPONSE_CODE_DECLINED:
  330. case self::RESPONSE_CODE_ERROR:
  331. throw new \Magento\Framework\Exception\LocalizedException(
  332. $this->dataHelper->wrapGatewayError($result->getXResponseReasonText())
  333. );
  334. default:
  335. throw new \Magento\Framework\Exception\LocalizedException(__('Payment voiding error.'));
  336. }
  337. }
  338. /**
  339. * Refund the amount need to decode last 4 digits for request.
  340. *
  341. * @param \Magento\Framework\DataObject|\Magento\Payment\Model\InfoInterface $payment
  342. * @param float $amount
  343. * @return $this
  344. * @throws \Exception
  345. */
  346. public function refund(\Magento\Payment\Model\InfoInterface $payment, $amount)
  347. {
  348. $last4 = $payment->getCcLast4();
  349. $payment->setCcLast4($payment->decrypt($last4));
  350. try {
  351. $this->processRefund($payment, $amount);
  352. } catch (\Exception $e) {
  353. $payment->setCcLast4($last4);
  354. throw $e;
  355. }
  356. $payment->setCcLast4($last4);
  357. return $this;
  358. }
  359. /**
  360. * Refund the amount with transaction id
  361. *
  362. * @param \Magento\Framework\DataObject $payment
  363. * @param float $amount
  364. * @return $this
  365. * @throws \Magento\Framework\Exception\LocalizedException
  366. */
  367. protected function processRefund(\Magento\Framework\DataObject $payment, $amount)
  368. {
  369. if ($amount <= 0) {
  370. throw new \Magento\Framework\Exception\LocalizedException(__('Invalid amount for refund.'));
  371. }
  372. if (!$payment->getParentTransactionId()) {
  373. throw new \Magento\Framework\Exception\LocalizedException(__('Invalid transaction ID.'));
  374. }
  375. $payment->setAnetTransType(self::REQUEST_TYPE_CREDIT);
  376. $payment->setAmount($amount);
  377. $payment->setXTransId($this->getRealParentTransactionId($payment));
  378. $request = $this->buildRequest($payment);
  379. $result = $this->postRequest($request);
  380. switch ($result->getXResponseCode()) {
  381. case self::RESPONSE_CODE_APPROVED:
  382. if ($result->getXResponseReasonCode() == self::RESPONSE_REASON_CODE_APPROVED) {
  383. if ($result->getXTransId() != $payment->getParentTransactionId()) {
  384. $payment->setTransactionId($result->getXTransId());
  385. }
  386. $payment->setIsTransactionClosed(true)
  387. ->setTransactionAdditionalInfo(self::REAL_TRANSACTION_ID_KEY, $result->getXTransId());
  388. return $this;
  389. }
  390. throw new \Magento\Framework\Exception\LocalizedException(
  391. $this->dataHelper->wrapGatewayError($result->getXResponseReasonText())
  392. );
  393. case self::RESPONSE_CODE_DECLINED:
  394. case self::RESPONSE_CODE_ERROR:
  395. throw new \Magento\Framework\Exception\LocalizedException(
  396. $this->dataHelper->wrapGatewayError($result->getXResponseReasonText())
  397. );
  398. default:
  399. throw new \Magento\Framework\Exception\LocalizedException(__('Payment refunding error.'));
  400. }
  401. }
  402. /**
  403. * Get CGI url
  404. *
  405. * @return string
  406. */
  407. public function getCgiUrl()
  408. {
  409. $uri = $this->getConfigData('cgi_url');
  410. return $uri ? $uri : self::CGI_URL;
  411. }
  412. /**
  413. * Return URL on which Authorize.net server will return payment result data in hidden request.
  414. *
  415. * @param int $storeId
  416. * @return string
  417. */
  418. public function getRelayUrl($storeId = null)
  419. {
  420. if ($storeId == null && $this->getStore()) {
  421. $storeId = $this->getStore();
  422. }
  423. return $this->dataHelper->getRelayUrl($storeId);
  424. }
  425. /**
  426. * Return response.
  427. *
  428. * @return \Magento\Authorizenet\Model\Directpost\Response
  429. */
  430. public function getResponse()
  431. {
  432. return $this->response;
  433. }
  434. /**
  435. * Instantiate state and set it to state object
  436. *
  437. * @param string $paymentAction
  438. * @param \Magento\Framework\DataObject $stateObject
  439. * @return void
  440. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  441. */
  442. public function initialize($paymentAction, $stateObject)
  443. {
  444. $requestType = null;
  445. switch ($paymentAction) {
  446. case self::ACTION_AUTHORIZE:
  447. $requestType = self::REQUEST_TYPE_AUTH_ONLY;
  448. //intentional
  449. case self::ACTION_AUTHORIZE_CAPTURE:
  450. $requestType = $requestType ?: self::REQUEST_TYPE_AUTH_CAPTURE;
  451. $payment = $this->getInfoInstance();
  452. $order = $payment->getOrder();
  453. $order->setCanSendNewEmailFlag(false);
  454. $payment->setBaseAmountAuthorized($order->getBaseTotalDue());
  455. $payment->setAmountAuthorized($order->getTotalDue());
  456. $payment->setAnetTransType($requestType);
  457. break;
  458. default:
  459. break;
  460. }
  461. }
  462. /**
  463. * Generate request object and fill its fields from Quote or Order object
  464. *
  465. * @param \Magento\Sales\Model\Order $order Quote or order object.
  466. * @return \Magento\Authorizenet\Model\Directpost\Request
  467. */
  468. public function generateRequestFromOrder(\Magento\Sales\Model\Order $order)
  469. {
  470. $request = $this->requestFactory->create()
  471. ->setConstantData($this)
  472. ->setDataFromOrder($order, $this)
  473. ->signRequestData();
  474. $this->_debug(['request' => $request->getData()]);
  475. return $request;
  476. }
  477. /**
  478. * Fill response with data.
  479. *
  480. * @param array $postData
  481. * @return $this
  482. */
  483. public function setResponseData(array $postData)
  484. {
  485. $this->getResponse()->setData($postData);
  486. return $this;
  487. }
  488. /**
  489. * Validate response data. Needed in controllers.
  490. *
  491. * @return bool true in case of validation success.
  492. * @throws \Magento\Framework\Exception\LocalizedException In case of validation error
  493. */
  494. public function validateResponse()
  495. {
  496. $response = $this->getResponse();
  497. $hashConfigKey = !empty($response->getData('x_SHA2_Hash')) ? 'signature_key' : 'trans_md5';
  498. //hash check
  499. if (!$response->isValidHash($this->getConfigData($hashConfigKey), $this->getConfigData('login'))
  500. ) {
  501. throw new \Magento\Framework\Exception\LocalizedException(
  502. __('The transaction was declined because the response hash validation failed.')
  503. );
  504. }
  505. return true;
  506. }
  507. /**
  508. * Operate with order using data from $_POST which came from authorize.net by Relay URL.
  509. *
  510. * @param array $responseData data from Authorize.net from $_POST
  511. * @return void
  512. * @throws \Magento\Framework\Exception\LocalizedException In case of validation error or order creation error
  513. */
  514. public function process(array $responseData)
  515. {
  516. $this->_debug(['response' => $responseData]);
  517. $this->setResponseData($responseData);
  518. //check MD5 error or others response errors
  519. //throws exception on false.
  520. $this->validateResponse();
  521. $response = $this->getResponse();
  522. $responseText = $this->dataHelper->wrapGatewayError($response->getXResponseReasonText());
  523. $isError = false;
  524. if ($this->getOrderIncrementId()) {
  525. $order = $this->getOrderFromResponse();
  526. //check payment method
  527. $payment = $order->getPayment();
  528. if (!$payment || $payment->getMethod() != $this->getCode()) {
  529. throw new \Magento\Framework\Exception\LocalizedException(
  530. __('This payment didn\'t work out because we can\'t find this order.')
  531. );
  532. }
  533. if ($order->getId()) {
  534. //operate with order
  535. $this->processOrder($order);
  536. } else {
  537. $isError = true;
  538. }
  539. } else {
  540. $isError = true;
  541. }
  542. if ($isError) {
  543. $responseText = $responseText && !$response->isApproved()
  544. ? $responseText
  545. : __('This payment didn\'t work out because we can\'t find this order.');
  546. throw new \Magento\Framework\Exception\LocalizedException($responseText);
  547. }
  548. }
  549. /**
  550. * Fill payment with credit card data from response from Authorize.net.
  551. *
  552. * @param \Magento\Framework\DataObject $payment
  553. * @return void
  554. */
  555. protected function fillPaymentByResponse(\Magento\Framework\DataObject $payment)
  556. {
  557. $response = $this->getResponse();
  558. $payment->setTransactionId($response->getXTransId())
  559. ->setParentTransactionId(null)
  560. ->setIsTransactionClosed(0)
  561. ->setTransactionAdditionalInfo(self::REAL_TRANSACTION_ID_KEY, $response->getXTransId());
  562. if ($response->getXMethod() == self::REQUEST_METHOD_CC) {
  563. $payment->setCcAvsStatus($response->getXAvsCode())
  564. ->setCcLast4($payment->encrypt(substr($response->getXAccountNumber(), -4)));
  565. }
  566. if ($response->getXResponseCode() == self::RESPONSE_CODE_HELD) {
  567. $payment->setIsTransactionPending(true)
  568. ->setIsFraudDetected(true);
  569. }
  570. $additionalInformationKeys = explode(',', $this->getValue('paymentInfoKeys'));
  571. foreach ($additionalInformationKeys as $paymentInfoKey) {
  572. $paymentInfoValue = $response->getDataByKey($paymentInfoKey);
  573. if ($paymentInfoValue !== null) {
  574. $payment->setAdditionalInformation($paymentInfoKey, $paymentInfoValue);
  575. }
  576. }
  577. }
  578. /**
  579. * Check response code came from Authorize.net.
  580. *
  581. * @return true in case of Approved response
  582. * @throws \Magento\Framework\Exception\LocalizedException In case of Declined or Error response from Authorize.net
  583. */
  584. public function checkResponseCode()
  585. {
  586. switch ($this->getResponse()->getXResponseCode()) {
  587. case self::RESPONSE_CODE_APPROVED:
  588. case self::RESPONSE_CODE_HELD:
  589. return true;
  590. case self::RESPONSE_CODE_DECLINED:
  591. case self::RESPONSE_CODE_ERROR:
  592. $errorMessage = $this->dataHelper->wrapGatewayError($this->getResponse()->getXResponseReasonText());
  593. $order = $this->getOrderFromResponse();
  594. $this->paymentFailures->handle((int)$order->getQuoteId(), (string)$errorMessage);
  595. throw new \Magento\Framework\Exception\LocalizedException($errorMessage);
  596. default:
  597. throw new \Magento\Framework\Exception\LocalizedException(
  598. __('There was a payment authorization error.')
  599. );
  600. }
  601. }
  602. /**
  603. * Check transaction id came from Authorize.net
  604. *
  605. * @return true in case of right transaction id
  606. * @throws \Magento\Framework\Exception\LocalizedException In case of bad transaction id.
  607. */
  608. public function checkTransId()
  609. {
  610. if (!$this->getResponse()->getXTransId()) {
  611. throw new \Magento\Framework\Exception\LocalizedException(
  612. __('Please enter a transaction ID to authorize this payment.')
  613. );
  614. }
  615. return true;
  616. }
  617. /**
  618. * Compare amount with amount from the response from Authorize.net.
  619. *
  620. * @param float $amount
  621. * @return bool
  622. */
  623. protected function matchAmount($amount)
  624. {
  625. return sprintf('%.2F', $amount) == sprintf('%.2F', $this->getResponse()->getXAmount());
  626. }
  627. /**
  628. * Operate with order using information from Authorize.net.
  629. *
  630. * Authorize order or authorize and capture it.
  631. *
  632. * @param \Magento\Sales\Model\Order $order
  633. * @return void
  634. * @throws \Magento\Framework\Exception\LocalizedException
  635. * @throws \Exception
  636. * @SuppressWarnings(PHPMD.NPathComplexity)
  637. */
  638. protected function processOrder(\Magento\Sales\Model\Order $order)
  639. {
  640. try {
  641. $this->checkResponseCode();
  642. $this->checkTransId();
  643. } catch (\Exception $e) {
  644. //decline the order (in case of wrong response code) but don't return money to customer.
  645. $message = $e->getMessage();
  646. $this->declineOrder($order, $message, false);
  647. throw $e;
  648. }
  649. $response = $this->getResponse();
  650. //create transaction. need for void if amount will not match.
  651. $payment = $order->getPayment();
  652. $this->fillPaymentByResponse($payment);
  653. $payment->getMethodInstance()->setIsInitializeNeeded(false);
  654. $payment->getMethodInstance()->setResponseData($response->getData());
  655. $this->processPaymentFraudStatus($payment);
  656. $payment->place();
  657. $this->addStatusComment($payment);
  658. $order->save();
  659. //match amounts. should be equals for authorization.
  660. //decline the order if amount does not match.
  661. if (!$this->matchAmount($payment->getBaseAmountAuthorized())) {
  662. $message = __(
  663. 'Something went wrong: the paid amount doesn\'t match the order amount.'
  664. . ' Please correct this and try again.'
  665. );
  666. $this->declineOrder($order, $message, true);
  667. throw new \Magento\Framework\Exception\LocalizedException($message);
  668. }
  669. try {
  670. if (!$response->hasOrderSendConfirmation() || $response->getOrderSendConfirmation()) {
  671. $this->orderSender->send($order);
  672. }
  673. $quote = $this->quoteRepository->get($order->getQuoteId())->setIsActive(false);
  674. $this->quoteRepository->save($quote);
  675. } catch (\Exception $e) {
  676. // do not cancel order if we couldn't send email
  677. }
  678. }
  679. /**
  680. * Process fraud status
  681. *
  682. * @param \Magento\Sales\Model\Order\Payment $payment
  683. * @return $this
  684. */
  685. protected function processPaymentFraudStatus(\Magento\Sales\Model\Order\Payment $payment)
  686. {
  687. try {
  688. $fraudDetailsResponse = $payment->getMethodInstance()
  689. ->fetchTransactionFraudDetails($this->getResponse()->getXTransId());
  690. $fraudData = $fraudDetailsResponse->getData();
  691. if (empty($fraudData)) {
  692. $payment->setIsFraudDetected(false);
  693. return $this;
  694. }
  695. $fdsFilterAction = (string)$fraudDetailsResponse->getFdsFilterAction();
  696. if ($this->fdsFilterActionIsReportOnly($fdsFilterAction) === false) {
  697. $payment->setIsFraudDetected(true);
  698. }
  699. $payment->setAdditionalInformation('fraud_details', $fraudData);
  700. } catch (\Exception $e) {
  701. //this request is optional
  702. }
  703. return $this;
  704. }
  705. /**
  706. * Add status comment to history
  707. *
  708. * @param \Magento\Sales\Model\Order\Payment $payment
  709. * @return $this
  710. */
  711. protected function addStatusComment(\Magento\Sales\Model\Order\Payment $payment)
  712. {
  713. try {
  714. $transactionId = $this->getResponse()->getXTransId();
  715. $data = $this->transactionService->getTransactionDetails($this, $transactionId);
  716. $transactionStatus = (string)$data->transaction->transactionStatus;
  717. $fdsFilterAction = (string)$data->transaction->FDSFilterAction;
  718. if ($payment->getIsTransactionPending()) {
  719. $message = 'Amount of %1 is pending approval on the gateway.<br/>'
  720. . 'Transaction "%2" status is "%3".<br/>'
  721. . 'Transaction FDS Filter Action is "%4"';
  722. $message = __(
  723. $message,
  724. $payment->getOrder()->getBaseCurrency()->formatTxt($this->getResponse()->getXAmount()),
  725. $transactionId,
  726. $this->dataHelper->getTransactionStatusLabel($transactionStatus),
  727. $this->dataHelper->getFdsFilterActionLabel($fdsFilterAction)
  728. );
  729. $payment->getOrder()->addStatusHistoryComment($message);
  730. }
  731. } catch (\Exception $e) {
  732. $this->getPsrLogger()->critical($e);
  733. //this request is optional
  734. }
  735. return $this;
  736. }
  737. /**
  738. * Register order cancellation. Return money to customer if needed.
  739. *
  740. * @param \Magento\Sales\Model\Order $order
  741. * @param string $message
  742. * @param bool $voidPayment
  743. * @return void
  744. */
  745. protected function declineOrder(\Magento\Sales\Model\Order $order, $message = '', $voidPayment = true)
  746. {
  747. try {
  748. $response = $this->getResponse();
  749. if ($voidPayment
  750. && $response->getXTransId()
  751. && strtoupper($response->getXType()) == self::REQUEST_TYPE_AUTH_ONLY
  752. ) {
  753. $order->getPayment()
  754. ->setTransactionId(null)
  755. ->setParentTransactionId($response->getXTransId())
  756. ->void($response);
  757. }
  758. $order->registerCancellation($message)->save();
  759. $this->_eventManager->dispatch('order_cancel_after', ['order' => $order ]);
  760. } catch (\Exception $e) {
  761. //quiet decline
  762. $this->getPsrLogger()->critical($e);
  763. }
  764. }
  765. /**
  766. * Return additional information`s transaction_id value of parent transaction model
  767. *
  768. * @param \Magento\Sales\Model\Order\Payment $payment
  769. * @return string
  770. */
  771. protected function getRealParentTransactionId($payment)
  772. {
  773. $transaction = $this->transactionRepository->getByTransactionId(
  774. $payment->getParentTransactionId(),
  775. $payment->getId(),
  776. $payment->getOrder()->getId()
  777. );
  778. return $transaction->getAdditionalInformation(self::REAL_TRANSACTION_ID_KEY);
  779. }
  780. /**
  781. * {inheritdoc}
  782. */
  783. public function getConfigInterface()
  784. {
  785. return $this;
  786. }
  787. /**
  788. * Getter for specified value according to set payment method code
  789. *
  790. * @param mixed $key
  791. * @param mixed $storeId
  792. * @return mixed
  793. */
  794. public function getValue($key, $storeId = null)
  795. {
  796. return $this->getConfigData($key, $storeId);
  797. }
  798. /**
  799. * Set initialization requirement state
  800. *
  801. * @param bool $isInitializeNeeded
  802. * @return void
  803. */
  804. public function setIsInitializeNeeded($isInitializeNeeded = true)
  805. {
  806. $this->_isInitializeNeeded = (bool)$isInitializeNeeded;
  807. }
  808. /**
  809. * Get whether it is possible to capture
  810. *
  811. * @return bool
  812. */
  813. public function canCapture()
  814. {
  815. return !$this->isGatewayActionsLocked($this->getInfoInstance());
  816. }
  817. /**
  818. * Fetch transaction details info
  819. *
  820. * Update transaction info if there is one placing transaction only
  821. *
  822. * @param \Magento\Payment\Model\InfoInterface $payment
  823. * @param string $transactionId
  824. * @return array
  825. */
  826. public function fetchTransactionInfo(\Magento\Payment\Model\InfoInterface $payment, $transactionId)
  827. {
  828. $transaction = $this->transactionRepository->getByTransactionId(
  829. $transactionId,
  830. $payment->getId(),
  831. $payment->getOrder()->getId()
  832. );
  833. $response = $this->getTransactionResponse($transactionId);
  834. if ($response->getXResponseCode() == self::RESPONSE_CODE_APPROVED) {
  835. if ($response->getTransactionStatus() == 'voided') {
  836. $payment->setIsTransactionDenied(true);
  837. $payment->setIsTransactionClosed(true);
  838. $transaction->close();
  839. } else {
  840. $transaction->setAdditionalInformation(self::TRANSACTION_FRAUD_STATE_KEY, false);
  841. $payment->setIsTransactionApproved(true);
  842. }
  843. } elseif ($response->getXResponseReasonCode() == self::RESPONSE_REASON_CODE_PENDING_REVIEW_DECLINED) {
  844. $payment->setIsTransactionDenied(true);
  845. }
  846. $this->addStatusCommentOnUpdate($payment, $response, $transactionId);
  847. return $response->getData();
  848. }
  849. /**
  850. * Add status comment on update
  851. *
  852. * @param \Magento\Sales\Model\Order\Payment $payment
  853. * @param \Magento\Framework\DataObject $response
  854. * @param string $transactionId
  855. * @return $this
  856. */
  857. protected function addStatusCommentOnUpdate(
  858. \Magento\Sales\Model\Order\Payment $payment,
  859. \Magento\Framework\DataObject $response,
  860. $transactionId
  861. ) {
  862. if ($payment->getIsTransactionApproved()) {
  863. $message = __(
  864. 'Transaction %1 has been approved. Amount %2. Transaction status is "%3"',
  865. $transactionId,
  866. $payment->getOrder()->getBaseCurrency()->formatTxt($payment->getAmountAuthorized()),
  867. $this->dataHelper->getTransactionStatusLabel($response->getTransactionStatus())
  868. );
  869. $payment->getOrder()->addStatusHistoryComment($message);
  870. } elseif ($payment->getIsTransactionDenied()) {
  871. $message = __(
  872. 'Transaction %1 has been voided/declined. Transaction status is "%2". Amount %3.',
  873. $transactionId,
  874. $this->dataHelper->getTransactionStatusLabel($response->getTransactionStatus()),
  875. $payment->getOrder()->getBaseCurrency()->formatTxt($payment->getAmountAuthorized())
  876. );
  877. $payment->getOrder()->addStatusHistoryComment($message);
  878. }
  879. return $this;
  880. }
  881. /**
  882. * Sets method code
  883. *
  884. * @param string $methodCode
  885. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  886. * @return void
  887. */
  888. public function setMethodCode($methodCode)
  889. {
  890. }
  891. /**
  892. * Sets path pattern
  893. *
  894. * @param string $pathPattern
  895. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  896. * @return void
  897. */
  898. public function setPathPattern($pathPattern)
  899. {
  900. }
  901. /**
  902. * This function returns full transaction details for a specified transaction ID.
  903. *
  904. * @param string $transactionId
  905. * @return \Magento\Framework\DataObject
  906. * @throws \Magento\Framework\Exception\LocalizedException
  907. * @link http://www.authorize.net/support/ReportingGuide_XML.pdf
  908. * @link http://developer.authorize.net/api/transaction_details/
  909. */
  910. protected function getTransactionResponse($transactionId)
  911. {
  912. $responseXmlDocument = $this->transactionService->getTransactionDetails($this, $transactionId);
  913. $response = new \Magento\Framework\DataObject();
  914. $response->setXResponseCode((string)$responseXmlDocument->transaction->responseCode)
  915. ->setXResponseReasonCode((string)$responseXmlDocument->transaction->responseReasonCode)
  916. ->setTransactionStatus((string)$responseXmlDocument->transaction->transactionStatus);
  917. return $response;
  918. }
  919. /**
  920. * Get psr logger.
  921. *
  922. * @return \Psr\Log\LoggerInterface
  923. * @deprecated 100.1.0
  924. */
  925. private function getPsrLogger()
  926. {
  927. if (null === $this->psrLogger) {
  928. $this->psrLogger = ObjectManager::getInstance()
  929. ->get(\Psr\Log\LoggerInterface::class);
  930. }
  931. return $this->psrLogger;
  932. }
  933. /**
  934. * Fetch order by increment id from response.
  935. *
  936. * @return \Magento\Sales\Model\Order
  937. */
  938. private function getOrderFromResponse(): \Magento\Sales\Model\Order
  939. {
  940. if (!$this->order) {
  941. $this->order = $this->orderFactory->create();
  942. if ($incrementId = $this->getOrderIncrementId()) {
  943. $this->order = $this->order->loadByIncrementId($incrementId);
  944. }
  945. }
  946. return $this->order;
  947. }
  948. /**
  949. * Fetch order increment id from response.
  950. *
  951. * @return string
  952. */
  953. private function getOrderIncrementId(): string
  954. {
  955. return $this->getResponse()->getXInvoiceNum();
  956. }
  957. /**
  958. * Checks if filter action is Report Only.
  959. *
  960. * Transactions that trigger this filter are processed as normal,
  961. * but are also reported in the Merchant Interface as triggering this filter.
  962. *
  963. * @param string $fdsFilterAction
  964. * @return bool
  965. */
  966. private function fdsFilterActionIsReportOnly($fdsFilterAction)
  967. {
  968. return $fdsFilterAction === (string)$this->dataHelper->getFdsFilterActionLabel('report');
  969. }
  970. }