Payflowpro.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985
  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\Framework\DataObject;
  8. use Magento\Framework\Exception\LocalizedException;
  9. use Magento\Payment\Helper\Formatter;
  10. use Magento\Payment\Model\InfoInterface;
  11. use Magento\Payment\Model\Method\ConfigInterface;
  12. use Magento\Payment\Model\Method\ConfigInterfaceFactory;
  13. use Magento\Payment\Model\Method\Online\GatewayInterface;
  14. use Magento\Payment\Observer\AbstractDataAssignObserver;
  15. use Magento\Paypal\Model\Payflow\Service\Gateway;
  16. use Magento\Paypal\Model\Payflow\Service\Response\Handler\HandlerInterface;
  17. use Magento\Quote\Model\Quote;
  18. use Magento\Sales\Api\Data\OrderPaymentInterface;
  19. use Magento\Sales\Model\Order;
  20. use Magento\Sales\Model\Order\Payment;
  21. use Magento\Store\Model\ScopeInterface;
  22. /**
  23. * Payflow Pro payment gateway model
  24. * @SuppressWarnings(PHPMD.TooManyFields)
  25. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  26. */
  27. class Payflowpro extends \Magento\Payment\Model\Method\Cc implements GatewayInterface
  28. {
  29. use Formatter;
  30. /**
  31. * Transaction action codes
  32. */
  33. const TRXTYPE_AUTH_ONLY = 'A';
  34. const TRXTYPE_SALE = 'S';
  35. const TRXTYPE_CREDIT = 'C';
  36. const TRXTYPE_DELAYED_CAPTURE = 'D';
  37. const TRXTYPE_DELAYED_VOID = 'V';
  38. const TRXTYPE_DELAYED_VOICE = 'F';
  39. const TRXTYPE_DELAYED_INQUIRY = 'I';
  40. const TRXTYPE_ACCEPT_DENY = 'U';
  41. const UPDATEACTION_APPROVED = 'APPROVE';
  42. const UPDATEACTION_DECLINED_BY_MERCHANT = 'FPS_MERCHANT_DECLINE';
  43. /**
  44. * Tender type codes
  45. */
  46. const TENDER_CC = 'C';
  47. /**
  48. * Gateway request URLs
  49. */
  50. const TRANSACTION_URL = 'https://payflowpro.paypal.com/transaction';
  51. const TRANSACTION_URL_TEST_MODE = 'https://pilot-payflowpro.paypal.com/transaction';
  52. /**#@+
  53. * Response code
  54. */
  55. const RESPONSE_CODE_APPROVED = 0;
  56. const RESPONSE_CODE_INVALID_AMOUNT = 4;
  57. const RESPONSE_CODE_FRAUDSERVICE_FILTER = 126;
  58. const RESPONSE_CODE_DECLINED = 12;
  59. const RESPONSE_CODE_DECLINED_BY_FILTER = 125;
  60. const RESPONSE_CODE_DECLINED_BY_MERCHANT = 128;
  61. const RESPONSE_CODE_CAPTURE_ERROR = 111;
  62. const RESPONSE_CODE_VOID_ERROR = 108;
  63. const PNREF = 'pnref';
  64. /**#@-*/
  65. protected $_responseParamsMappings = [
  66. 'firstname' => 'billtofirstname',
  67. 'lastname' => 'billtolastname',
  68. 'address' => 'billtostreet',
  69. 'city' => 'billtocity',
  70. 'state' => 'billtostate',
  71. 'zip' => 'billtozip',
  72. 'country' => 'billtocountry',
  73. 'phone' => 'billtophone',
  74. 'email' => 'billtoemail',
  75. 'nametoship' => 'shiptofirstname',
  76. 'addresstoship' => 'shiptostreet',
  77. 'citytoship' => 'shiptocity',
  78. 'statetoship' => 'shiptostate',
  79. 'ziptoship' => 'shiptozip',
  80. 'countrytoship' => 'shiptocountry',
  81. 'phonetoship' => 'shiptophone',
  82. 'emailtoship' => 'shiptoemail',
  83. 'faxtoship' => 'shiptofax',
  84. 'method' => 'tender',
  85. 'cscmatch' => 'cvv2match',
  86. 'type' => 'trxtype',
  87. 'cclast4' => 'acct',
  88. 'ccavsstatus' => 'avsdata',
  89. 'amt' => 'amt',
  90. 'transtime' => 'transtime',
  91. 'expdate' => 'expdate',
  92. 'securetoken' => 'securetoken',
  93. 'securetokenid' => 'securetokenid',
  94. 'authcode' => 'authcode',
  95. 'hostcode' => 'hostcode',
  96. 'pnref' => 'pnref',
  97. 'cc_type' => 'cardtype'
  98. ];
  99. /**
  100. * PayPal credit card type map.
  101. * @see https://developer.paypal.com/docs/classic/payflow/integration-guide/#credit-card-transaction-responses
  102. *
  103. * @var array
  104. */
  105. private $ccTypeMap = [
  106. '0' => 'VI',
  107. '1' => 'MC',
  108. '2' => 'DI',
  109. '3' => 'AE',
  110. '4' => 'DN',
  111. '5' => 'JCB'
  112. ];
  113. /**
  114. * Payment method code
  115. *
  116. * @var string
  117. */
  118. protected $_code = \Magento\Paypal\Model\Config::METHOD_PAYFLOWPRO;
  119. /**
  120. * Availability option
  121. *
  122. * @var bool
  123. */
  124. protected $_isGateway = true;
  125. /**
  126. * Availability option
  127. *
  128. * @var bool
  129. */
  130. protected $_canAuthorize = true;
  131. /**
  132. * Availability option
  133. *
  134. * @var bool
  135. */
  136. protected $_canCapture = true;
  137. /**
  138. * Availability option
  139. *
  140. * @var bool
  141. */
  142. protected $_canCapturePartial = true;
  143. /**
  144. * Availability option
  145. *
  146. * @var bool
  147. */
  148. protected $_canRefund = true;
  149. /**
  150. * Availability option
  151. *
  152. * @var bool
  153. */
  154. protected $_canRefundInvoicePartial = true;
  155. /**
  156. * Availability option
  157. *
  158. * @var bool
  159. */
  160. protected $_canVoid = true;
  161. /**
  162. * Availability option
  163. *
  164. * @var bool
  165. */
  166. protected $_canUseInternal = true;
  167. /**
  168. * Availability option
  169. *
  170. * @var bool
  171. */
  172. protected $_canUseCheckout = true;
  173. /**
  174. * Availability option
  175. *
  176. * @var bool
  177. */
  178. protected $_canSaveCc = false;
  179. /**
  180. * Availability option
  181. *
  182. * @var bool
  183. */
  184. protected $_isProxy = false;
  185. /**
  186. * Availability option
  187. *
  188. * @var bool
  189. */
  190. protected $_canFetchTransactionInfo = true;
  191. /**
  192. * Payment Method feature
  193. *
  194. * @var bool
  195. */
  196. protected $_canReviewPayment = true;
  197. /**
  198. * Gateway request timeout
  199. *
  200. * @var int
  201. */
  202. protected $_clientTimeout = 45;
  203. /**
  204. * Fields that should be replaced in debug with '***'
  205. *
  206. * @var string[]
  207. */
  208. protected $_debugReplacePrivateDataKeys = ['user', 'pwd', 'acct', 'expdate', 'cvv2'];
  209. /**
  210. * @var \Magento\Store\Model\StoreManagerInterface
  211. */
  212. protected $storeManager;
  213. /**
  214. * @var ConfigInterfaceFactory
  215. */
  216. protected $configFactory;
  217. /**
  218. * @var ConfigInterface
  219. */
  220. private $config;
  221. /**
  222. * @var Gateway
  223. */
  224. private $gateway;
  225. /**
  226. * @var HandlerInterface
  227. */
  228. private $errorHandler;
  229. /**
  230. * @param \Magento\Framework\Model\Context $context
  231. * @param \Magento\Framework\Registry $registry
  232. * @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory
  233. * @param \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory
  234. * @param \Magento\Payment\Helper\Data $paymentData
  235. * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
  236. * @param \Magento\Payment\Model\Method\Logger $logger
  237. * @param \Magento\Framework\Module\ModuleListInterface $moduleList
  238. * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
  239. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  240. * @param ConfigInterfaceFactory $configFactory
  241. * @param Gateway $gateway
  242. * @param HandlerInterface $errorHandler
  243. * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
  244. * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
  245. * @param array $data
  246. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  247. */
  248. public function __construct(
  249. \Magento\Framework\Model\Context $context,
  250. \Magento\Framework\Registry $registry,
  251. \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory,
  252. \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory,
  253. \Magento\Payment\Helper\Data $paymentData,
  254. \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
  255. \Magento\Payment\Model\Method\Logger $logger,
  256. \Magento\Framework\Module\ModuleListInterface $moduleList,
  257. \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
  258. \Magento\Store\Model\StoreManagerInterface $storeManager,
  259. ConfigInterfaceFactory $configFactory,
  260. Gateway $gateway,
  261. HandlerInterface $errorHandler,
  262. \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
  263. \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
  264. array $data = []
  265. ) {
  266. $this->storeManager = $storeManager;
  267. $this->configFactory = $configFactory;
  268. $this->gateway = $gateway;
  269. parent::__construct(
  270. $context,
  271. $registry,
  272. $extensionFactory,
  273. $customAttributeFactory,
  274. $paymentData,
  275. $scopeConfig,
  276. $logger,
  277. $moduleList,
  278. $localeDate,
  279. $resource,
  280. $resourceCollection,
  281. $data
  282. );
  283. $this->errorHandler = $errorHandler;
  284. }
  285. /**
  286. * Check whether payment method can be used
  287. *
  288. * @param \Magento\Quote\Api\Data\CartInterface|Quote|null $quote
  289. * @return bool
  290. * @throws \Magento\Framework\Exception\LocalizedException
  291. */
  292. public function isAvailable(\Magento\Quote\Api\Data\CartInterface $quote = null)
  293. {
  294. return parent::isAvailable($quote) && $this->getConfig()->isMethodAvailable($this->getCode());
  295. }
  296. /**
  297. * Is active
  298. *
  299. * @param int|null $storeId
  300. * @return bool
  301. */
  302. public function isActive($storeId = null)
  303. {
  304. $pathPayflowPro = 'payment/' . Config::METHOD_PAYFLOWPRO . '/active';
  305. $pathPaymentPro = 'payment/' . Config::METHOD_PAYMENT_PRO . '/active';
  306. return (bool)(int) $this->_scopeConfig->getValue($pathPayflowPro, ScopeInterface::SCOPE_STORE, $storeId)
  307. || (bool)(int) $this->_scopeConfig->getValue($pathPaymentPro, ScopeInterface::SCOPE_STORE, $storeId);
  308. }
  309. /**
  310. * Payment action getter compatible with payment model
  311. *
  312. * @return string
  313. */
  314. public function getConfigPaymentAction()
  315. {
  316. return $this->getConfig()->getPaymentAction();
  317. }
  318. /**
  319. * Authorize payment
  320. *
  321. * @param InfoInterface|Payment|Object $payment
  322. * @param float $amount
  323. * @return $this
  324. * @throws \Magento\Framework\Exception\LocalizedException
  325. * @throws \Magento\Framework\Exception\State\InvalidTransitionException
  326. */
  327. public function authorize(\Magento\Payment\Model\InfoInterface $payment, $amount)
  328. {
  329. $request = $this->_buildPlaceRequest($payment, $amount);
  330. $this->addRequestOrderInfo($request, $payment->getOrder());
  331. $request->setTrxtype(self::TRXTYPE_AUTH_ONLY);
  332. $response = $this->postRequest($request, $this->getConfig());
  333. $this->processErrors($response);
  334. $this->setTransStatus($payment, $response);
  335. return $this;
  336. }
  337. /**
  338. * Get capture amount
  339. *
  340. * @param float $amount
  341. * @return float|int
  342. */
  343. protected function _getCaptureAmount($amount)
  344. {
  345. $infoInstance = $this->getInfoInstance();
  346. $amountToPay = $amount;
  347. $authorizedAmount = $infoInstance->getAmountAuthorized();
  348. return abs($amountToPay - $authorizedAmount) < 0.00001 ? 0 : $amountToPay;
  349. }
  350. /**
  351. * Capture payment
  352. *
  353. * @param InfoInterface|Payment|Object $payment
  354. * @param float $amount
  355. * @return $this
  356. * @throws \Magento\Framework\Exception\LocalizedException
  357. * @throws \Magento\Framework\Exception\State\InvalidTransitionException
  358. */
  359. public function capture(\Magento\Payment\Model\InfoInterface $payment, $amount)
  360. {
  361. if ($payment->getAdditionalInformation(self::PNREF)) {
  362. $request = $this->buildBasicRequest();
  363. $request->setAmt($this->formatPrice($amount));
  364. $request->setTrxtype(self::TRXTYPE_SALE);
  365. $request->setOrigid($payment->getAdditionalInformation(self::PNREF));
  366. $payment->unsAdditionalInformation(self::PNREF);
  367. $request->setData('currency', $payment->getOrder()->getBaseCurrencyCode());
  368. } elseif ($payment->getParentTransactionId()) {
  369. $request = $this->buildBasicRequest();
  370. $request->setOrigid($payment->getParentTransactionId());
  371. $captureAmount = $this->_getCaptureAmount($amount);
  372. if ($captureAmount) {
  373. $request->setAmt($this->formatPrice($captureAmount));
  374. }
  375. $trxType = $this->getInfoInstance()->hasAmountPaid() ? self::TRXTYPE_SALE : self::TRXTYPE_DELAYED_CAPTURE;
  376. $request->setTrxtype($trxType);
  377. } else {
  378. $request = $this->_buildPlaceRequest($payment, $amount);
  379. $request->setTrxtype(self::TRXTYPE_SALE);
  380. }
  381. $this->addRequestOrderInfo($request, $payment->getOrder());
  382. $response = $this->postRequest($request, $this->getConfig());
  383. $this->processErrors($response);
  384. $this->setTransStatus($payment, $response);
  385. return $this;
  386. }
  387. /**
  388. * Void payment
  389. *
  390. * @param InfoInterface|Payment|Object $payment
  391. * @return $this
  392. * @throws \Magento\Framework\Exception\LocalizedException
  393. * @throws \Magento\Framework\Exception\State\InvalidTransitionException
  394. */
  395. public function void(\Magento\Payment\Model\InfoInterface $payment)
  396. {
  397. $request = $this->buildBasicRequest();
  398. $request->setTrxtype(self::TRXTYPE_DELAYED_VOID);
  399. $request->setOrigid($payment->getParentTransactionId());
  400. $response = $this->postRequest($request, $this->getConfig());
  401. $this->processErrors($response);
  402. if ($response->getResultCode() == self::RESPONSE_CODE_APPROVED) {
  403. $payment->setTransactionId(
  404. $response->getPnref()
  405. )->setIsTransactionClosed(
  406. 1
  407. )->setShouldCloseParentTransaction(
  408. 1
  409. );
  410. }
  411. return $this;
  412. }
  413. /**
  414. * Check void availability
  415. *
  416. * @return bool
  417. * @throws \Magento\Framework\Exception\LocalizedException
  418. */
  419. public function canVoid()
  420. {
  421. if ($this->getInfoInstance()->getAmountPaid()) {
  422. $this->_canVoid = false;
  423. }
  424. return $this->_canVoid;
  425. }
  426. /**
  427. * Attempt to void the authorization on cancelling
  428. *
  429. * @param InfoInterface|Object $payment
  430. * @return $this
  431. */
  432. public function cancel(\Magento\Payment\Model\InfoInterface $payment)
  433. {
  434. if (!$payment->getOrder()->getInvoiceCollection()->count()) {
  435. return $this->void($payment);
  436. }
  437. return false;
  438. }
  439. /**
  440. * Refund capture
  441. *
  442. * @param InfoInterface|Payment|Object $payment
  443. * @param float $amount
  444. * @return $this
  445. * @throws \Magento\Framework\Exception\LocalizedException
  446. * @throws \Magento\Framework\Exception\State\InvalidTransitionException
  447. */
  448. public function refund(\Magento\Payment\Model\InfoInterface $payment, $amount)
  449. {
  450. $request = $this->buildBasicRequest();
  451. $request->setTrxtype(self::TRXTYPE_CREDIT);
  452. $request->setOrigid($payment->getParentTransactionId());
  453. $request->setAmt($this->formatPrice($amount));
  454. $response = $this->postRequest($request, $this->getConfig());
  455. $this->processErrors($response);
  456. if ($response->getResultCode() == self::RESPONSE_CODE_APPROVED) {
  457. $payment->setTransactionId($response->getPnref())->setIsTransactionClosed(true);
  458. }
  459. return $this;
  460. }
  461. /**
  462. * Fetch transaction details info
  463. *
  464. * @param InfoInterface $payment
  465. * @param string $transactionId
  466. * @return array
  467. */
  468. public function fetchTransactionInfo(InfoInterface $payment, $transactionId)
  469. {
  470. $response = $this->transactionInquiryRequest($payment, $transactionId);
  471. $this->processErrors($response);
  472. if (!$this->_isTransactionUnderReview($response->getOrigresult())) {
  473. $payment->setTransactionId($response->getOrigpnref())->setIsTransactionClosed(0);
  474. if ($response->getOrigresult() == self::RESPONSE_CODE_APPROVED) {
  475. $payment->setIsTransactionApproved(true);
  476. } elseif ($response->getOrigresult() == self::RESPONSE_CODE_DECLINED_BY_MERCHANT) {
  477. $payment->setIsTransactionDenied(true);
  478. }
  479. }
  480. $rawData = $response->getData();
  481. return $rawData ? $rawData : [];
  482. }
  483. /**
  484. * Check whether the transaction is in payment review status
  485. *
  486. * @param string $status
  487. * @return bool
  488. */
  489. protected static function _isTransactionUnderReview($status)
  490. {
  491. if (in_array($status, [self::RESPONSE_CODE_APPROVED, self::RESPONSE_CODE_DECLINED_BY_MERCHANT])) {
  492. return false;
  493. }
  494. return true;
  495. }
  496. /**
  497. * Get Config instance
  498. *
  499. * @return PayflowConfig
  500. */
  501. public function getConfig()
  502. {
  503. if (!$this->config) {
  504. $storeId = $this->storeManager->getStore($this->getStore())->getId();
  505. $this->config = $this->configFactory->create();
  506. $this->config->setStoreId($storeId);
  507. $this->config->setMethodInstance($this);
  508. $this->config->setMethod($this);
  509. }
  510. return $this->config;
  511. }
  512. /**
  513. * @inheritdoc
  514. */
  515. public function postRequest(DataObject $request, ConfigInterface $config)
  516. {
  517. try {
  518. return $this->gateway->postRequest($request, $config);
  519. } catch (\Zend_Http_Client_Exception $e) {
  520. throw new LocalizedException(
  521. __('Payment Gateway is unreachable at the moment. Please use another payment option.'),
  522. $e
  523. );
  524. }
  525. }
  526. /**
  527. * Return request object with information for 'authorization' or 'sale' action
  528. *
  529. * @param Object|Payment $payment
  530. * @param float $amount
  531. * @return DataObject
  532. */
  533. protected function _buildPlaceRequest(DataObject $payment, $amount)
  534. {
  535. $request = $this->buildBasicRequest();
  536. $request->setAmt($this->formatPrice($amount));
  537. $request->setAcct($payment->getCcNumber());
  538. $request->setExpdate(sprintf('%02d', $payment->getCcExpMonth()) . substr($payment->getCcExpYear(), -2, 2));
  539. $request->setCvv2($payment->getCcCid());
  540. $order = $payment->getOrder();
  541. $request->setCurrency($order->getBaseCurrencyCode());
  542. $request = $this->fillCustomerContacts($order, $request);
  543. return $request;
  544. }
  545. /**
  546. * Return request object with basic information for gateway request
  547. *
  548. * @return DataObject
  549. */
  550. public function buildBasicRequest()
  551. {
  552. $request = new DataObject();
  553. /** @var \Magento\Paypal\Model\PayflowConfig $config */
  554. $config = $this->getConfig();
  555. $request->setUser($this->getConfigData('user'));
  556. $request->setVendor($this->getConfigData('vendor'));
  557. $request->setPartner($this->getConfigData('partner'));
  558. $request->setPwd($this->getConfigData('pwd'));
  559. $request->setVerbosity($this->getConfigData('verbosity'));
  560. $request->setData('BUTTONSOURCE', $config->getBuildNotationCode());
  561. $request->setTender(self::TENDER_CC);
  562. return $request;
  563. }
  564. /**
  565. * If response is failed throw exception
  566. *
  567. * @param DataObject $response
  568. * @return void
  569. * @throws \Magento\Payment\Gateway\Command\CommandException
  570. * @throws \Magento\Framework\Exception\State\InvalidTransitionException
  571. */
  572. public function processErrors(DataObject $response)
  573. {
  574. if ($response->getResultCode() == self::RESPONSE_CODE_VOID_ERROR) {
  575. throw new \Magento\Framework\Exception\State\InvalidTransitionException(
  576. __("The verification transaction can't be voided. ")
  577. );
  578. } elseif ($response->getResultCode() != self::RESPONSE_CODE_APPROVED &&
  579. $response->getResultCode() != self::RESPONSE_CODE_FRAUDSERVICE_FILTER
  580. ) {
  581. throw new \Magento\Payment\Gateway\Command\CommandException(__($response->getRespmsg()));
  582. } elseif ($response->getOrigresult() == self::RESPONSE_CODE_DECLINED_BY_FILTER) {
  583. throw new \Magento\Payment\Gateway\Command\CommandException(__($response->getRespmsg()));
  584. }
  585. }
  586. /**
  587. * Attempt to accept a pending payment
  588. *
  589. * @param InfoInterface $payment
  590. * @return bool
  591. */
  592. public function acceptPayment(InfoInterface $payment)
  593. {
  594. return $this->reviewPayment($payment, self::UPDATEACTION_APPROVED);
  595. }
  596. /**
  597. * Attempt to deny a pending payment
  598. *
  599. * @param InfoInterface $payment
  600. * @return bool
  601. */
  602. public function denyPayment(InfoInterface $payment)
  603. {
  604. return $this->reviewPayment($payment, self::UPDATEACTION_DECLINED_BY_MERCHANT);
  605. }
  606. /**
  607. * Perform the payment review
  608. *
  609. * @param InfoInterface $payment
  610. * @param string $action
  611. * @return bool
  612. */
  613. public function reviewPayment(InfoInterface $payment, $action)
  614. {
  615. $request = $this->buildBasicRequest();
  616. $transactionId = ($payment->getCcTransId()) ? $payment->getCcTransId() : $payment->getLastTransId();
  617. $request->setTrxtype(self::TRXTYPE_ACCEPT_DENY);
  618. $request->setOrigid($transactionId);
  619. $request->setUpdateaction($action);
  620. $response = $this->postRequest($request, $this->getConfig());
  621. $payment->setAdditionalInformation((array)$response->getData());
  622. $this->processErrors($response);
  623. if (!$this->_isTransactionUnderReview($response->getOrigresult())) {
  624. $payment->setTransactionId($response->getOrigpnref())->setIsTransactionClosed(0);
  625. if ($response->getOrigresult() == self::RESPONSE_CODE_APPROVED) {
  626. $payment->setIsTransactionApproved(true);
  627. } elseif ($response->getOrigresult() == self::RESPONSE_CODE_DECLINED_BY_MERCHANT) {
  628. $payment->setIsTransactionDenied(true);
  629. }
  630. }
  631. $rawData = $response->getData();
  632. return ($rawData) ? $rawData : [];
  633. }
  634. /**
  635. * Set billing address
  636. *
  637. * @param DataObject $request
  638. * @param DataObject $billing
  639. *
  640. * @return Object
  641. */
  642. public function setBilling(DataObject $request, $billing)
  643. {
  644. $request->setFirstname(
  645. $billing->getFirstname()
  646. )->setLastname(
  647. $billing->getLastname()
  648. )->setStreet(
  649. implode(' ', $billing->getStreet())
  650. )->setCity(
  651. $billing->getCity()
  652. )->setState(
  653. $billing->getRegionCode()
  654. )->setZip(
  655. $billing->getPostcode()
  656. )->setCountry(
  657. $billing->getCountryId()
  658. );
  659. return $request;
  660. }
  661. /**
  662. * Set shipping address
  663. *
  664. * @param DataObject $request
  665. * @param DataObject $shipping
  666. *
  667. * @return Object
  668. */
  669. public function setShipping($request, $shipping)
  670. {
  671. $request->setShiptofirstname(
  672. $shipping->getFirstname()
  673. )->setShiptolastname(
  674. $shipping->getLastname()
  675. )->setShiptostreet(
  676. implode(' ', $shipping->getStreet())
  677. )->setShiptocity(
  678. $shipping->getCity()
  679. )->setShiptostate(
  680. $shipping->getRegionCode()
  681. )->setShiptozip(
  682. $shipping->getPostcode()
  683. )->setShiptocountry(
  684. $shipping->getCountryId()
  685. );
  686. return $request;
  687. }
  688. /**
  689. * Fill response with data.
  690. *
  691. * @param array $postData
  692. * @param DataObject $response
  693. *
  694. * @return DataObject
  695. */
  696. public function mapGatewayResponse(array $postData, DataObject $response)
  697. {
  698. $response->setData(array_change_key_case($postData));
  699. foreach ($this->_responseParamsMappings as $originKey => $key) {
  700. if ($response->getData($key) !== null) {
  701. $response->setData($originKey, $response->getData($key));
  702. }
  703. }
  704. $response->setData(
  705. 'avsdata',
  706. $this->mapResponseAvsData(
  707. $response->getData('avsaddr'),
  708. $response->getData('avszip')
  709. )
  710. );
  711. $response->setData(
  712. 'name',
  713. $this->mapResponseBillToName(
  714. $response->getData('billtofirstname'),
  715. $response->getData('billtolastname')
  716. )
  717. );
  718. $response->setData(
  719. OrderPaymentInterface::CC_TYPE,
  720. $this->mapResponseCreditCardType(
  721. $response->getData(OrderPaymentInterface::CC_TYPE)
  722. )
  723. );
  724. return $response;
  725. }
  726. /**
  727. * Set transaction status
  728. *
  729. * @param DataObject $payment
  730. * @param DataObject $response
  731. *
  732. * @return Object
  733. * @throws \Magento\Framework\Exception\LocalizedException
  734. */
  735. public function setTransStatus($payment, $response)
  736. {
  737. if ($payment instanceof InfoInterface) {
  738. $this->errorHandler->handle($payment, $response);
  739. }
  740. switch ($response->getResultCode()) {
  741. case self::RESPONSE_CODE_APPROVED:
  742. $payment->setTransactionId($response->getPnref())->setIsTransactionClosed(0);
  743. break;
  744. case self::RESPONSE_CODE_DECLINED_BY_FILTER:
  745. case self::RESPONSE_CODE_FRAUDSERVICE_FILTER:
  746. $payment->setTransactionId($response->getPnref())->setIsTransactionClosed(0);
  747. $payment->setIsTransactionPending(true);
  748. $payment->setIsFraudDetected(true);
  749. break;
  750. case self::RESPONSE_CODE_DECLINED:
  751. throw new \Magento\Framework\Exception\LocalizedException(
  752. __($response->getRespmsg())
  753. );
  754. default:
  755. break;
  756. }
  757. return $payment;
  758. }
  759. /**
  760. * Fill customer contacts
  761. *
  762. * @param DataObject $order
  763. * @param DataObject $request
  764. * @return DataObject
  765. */
  766. public function fillCustomerContacts(DataObject $order, DataObject $request)
  767. {
  768. $billing = $order->getBillingAddress();
  769. if (!empty($billing)) {
  770. $request = $this->setBilling($request, $billing);
  771. $request->setEmail($order->getCustomerEmail());
  772. }
  773. $shipping = $order->getShippingAddress();
  774. if (!empty($shipping)) {
  775. $request = $this->setShipping($request, $shipping);
  776. return $request;
  777. }
  778. return $request;
  779. }
  780. /**
  781. * Add order details to payment request
  782. *
  783. * @param DataObject $request
  784. * @param Order $order
  785. * @return void
  786. */
  787. public function addRequestOrderInfo(DataObject $request, Order $order)
  788. {
  789. $id = $order->getId();
  790. // for auth request order id is not exists yet
  791. if (!empty($id)) {
  792. $request->setPonum($id);
  793. }
  794. $orderIncrementId = $order->getIncrementId();
  795. $request->setCustref($orderIncrementId)
  796. ->setInvnum($orderIncrementId)
  797. ->setData('comment1', $orderIncrementId);
  798. }
  799. /**
  800. * Assign data to info model instance
  801. *
  802. * @param array|DataObject $data
  803. * @return $this
  804. * @throws \Magento\Framework\Exception\LocalizedException
  805. */
  806. public function assignData(DataObject $data)
  807. {
  808. $this->_eventManager->dispatch(
  809. 'payment_method_assign_data_' . $this->getCode(),
  810. [
  811. AbstractDataAssignObserver::METHOD_CODE => $this,
  812. AbstractDataAssignObserver::MODEL_CODE => $this->getInfoInstance(),
  813. AbstractDataAssignObserver::DATA_CODE => $data
  814. ]
  815. );
  816. $this->_eventManager->dispatch(
  817. 'payment_method_assign_data',
  818. [
  819. AbstractDataAssignObserver::METHOD_CODE => $this,
  820. AbstractDataAssignObserver::MODEL_CODE => $this->getInfoInstance(),
  821. AbstractDataAssignObserver::DATA_CODE => $data
  822. ]
  823. );
  824. return $this;
  825. }
  826. /**
  827. * Make a transaction Inquiry Request
  828. *
  829. * @param InfoInterface $payment
  830. * @param string $transactionId
  831. * @return DataObject
  832. * @throws LocalizedException
  833. */
  834. protected function transactionInquiryRequest(InfoInterface $payment, $transactionId)
  835. {
  836. $request = $this->buildBasicRequest();
  837. $request->setTrxtype(self::TRXTYPE_DELAYED_INQUIRY);
  838. $transactionId = $payment->getCcTransId() ? $payment->getCcTransId() : $transactionId;
  839. $request->setOrigid($transactionId);
  840. $response = $this->postRequest($request, $this->getConfig());
  841. return $response;
  842. }
  843. /**
  844. * Maps PayPal `avsdata` field.
  845. *
  846. * @param string|null $avsAddr
  847. * @param string|null $avsZip
  848. * @return string|null
  849. */
  850. private function mapResponseAvsData($avsAddr, $avsZip)
  851. {
  852. return isset($avsAddr, $avsZip) ? $avsAddr . $avsZip : null;
  853. }
  854. /**
  855. * Maps PayPal `name` field.
  856. *
  857. * @param string|null $billToFirstName
  858. * @param string|null $billToLastName
  859. * @return string|null
  860. */
  861. private function mapResponseBillToName($billToFirstName, $billToLastName)
  862. {
  863. return isset($billToFirstName, $billToLastName)
  864. ? implode(' ', [$billToFirstName, $billToLastName])
  865. : null;
  866. }
  867. /**
  868. * Map PayPal transaction response credit card type to Magento values if possible.
  869. *
  870. * @param string|null $ccType
  871. * @return string|null
  872. */
  873. private function mapResponseCreditCardType($ccType)
  874. {
  875. return isset($this->ccTypeMap[$ccType]) ? $this->ccTypeMap[$ccType] : $ccType;
  876. }
  877. }