Payflowlink.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  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\Payment\Model\Method\AbstractMethod;
  8. use Magento\Payment\Model\Method\ConfigInterfaceFactory;
  9. use Magento\Paypal\Model\Payflow\Service\Response\Handler\HandlerInterface;
  10. use Magento\Sales\Api\Data\OrderPaymentInterface;
  11. use Magento\Sales\Model\Order;
  12. use Magento\Sales\Model\Order\Email\Sender\OrderSender;
  13. /**
  14. * Payflow Link payment gateway model
  15. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  16. */
  17. class Payflowlink extends \Magento\Paypal\Model\Payflowpro
  18. {
  19. /**
  20. * Default layout template
  21. */
  22. const LAYOUT_TEMPLATE = 'mobile';
  23. /**
  24. * Controller for callback urls
  25. *
  26. * @var string
  27. */
  28. protected $_callbackController = 'payflow';
  29. /**
  30. * Payment method code
  31. *
  32. * @var string
  33. */
  34. protected $_code = \Magento\Paypal\Model\Config::METHOD_PAYFLOWLINK;
  35. /**
  36. * @var string
  37. */
  38. protected $_formBlockType = \Magento\Paypal\Block\Payflow\Link\Form::class;
  39. /**
  40. * @var string
  41. */
  42. protected $_infoBlockType = \Magento\Paypal\Block\Payment\Info::class;
  43. /**
  44. * Availability option
  45. *
  46. * @var bool
  47. */
  48. protected $_canUseInternal = false;
  49. /**
  50. * Availability option
  51. *
  52. * @var bool
  53. */
  54. protected $_isInitializeNeeded = true;
  55. /**
  56. * Request & response model
  57. *
  58. * @var \Magento\Paypal\Model\Payflow\Request
  59. */
  60. protected $_response;
  61. /**
  62. * Gateway request URL
  63. */
  64. const TRANSACTION_PAYFLOW_URL = 'https://payflowlink.paypal.com/';
  65. /**
  66. * Error message
  67. */
  68. const RESPONSE_ERROR_MSG = 'Payment error. %s was not found.';
  69. /**
  70. * Key for storing secure hash in additional information of payment model
  71. *
  72. * @var string
  73. */
  74. protected $_secureSilentPostHashKey = 'secure_silent_post_hash';
  75. /**
  76. * @var \Magento\Paypal\Model\Payflow\RequestFactory
  77. */
  78. protected $_requestFactory;
  79. /**
  80. * @var \Magento\Quote\Api\CartRepositoryInterface
  81. */
  82. protected $quoteRepository;
  83. /**
  84. * @var \Magento\Sales\Model\OrderFactory
  85. */
  86. protected $_orderFactory;
  87. /**
  88. * @var \Magento\Store\Model\WebsiteFactory
  89. */
  90. protected $_websiteFactory;
  91. /**
  92. * @var OrderSender
  93. */
  94. protected $orderSender;
  95. /**
  96. * @var \Magento\Framework\Math\Random
  97. */
  98. private $mathRandom;
  99. /**
  100. * @param \Magento\Framework\Model\Context $context
  101. * @param \Magento\Framework\Registry $registry
  102. * @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory
  103. * @param \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory
  104. * @param \Magento\Payment\Helper\Data $paymentData
  105. * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
  106. * @param \Magento\Payment\Model\Method\Logger $logger
  107. * @param \Magento\Framework\Module\ModuleListInterface $moduleList
  108. * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
  109. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  110. * @param ConfigInterfaceFactory $configFactory
  111. * @param Payflow\Service\Gateway $gateway
  112. * @param HandlerInterface $errorHandler
  113. * @param \Magento\Framework\Math\Random $mathRandom
  114. * @param Payflow\RequestFactory $requestFactory
  115. * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository
  116. * @param \Magento\Sales\Model\OrderFactory $orderFactory
  117. * @param \Magento\Framework\App\RequestInterface $requestHttp
  118. * @param \Magento\Store\Model\WebsiteFactory $websiteFactory
  119. * @param OrderSender $orderSender
  120. * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
  121. * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
  122. * @param array $data
  123. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  124. */
  125. public function __construct(
  126. \Magento\Framework\Model\Context $context,
  127. \Magento\Framework\Registry $registry,
  128. \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory,
  129. \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory,
  130. \Magento\Payment\Helper\Data $paymentData,
  131. \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
  132. \Magento\Payment\Model\Method\Logger $logger,
  133. \Magento\Framework\Module\ModuleListInterface $moduleList,
  134. \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
  135. \Magento\Store\Model\StoreManagerInterface $storeManager,
  136. ConfigInterfaceFactory $configFactory,
  137. \Magento\Paypal\Model\Payflow\Service\Gateway $gateway,
  138. HandlerInterface $errorHandler,
  139. \Magento\Framework\Math\Random $mathRandom,
  140. \Magento\Paypal\Model\Payflow\RequestFactory $requestFactory,
  141. \Magento\Quote\Api\CartRepositoryInterface $quoteRepository,
  142. \Magento\Sales\Model\OrderFactory $orderFactory,
  143. \Magento\Framework\App\RequestInterface $requestHttp,
  144. \Magento\Store\Model\WebsiteFactory $websiteFactory,
  145. OrderSender $orderSender,
  146. \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
  147. \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
  148. array $data = []
  149. ) {
  150. $this->_requestFactory = $requestFactory;
  151. $this->quoteRepository = $quoteRepository;
  152. $this->_orderFactory = $orderFactory;
  153. $this->_requestHttp = $requestHttp;
  154. $this->_websiteFactory = $websiteFactory;
  155. $this->orderSender = $orderSender;
  156. parent::__construct(
  157. $context,
  158. $registry,
  159. $extensionFactory,
  160. $customAttributeFactory,
  161. $paymentData,
  162. $scopeConfig,
  163. $logger,
  164. $moduleList,
  165. $localeDate,
  166. $storeManager,
  167. $configFactory,
  168. $gateway,
  169. $errorHandler,
  170. $resource,
  171. $resourceCollection,
  172. $data
  173. );
  174. $this->mathRandom = $mathRandom;
  175. }
  176. /**
  177. * Do not validate payment form using server methods
  178. *
  179. * @return true
  180. */
  181. public function validate()
  182. {
  183. return true;
  184. }
  185. /**
  186. * Check whether payment method can be used
  187. *
  188. * @param \Magento\Quote\Api\Data\CartInterface|\Magento\Quote\Model\Quote|null $quote
  189. * @return bool
  190. * @throws \Magento\Framework\Exception\LocalizedException
  191. */
  192. public function isAvailable(\Magento\Quote\Api\Data\CartInterface $quote = null)
  193. {
  194. return AbstractMethod::isAvailable($quote) && $this->getConfig()->isMethodAvailable($this->getCode());
  195. }
  196. /**
  197. * Is active
  198. *
  199. * @param int|null $storeId
  200. * @return bool
  201. */
  202. public function isActive($storeId = null)
  203. {
  204. return (bool)(int)$this->getConfigData('active', $storeId);
  205. }
  206. /**
  207. * Instantiate state and set it to state object
  208. *
  209. * @param string $paymentAction
  210. * @param \Magento\Framework\DataObject $stateObject
  211. * @return void
  212. */
  213. public function initialize($paymentAction, $stateObject)
  214. {
  215. switch ($paymentAction) {
  216. case \Magento\Paypal\Model\Config::PAYMENT_ACTION_AUTH:
  217. case \Magento\Paypal\Model\Config::PAYMENT_ACTION_SALE:
  218. $payment = $this->getInfoInstance();
  219. /** @var Order $order */
  220. $order = $payment->getOrder();
  221. $order->setCanSendNewEmailFlag(false);
  222. $payment->setAmountAuthorized($order->getTotalDue());
  223. $payment->setBaseAmountAuthorized($order->getBaseTotalDue());
  224. $this->_generateSecureSilentPostHash($payment);
  225. $this->setStore($order->getStoreId());
  226. $request = $this->_buildTokenRequest($payment);
  227. $response = $this->postRequest($request, $this->getConfig());
  228. $this->_processTokenErrors($response, $payment);
  229. $order = $payment->getOrder();
  230. $order->setCanSendNewEmailFlag(false);
  231. $stateObject->setState(\Magento\Sales\Model\Order::STATE_PENDING_PAYMENT);
  232. $stateObject->setStatus('pending_payment');
  233. $stateObject->setIsNotified(false);
  234. break;
  235. default:
  236. break;
  237. }
  238. }
  239. /**
  240. * Return response model.
  241. *
  242. * @return \Magento\Paypal\Model\Payflow\Request
  243. */
  244. public function getResponse()
  245. {
  246. if (!$this->_response) {
  247. $this->_response = $this->_requestFactory->create();
  248. }
  249. return $this->_response;
  250. }
  251. /**
  252. * Operate with order using data from $_POST which came from Silent Post Url.
  253. *
  254. * @param array $responseData
  255. * @return void
  256. * @throws \Magento\Framework\Exception\LocalizedException In case of validation error or order creation error
  257. */
  258. public function process($responseData)
  259. {
  260. $debugData = ['response' => $responseData];
  261. $this->_debug($debugData);
  262. $this->mapGatewayResponse($responseData, $this->getResponse());
  263. $order = $this->_getOrderFromResponse();
  264. if ($order) {
  265. $this->_processOrder($order);
  266. }
  267. }
  268. /**
  269. * Operate with order using information from silent post
  270. *
  271. * @param \Magento\Sales\Model\Order $order
  272. * @return void
  273. * @throws \Magento\Framework\Exception\LocalizedException
  274. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  275. * @SuppressWarnings(PHPMD.NPathComplexity)
  276. */
  277. protected function _processOrder(\Magento\Sales\Model\Order $order)
  278. {
  279. $response = $this->getResponse();
  280. $payment = $order->getPayment();
  281. $payment->setTransactionId($response->getPnref())->setIsTransactionClosed(0);
  282. $payment->setCcType($response->getData(OrderPaymentInterface::CC_TYPE));
  283. $canSendNewOrderEmail = true;
  284. if ($response->getResult() == self::RESPONSE_CODE_FRAUDSERVICE_FILTER ||
  285. $response->getResult() == self::RESPONSE_CODE_DECLINED_BY_FILTER
  286. ) {
  287. $canSendNewOrderEmail = false;
  288. $payment->setIsTransactionPending(true)
  289. ->setIsFraudDetected(true);
  290. $fraudMessage = $response->getData('respmsg');
  291. if ($response->getData('fps_prexmldata')) {
  292. $xml = new \SimpleXMLElement($response->getData('fps_prexmldata'));
  293. $fraudMessage = (string)$xml->rule->triggeredMessage;
  294. }
  295. $payment->setAdditionalInformation(
  296. Info::PAYPAL_FRAUD_FILTERS,
  297. $fraudMessage
  298. );
  299. }
  300. if ($response->getData('avsdata') && strstr(substr($response->getData('avsdata'), 0, 2), 'N')) {
  301. $payment->setAdditionalInformation(Info::PAYPAL_AVS_CODE, substr($response->getData('avsdata'), 0, 2));
  302. }
  303. if ($response->getData('cvv2match') && $response->getData('cvv2match') != 'Y') {
  304. $payment->setAdditionalInformation(Info::PAYPAL_CVV_2_MATCH, $response->getData('cvv2match'));
  305. }
  306. switch ($response->getType()) {
  307. case self::TRXTYPE_AUTH_ONLY:
  308. $payment->registerAuthorizationNotification($payment->getBaseAmountAuthorized());
  309. break;
  310. case self::TRXTYPE_SALE:
  311. $payment->registerCaptureNotification($payment->getBaseAmountAuthorized());
  312. break;
  313. default:
  314. break;
  315. }
  316. $order->save();
  317. try {
  318. if ($canSendNewOrderEmail) {
  319. $this->orderSender->send($order);
  320. }
  321. $quote = $this->quoteRepository->get($order->getQuoteId())->setIsActive(false);
  322. $this->quoteRepository->save($quote);
  323. } catch (\Exception $e) {
  324. throw new \Magento\Framework\Exception\LocalizedException(__('We cannot send the new order email.'));
  325. }
  326. }
  327. /**
  328. * Check response from Payflow gateway.
  329. *
  330. * @return false|\Magento\Sales\Model\Order in case of validation passed
  331. * @throws \Magento\Framework\Exception\LocalizedException In other cases
  332. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  333. * @SuppressWarnings(PHPMD.NPathComplexity)
  334. */
  335. protected function _getOrderFromResponse()
  336. {
  337. $response = $this->getResponse();
  338. $order = $this->_orderFactory->create()->loadByIncrementId($response->getInvnum());
  339. if ($this->_getSecureSilentPostHash(
  340. $order->getPayment()
  341. ) != $response->getData('user2') || $this->_code != $order->getPayment()->getMethodInstance()->getCode()
  342. ) {
  343. return false;
  344. }
  345. if ($response->getResult() != self::RESPONSE_CODE_FRAUDSERVICE_FILTER &&
  346. $response->getResult() != self::RESPONSE_CODE_DECLINED_BY_FILTER &&
  347. $response->getResult() != self::RESPONSE_CODE_APPROVED
  348. ) {
  349. if ($order->getState() != \Magento\Sales\Model\Order::STATE_CANCELED) {
  350. $order->registerCancellation($response->getRespmsg())->save();
  351. }
  352. throw new \Magento\Framework\Exception\LocalizedException(__($response->getRespmsg()));
  353. }
  354. $amountCompared = $response->getAmt() == $order->getPayment()->getBaseAmountAuthorized() ? true : false;
  355. if (!$order->getId() ||
  356. $order->getState() != \Magento\Sales\Model\Order::STATE_PENDING_PAYMENT ||
  357. !$amountCompared
  358. ) {
  359. throw new \Magento\Framework\Exception\LocalizedException(
  360. __('Payment error. %value was not found.', ['value' => 'Order'])
  361. );
  362. }
  363. $fetchData = $this->fetchTransactionInfo($order->getPayment(), $response->getPnref());
  364. if (!isset($fetchData['custref']) || $fetchData['custref'] != $order->getIncrementId()) {
  365. throw new \Magento\Framework\Exception\LocalizedException(
  366. __('Payment error. %value was not found.', ['value' => 'Transaction'])
  367. );
  368. }
  369. return $order;
  370. }
  371. /**
  372. * Build request for getting token
  373. *
  374. * @param \Magento\Sales\Model\Order\Payment $payment
  375. * @return \Magento\Framework\DataObject
  376. */
  377. protected function _buildTokenRequest(\Magento\Sales\Model\Order\Payment $payment)
  378. {
  379. $request = $this->buildBasicRequest();
  380. $request->setCreatesecuretoken('Y')
  381. ->setSecuretokenid($this->mathRandom->getUniqueHash())
  382. ->setTrxtype($this->_getTrxTokenType());
  383. $order = $payment->getOrder();
  384. $request->setAmt(sprintf('%.2F', $order->getBaseTotalDue()))
  385. ->setCurrency($order->getBaseCurrencyCode());
  386. $this->addRequestOrderInfo($request, $order);
  387. $request = $this->fillCustomerContacts($order, $request);
  388. //pass store Id to request
  389. $request->setData('USER1', $order->getStoreId());
  390. $request->setData('USER2', $this->_getSecureSilentPostHash($payment));
  391. return $request;
  392. }
  393. /**
  394. * Get store id from response if exists
  395. * or default
  396. *
  397. * @return int
  398. */
  399. protected function _getStoreId()
  400. {
  401. $response = $this->getResponse();
  402. if ($response->getData('user1')) {
  403. return (int)$response->getData('user1');
  404. }
  405. return $this->storeManager->getStore($this->getStore())->getId();
  406. }
  407. /**
  408. * Return request object with basic information for gateway request
  409. *
  410. * @return \Magento\Paypal\Model\Payflow\Request
  411. */
  412. public function buildBasicRequest()
  413. {
  414. /** @var \Magento\Paypal\Model\Payflow\Request $request */
  415. $request = $this->_requestFactory->create();
  416. $cscEditable = $this->getConfigData('csc_editable');
  417. $data = parent::buildBasicRequest();
  418. $request->setData($data->getData());
  419. $request->setCancelurl(
  420. $this->_getCallbackUrl('cancelPayment')
  421. )->setErrorurl(
  422. $this->_getCallbackUrl('returnUrl')
  423. )->setSilentpost(
  424. 'TRUE'
  425. )->setSilentposturl(
  426. $this->_getCallbackUrl('silentPost')
  427. )->setReturnurl(
  428. $this->_getCallbackUrl('returnUrl')
  429. )->setTemplate(
  430. self::LAYOUT_TEMPLATE
  431. )->setDisablereceipt(
  432. 'TRUE'
  433. )->setCscrequired(
  434. $cscEditable && $this->getConfigData('csc_required') ? 'TRUE' : 'FALSE'
  435. )->setCscedit(
  436. $cscEditable ? 'TRUE' : 'FALSE'
  437. )->setEmailcustomer(
  438. $this->getConfigData('email_confirmation') ? 'TRUE' : 'FALSE'
  439. )->setUrlmethod(
  440. $this->getConfigData('url_method')
  441. );
  442. return $request;
  443. }
  444. /**
  445. * Get payment action code
  446. *
  447. * @return string
  448. */
  449. protected function _getTrxTokenType()
  450. {
  451. switch ($this->getConfigData('payment_action')) {
  452. case \Magento\Paypal\Model\Config::PAYMENT_ACTION_AUTH:
  453. return self::TRXTYPE_AUTH_ONLY;
  454. case \Magento\Paypal\Model\Config::PAYMENT_ACTION_SALE:
  455. return self::TRXTYPE_SALE;
  456. default:
  457. break;
  458. }
  459. }
  460. /**
  461. * If response is failed throw exception
  462. * Set token data in payment object
  463. *
  464. * @param \Magento\Framework\DataObject $response
  465. * @param \Magento\Sales\Model\Order\Payment $payment
  466. * @return void
  467. * @throws \Magento\Framework\Exception\LocalizedException
  468. */
  469. protected function _processTokenErrors($response, $payment)
  470. {
  471. if (!$response->getSecuretoken() &&
  472. $response->getResult() != self::RESPONSE_CODE_APPROVED &&
  473. $response->getResult() != self::RESPONSE_CODE_FRAUDSERVICE_FILTER
  474. ) {
  475. throw new \Magento\Framework\Exception\LocalizedException(__($response->getRespmsg()));
  476. } else {
  477. $payment->setAdditionalInformation(
  478. 'secure_token_id',
  479. $response->getSecuretokenid()
  480. )->setAdditionalInformation(
  481. 'secure_token',
  482. $response->getSecuretoken()
  483. );
  484. }
  485. }
  486. /**
  487. * Return secure hash value for silent post request
  488. *
  489. * @param \Magento\Sales\Model\Order\Payment $payment
  490. * @return string
  491. */
  492. protected function _getSecureSilentPostHash($payment)
  493. {
  494. return $payment->getAdditionalInformation($this->_secureSilentPostHashKey);
  495. }
  496. /**
  497. * Generate end return new secure hash value
  498. *
  499. * @param \Magento\Sales\Model\Order\Payment $payment
  500. * @return string
  501. */
  502. protected function _generateSecureSilentPostHash($payment)
  503. {
  504. $secureHash = md5($this->mathRandom->getRandomString(10));
  505. $payment->setAdditionalInformation($this->_secureSilentPostHashKey, $secureHash);
  506. return $secureHash;
  507. }
  508. /**
  509. * Get callback url
  510. *
  511. * @param string $actionName
  512. * @return string
  513. */
  514. protected function _getCallbackUrl($actionName)
  515. {
  516. if ($this->_requestHttp->getParam('website')) {
  517. /** @var $website \Magento\Store\Model\Website */
  518. $website = $this->_websiteFactory->create()->load($this->_requestHttp->getParam('website'));
  519. $secure = $this->_scopeConfig->isSetFlag(
  520. \Magento\Store\Model\Store::XML_PATH_SECURE_IN_FRONTEND,
  521. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  522. $website->getDefaultStore()
  523. );
  524. $path = $secure
  525. ? \Magento\Store\Model\Store::XML_PATH_SECURE_BASE_LINK_URL
  526. : \Magento\Store\Model\Store::XML_PATH_UNSECURE_BASE_LINK_URL;
  527. $websiteUrl = $this->_scopeConfig->getValue(
  528. $path,
  529. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  530. $website->getDefaultStore()
  531. );
  532. } else {
  533. $secure = $this->_scopeConfig->isSetFlag(
  534. \Magento\Store\Model\Store::XML_PATH_SECURE_IN_FRONTEND,
  535. \Magento\Store\Model\ScopeInterface::SCOPE_STORE
  536. );
  537. $websiteUrl = $this->storeManager->getStore()->getBaseUrl(
  538. \Magento\Framework\UrlInterface::URL_TYPE_LINK,
  539. $secure
  540. );
  541. }
  542. return $websiteUrl . 'paypal/' . $this->_callbackController . '/' . $actionName;
  543. }
  544. }