Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
Ipn.php
Go to the documentation of this file.
1 <?php
7 namespace Magento\Paypal\Model;
8 
9 use Exception;
13 
19 {
23  protected $_order;
24 
28  protected $_orderFactory;
29 
35  protected $_paypalInfo;
36 
40  protected $orderSender;
41 
45  protected $creditmemoSender;
46 
57  public function __construct(
58  \Magento\Paypal\Model\ConfigFactory $configFactory,
59  \Psr\Log\LoggerInterface $logger,
60  \Magento\Framework\HTTP\Adapter\CurlFactory $curlFactory,
61  \Magento\Sales\Model\OrderFactory $orderFactory,
62  Info $paypalInfo,
65  array $data = []
66  ) {
67  parent::__construct($configFactory, $logger, $curlFactory, $data);
68  $this->_orderFactory = $orderFactory;
69  $this->_paypalInfo = $paypalInfo;
70  $this->orderSender = $orderSender;
71  $this->creditmemoSender = $creditmemoSender;
72  }
73 
80  public function processIpnRequest()
81  {
82  $this->_addDebugData('ipn', $this->getRequestData());
83 
84  try {
85  $this->_getConfig();
86  $this->_postBack();
87  $this->_processOrder();
88  } catch (Exception $e) {
89  $this->_addDebugData('exception', $e->getMessage());
90  $this->_debug();
91  throw $e;
92  }
93  $this->_debug();
94  }
95 
102  protected function _getConfig()
103  {
104  $order = $this->_getOrder();
105  $methodCode = $order->getPayment()->getMethod();
106  $parameters = ['params' => [$methodCode, $order->getStoreId()]];
107  $this->_config = $this->_configFactory->create($parameters);
108  if (!$this->_config->isMethodActive($methodCode) || !$this->_config->isMethodAvailable()) {
109  throw new Exception(sprintf('The "%s" method isn\'t available.', $methodCode));
110  }
113  // verify merchant email intended to receive notification
114  $merchantEmail = $this->_config->getValue('businessAccount');
115  if (!$merchantEmail) {
116  return $this->_config;
117  }
118  $receiver = $this->getRequestData('business') ?: $this->getRequestData('receiver_email');
119  if (strtolower($merchantEmail) != strtolower($receiver)) {
120  throw new Exception(
121  sprintf(
122  'The requested "%s" and the configured "%s" merchant emails don\'t match.',
123  $receiver,
124  $merchantEmail
125  )
126  );
127  }
128 
129  return $this->_config;
130  }
131 
138  protected function _getOrder()
139  {
140  $incrementId = $this->getRequestData('invoice');
141  $this->_order = $this->_orderFactory->create()->loadByIncrementId($incrementId);
142  if (!$this->_order->getId()) {
143  throw new Exception(sprintf('The "%s" order ID is incorrect. Verify the ID and try again.', $incrementId));
144  }
145  return $this->_order;
146  }
147 
156  protected function _processOrder()
157  {
158  $this->_getConfig();
159  try {
160  // Handle payment_status
161  $transactionType = $this->getRequestData('txn_type');
162  switch ($transactionType) {
163  // handle new case created
165  $this->_registerDispute();
166  break;
167  // handle new adjustment is created
169  $this->_registerAdjustment();
170  break;
171  //handle new transaction created
172  default:
173  $this->_registerTransaction();
174  break;
175  }
176  } catch (\Magento\Framework\Exception\LocalizedException $e) {
177  $comment = $this->_createIpnComment(__('Note: %1', $e->getMessage()), true);
178  $comment->save();
179  throw $e;
180  }
181  }
182 
188  protected function _registerDispute()
189  {
190  $reasonComment = $this->_paypalInfo->explainReasonCode($this->getRequestData('reason_code'));
191  $caseType = $this->getRequestData('case_type');
192  $caseTypeLabel = $this->_paypalInfo->getCaseTypeLabel($caseType);
193  $caseId = $this->getRequestData('case_id');
194  //Add IPN comment about registered dispute
195  $message = __(
196  'IPN "%1". Case type "%2". Case ID "%3" %4',
197  ucfirst($caseType),
198  $caseTypeLabel,
199  $caseId,
200  $reasonComment
201  );
202  $this->_order->addStatusHistoryComment($message)->setIsCustomerNotified(false)->save();
203  }
204 
210  protected function _registerAdjustment()
211  {
212  $reasonCode = $this->getRequestData('reason_code');
213  $reasonComment = $this->_paypalInfo->explainReasonCode($reasonCode);
214  $notificationAmount = $this->_order->getBaseCurrency()->formatTxt($this->getRequestData('mc_gross'));
215  // Add IPN comment about registered dispute
216  $message = __(
217  'IPN "%1". A dispute has been resolved and closed. %2 Transaction amount %3.',
218  ucfirst($reasonCode),
219  $notificationAmount,
220  $reasonComment
221  );
222  $this->_order->addStatusHistoryComment($message)->setIsCustomerNotified(false)->save();
223  }
224 
233  protected function _registerTransaction()
234  {
235  // Handle payment_status
236  $paymentStatus = $this->_filterPaymentStatus($this->getRequestData('payment_status'));
237  switch ($paymentStatus) {
238  // paid
240  $this->_registerPaymentCapture(true);
241  break;
242  // the holded payment was denied on paypal side
244  $this->_registerPaymentDenial();
245  break;
246  // customer attempted to pay via bank account, but failed
248  // cancel order
249  $this->_registerPaymentFailure();
250  break;
251  // payment was obtained, but money were not captured yet
253  $this->_registerPaymentPending();
254  break;
257  break;
259  //break is intentionally omitted
261  $this->_registerPaymentReversal();
262  break;
264  $this->_registerPaymentRefund();
265  break;
266  // authorization expire/void
268  // break is intentionally omitted
270  $this->_registerPaymentVoid();
271  break;
272  default:
273  throw new Exception("The '{$paymentStatus}' payment status couldn't be handled.");
274  }
275  }
276 
283  protected function _registerPaymentCapture($skipFraudDetection = false)
284  {
285  if ($this->getRequestData('transaction_entity') == 'auth') {
286  return;
287  }
288  $parentTransactionId = $this->getRequestData('parent_txn_id');
289  $this->_importPaymentInformation();
290  $payment = $this->_order->getPayment();
291  $payment->setTransactionId($this->getRequestData('txn_id'));
292  $payment->setCurrencyCode($this->getRequestData('mc_currency'));
293  $payment->setPreparedMessage($this->_createIpnComment(''));
294  $payment->setParentTransactionId($parentTransactionId);
295  $payment->setShouldCloseParentTransaction('Completed' === $this->getRequestData('auth_status'));
296  $payment->setIsTransactionClosed(0);
297  $payment->registerCaptureNotification(
298  $this->getRequestData('mc_gross'),
299  $skipFraudDetection && $parentTransactionId
300  );
301  $this->_order->save();
302 
303  // notify customer
304  $invoice = $payment->getCreatedInvoice();
305  if ($invoice && !$this->_order->getEmailSent()) {
306  $this->orderSender->send($this->_order);
307  $this->_order->addStatusHistoryComment(
308  __('You notified customer about invoice #%1.', $invoice->getIncrementId())
309  )
310  ->setIsCustomerNotified(true)
311  ->save();
312  }
313  }
314 
321  protected function _registerPaymentDenial()
322  {
323  try {
324  $this->_importPaymentInformation();
325  $this->_order->getPayment()
326  ->setTransactionId($this->getRequestData('txn_id'))
327  ->setNotificationResult(true)
328  ->setIsTransactionClosed(true)
329  ->deny(false);
330  $this->_order->save();
331  } catch (LocalizedException $e) {
332  if ($e->getMessage() != __('We cannot cancel this order.')) {
333  throw $e;
334  }
335  }
336  }
337 
343  protected function _registerPaymentFailure()
344  {
345  $this->_importPaymentInformation();
346  $this->_order->registerCancellation($this->_createIpnComment(''))->save();
347  }
348 
355  public function _registerPaymentPending()
356  {
357  $reason = $this->getRequestData('pending_reason');
358  if ('authorization' === $reason) {
359  $this->_registerPaymentAuthorization();
360  return;
361  }
362  if ('order' === $reason) {
363  throw new Exception('The "order" authorizations aren\'t implemented.');
364  }
365  // case when was placed using PayPal standard
366  if (\Magento\Sales\Model\Order::STATE_PENDING_PAYMENT == $this->_order->getState()
367  && !$this->getRequestData('transaction_entity')
368  ) {
369  $this->_registerPaymentCapture();
370  return;
371  }
372 
373  $this->_importPaymentInformation();
374 
375  $this->_order->getPayment()
376  ->setPreparedMessage($this->_createIpnComment($this->_paypalInfo->explainPendingReason($reason)))
377  ->setTransactionId($this->getRequestData('txn_id'))
378  ->setIsTransactionClosed(0)
379  ->update(false);
380  $this->_order->save();
381  }
382 
388  protected function _registerPaymentAuthorization()
389  {
391  $payment = $this->_order->getPayment();
392  if ($this->_order->canFetchPaymentReviewUpdate()) {
393  $payment->update(true);
394  } else {
395  $this->_importPaymentInformation();
396  $payment->setPreparedMessage($this->_createIpnComment(''))
397  ->setTransactionId($this->getRequestData('txn_id'))
398  ->setParentTransactionId($this->getRequestData('parent_txn_id'))
399  ->setCurrencyCode($this->getRequestData('mc_currency'))
400  ->setIsTransactionClosed(0)
401  ->registerAuthorizationNotification($this->getRequestData('mc_gross'));
402  }
403  if (!$this->_order->getEmailSent()) {
404  $this->orderSender->send($this->_order);
405  }
406  $this->_order->save();
407  }
408 
414  protected function _registerMasspaymentsSuccess()
415  {
416  $comment = $this->_createIpnComment('', true);
417  $comment->save();
418  }
419 
425  protected function _registerPaymentReversal()
426  {
427  $reasonCode = $this->getRequestData('reason_code');
428  $reasonComment = $this->_paypalInfo->explainReasonCode($reasonCode);
429  $notificationAmount = $this->_order->getBaseCurrency()
430  ->formatTxt(
431  $this->getRequestData('mc_gross') + $this->getRequestData('mc_fee')
432  );
433  $paymentStatus = $this->_filterPaymentStatus($this->getRequestData('payment_status'));
434  $orderStatus = $paymentStatus ==
436  //Change order status to PayPal Reversed/PayPal Cancelled Reversal if it is possible.
437  $message = __(
438  'IPN "%1". %2 Transaction amount %3. Transaction ID: "%4"',
439  $this->getRequestData('payment_status'),
440  $reasonComment,
441  $notificationAmount,
442  $this->getRequestData('txn_id')
443  );
444  $this->_order->setStatus($orderStatus);
445  $this->_order->addStatusHistoryComment($message, $orderStatus)
446  ->setIsCustomerNotified(false)
447  ->save();
448  }
449 
455  protected function _registerPaymentRefund()
456  {
457  $this->_importPaymentInformation();
458  $reason = $this->getRequestData('reason_code');
459  $isRefundFinal = !$this->_paypalInfo->isReversalDisputable($reason);
460  $payment = $this->_order->getPayment()
461  ->setPreparedMessage($this->_createIpnComment($this->_paypalInfo->explainReasonCode($reason)))
462  ->setTransactionId($this->getRequestData('txn_id'))
463  ->setParentTransactionId($this->getRequestData('parent_txn_id'))
464  ->setIsTransactionClosed($isRefundFinal)
465  ->registerRefundNotification(-1 * $this->getRequestData('mc_gross'));
466  $this->_order->save();
467 
468  // TODO: there is no way to close a capture right now
469 
470  $creditMemo = $payment->getCreatedCreditmemo();
471  if ($creditMemo) {
472  $this->creditmemoSender->send($creditMemo);
473  $this->_order->addStatusHistoryComment(
474  __('You notified customer about creditmemo #%1.', $creditMemo->getIncrementId())
475  )
476  ->setIsCustomerNotified(true)
477  ->save();
478  }
479  }
480 
486  protected function _registerPaymentVoid()
487  {
488  $this->_importPaymentInformation();
489 
490  $parentTxnId = $this->getRequestData('transaction_entity') == 'auth'
491  ? $this->getRequestData('txn_id')
492  : $this->getRequestData('parent_txn_id');
493 
494  $this->_order->getPayment()
495  ->setPreparedMessage($this->_createIpnComment(''))
496  ->setParentTransactionId($parentTxnId)
497  ->registerVoidNotification();
498 
499  $this->_order->save();
500  }
501 
510  protected function _importPaymentInformation()
511  {
512  $payment = $this->_order->getPayment();
513  $was = $payment->getAdditionalInformation();
514 
515  // collect basic information
516  $from = [];
517  foreach ([
519  'payer_email' => Info::PAYER_EMAIL,
525  ] as $privateKey => $publicKey) {
526  if (is_int($privateKey)) {
527  $privateKey = $publicKey;
528  }
529  $value = $this->getRequestData($privateKey);
530  if ($value) {
531  $from[$publicKey] = $value;
532  }
533  }
534  if (isset($from['payment_status'])) {
535  $from['payment_status'] = $this->_filterPaymentStatus($this->getRequestData('payment_status'));
536  }
537 
538  // collect fraud filters
539  $fraudFilters = [];
540  for ($i = 1; $value = $this->getRequestData("fraud_management_pending_filters_{$i}"); $i++) {
541  $fraudFilters[] = $value;
542  }
543  if ($fraudFilters) {
544  $from[Info::FRAUD_FILTERS] = $fraudFilters;
545  }
546 
547  $this->_paypalInfo->importToPayment($from, $payment);
548 
555  $payment->setIsTransactionPending(true);
556  if ($fraudFilters) {
557  $payment->setIsFraudDetected(true);
558  }
559  }
561  $payment->setIsTransactionApproved(true);
563  $payment->setIsTransactionDenied(true);
564  }
565 
566  return $was != $payment->getAdditionalInformation();
567  }
568 
577  protected function _createIpnComment($comment = '', $addToHistory = false)
578  {
579  $message = __('IPN "%1"', $this->getRequestData('payment_status'));
580  if ($comment) {
581  $message .= ' ' . $comment;
582  }
583  if ($addToHistory) {
584  $message = $this->_order->addStatusHistoryComment($message);
585  $message->setIsCustomerNotified(null);
586  }
587  return $message;
588  }
589 }
const PAYMENTSTATUS_COMPLETED
Definition: Info.php:117
static isPaymentReviewRequired(\Magento\Payment\Model\InfoInterface $payment)
Definition: Info.php:338
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
_createIpnComment($comment='', $addToHistory=false)
Definition: Ipn.php:577
const PAYMENTSTATUS_PROCESSED
Definition: Info.php:137
$order
Definition: order.php:55
__construct(\Magento\Paypal\Model\ConfigFactory $configFactory, \Psr\Log\LoggerInterface $logger, \Magento\Framework\HTTP\Adapter\CurlFactory $curlFactory, \Magento\Sales\Model\OrderFactory $orderFactory, Info $paypalInfo, OrderSender $orderSender, CreditmemoSender $creditmemoSender, array $data=[])
Definition: Ipn.php:57
__()
Definition: __.php:13
const ORDER_STATUS_CANCELED_REVERSAL
Definition: Info.php:163
$message
static isPaymentFailed(\Magento\Payment\Model\InfoInterface $payment)
Definition: Info.php:411
$logger
$payment
Definition: order.php:17
_registerPaymentCapture($skipFraudDetection=false)
Definition: Ipn.php:283
_filterPaymentStatus($ipnPaymentStatus)
$orderStatus
Definition: order_status.php:9
$value
Definition: gender.phtml:16
const PAYMENTSTATUS_UNREVERSED
Definition: Info.php:135
$invoice
static isPaymentSuccessful(\Magento\Payment\Model\InfoInterface $payment)
Definition: Info.php:381
$configFactory
Definition: config_data.php:43
const PAYMENTSTATUS_REFUNDED
Definition: Info.php:129
const PAYMENTSTATUS_REVERSED
Definition: Info.php:133
$i
Definition: gallery.phtml:31