![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/old/vendor/magento/module-paypal/Model/ |
<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Paypal\Model; use Exception; use Magento\Framework\Exception\LocalizedException; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Email\Sender\CreditmemoSender; use Magento\Sales\Model\Order\Email\Sender\OrderSender; /** * PayPal Instant Payment Notification processor model * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Ipn extends \Magento\Paypal\Model\AbstractIpn implements IpnInterface { /** * @var \Magento\Sales\Model\Order */ protected $_order; /** * @var \Magento\Sales\Model\OrderFactory */ protected $_orderFactory; /** * PayPal info instance * * @var Info */ protected $_paypalInfo; /** * @var OrderSender */ protected $orderSender; /** * @var CreditmemoSender */ protected $creditmemoSender; /** * @param \Magento\Paypal\Model\ConfigFactory $configFactory * @param \Psr\Log\LoggerInterface $logger * @param \Magento\Framework\HTTP\Adapter\CurlFactory $curlFactory * @param \Magento\Sales\Model\OrderFactory $orderFactory * @param Info $paypalInfo * @param OrderSender $orderSender * @param CreditmemoSender $creditmemoSender * @param array $data */ public function __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 = [] ) { parent::__construct($configFactory, $logger, $curlFactory, $data); $this->_orderFactory = $orderFactory; $this->_paypalInfo = $paypalInfo; $this->orderSender = $orderSender; $this->creditmemoSender = $creditmemoSender; } /** * Get ipn data, send verification to PayPal, run corresponding handler * * @return void * @throws Exception */ public function processIpnRequest() { $this->_addDebugData('ipn', $this->getRequestData()); try { $this->_getConfig(); $this->_postBack(); $this->_processOrder(); } catch (Exception $e) { $this->_addDebugData('exception', $e->getMessage()); $this->_debug(); throw $e; } $this->_debug(); } /** * Get config with the method code and store id and validate * * @return \Magento\Paypal\Model\Config * @throws Exception */ protected function _getConfig() { $order = $this->_getOrder(); $methodCode = $order->getPayment()->getMethod(); $parameters = ['params' => [$methodCode, $order->getStoreId()]]; $this->_config = $this->_configFactory->create($parameters); if (!$this->_config->isMethodActive($methodCode) || !$this->_config->isMethodAvailable()) { // phpcs:ignore Magento2.Exceptions.DirectThrow throw new Exception(sprintf('The "%s" method isn\'t available.', $methodCode)); } /** @link https://cms.paypal.com/cgi-bin/marketingweb?cmd=_render-content&content_ID= * developer/e_howto_admin_IPNIntro */ // verify merchant email intended to receive notification $merchantEmail = $this->_config->getValue('businessAccount'); if (!$merchantEmail) { return $this->_config; } $receiver = $this->getRequestData('business') ?: ($this->getRequestData('receiver_email') ?? ''); if (strtolower($merchantEmail) != strtolower($receiver)) { // phpcs:ignore Magento2.Exceptions.DirectThrow throw new Exception( sprintf( 'The requested "%s" and the configured "%s" merchant emails don\'t match.', $receiver, $merchantEmail ) ); } return $this->_config; } /** * Load order * * @return \Magento\Sales\Model\Order * @throws Exception */ protected function _getOrder() { $incrementId = $this->getRequestData('invoice'); $this->_order = $this->_orderFactory->create()->loadByIncrementId($incrementId); if (!$this->_order->getId()) { // phpcs:ignore Magento2.Exceptions.DirectThrow throw new Exception(sprintf('The "%s" order ID is incorrect. Verify the ID and try again.', $incrementId)); } return $this->_order; } /** * IPN workflow implementation * Everything should be added to order comments. In positive processing cases customer will get email notifications. * Admin will be notified on errors. * * @return void * @throws \Magento\Framework\Exception\LocalizedException */ protected function _processOrder() { $this->_getConfig(); try { // Handle payment_status $transactionType = $this->getRequestData('txn_type'); switch ($transactionType) { // handle new case created case Info::TXN_TYPE_NEW_CASE: $this->_registerDispute(); break; // handle new adjustment is created case Info::TXN_TYPE_ADJUSTMENT: $this->_registerAdjustment(); break; //handle new transaction created default: $this->_registerTransaction(); break; } } catch (\Magento\Framework\Exception\LocalizedException $e) { $comment = $this->_createIpnComment(__('Note: %1', $e->getMessage()), true); $comment->save(); throw $e; } } /** * Process dispute notification * * @return void */ protected function _registerDispute() { $reasonComment = $this->_paypalInfo->explainReasonCode($this->getRequestData('reason_code')); $caseType = $this->getRequestData('case_type'); $caseTypeLabel = $this->_paypalInfo->getCaseTypeLabel($caseType); $caseId = $this->getRequestData('case_id'); //Add IPN comment about registered dispute $message = __( 'IPN "%1". Case type "%2". Case ID "%3" %4', ucfirst($caseType), $caseTypeLabel, $caseId, $reasonComment ); $this->_order->addStatusHistoryComment($message)->setIsCustomerNotified(false)->save(); } /** * Process adjustment notification * * @return void */ protected function _registerAdjustment() { $reasonCode = $this->getRequestData('reason_code'); $reasonComment = $this->_paypalInfo->explainReasonCode($reasonCode); $notificationAmount = $this->_order->getBaseCurrency()->formatTxt($this->getRequestData('mc_gross')); // Add IPN comment about registered dispute $message = __( 'IPN "%1". A dispute has been resolved and closed. %2 Transaction amount %3.', ucfirst($reasonCode), $notificationAmount, $reasonComment ); $this->_order->addStatusHistoryComment($message)->setIsCustomerNotified(false)->save(); } /** * Process regular IPN notifications * * @return void * @throws \Magento\Framework\Exception\LocalizedException * @throws Exception * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _registerTransaction() { // Handle payment_status $paymentStatus = $this->_filterPaymentStatus($this->getRequestData('payment_status')); switch ($paymentStatus) { // paid case Info::PAYMENTSTATUS_COMPLETED: $this->_registerPaymentCapture(true); break; // the holded payment was denied on paypal side case Info::PAYMENTSTATUS_DENIED: $this->_registerPaymentDenial(); break; // customer attempted to pay via bank account, but failed case Info::PAYMENTSTATUS_FAILED: if ($this->_order->getState() === \Magento\Sales\Model\Order::STATE_PAYMENT_REVIEW) { $this->_registerPaymentDenial(); } else { $this->_registerPaymentFailure(); } break; // payment was obtained, but money were not captured yet case Info::PAYMENTSTATUS_PENDING: $this->_registerPaymentPending(); break; case Info::PAYMENTSTATUS_PROCESSED: $this->_registerMasspaymentsSuccess(); break; case Info::PAYMENTSTATUS_REVERSED: //break is intentionally omitted case Info::PAYMENTSTATUS_UNREVERSED: $this->_registerPaymentReversal(); break; case Info::PAYMENTSTATUS_REFUNDED: $this->_registerPaymentRefund(); break; // authorization expire/void case Info::PAYMENTSTATUS_EXPIRED: // break is intentionally omitted case Info::PAYMENTSTATUS_VOIDED: $this->_registerPaymentVoid(); break; default: // phpcs:ignore Magento2.Exceptions.DirectThrow throw new Exception("The '{$paymentStatus}' payment status couldn't be handled."); } } /** * Process completed payment (either full or partial) * * @param bool $skipFraudDetection * @return void */ protected function _registerPaymentCapture($skipFraudDetection = false) { if ($this->getRequestData('transaction_entity') == 'auth') { return; } $parentTransactionId = $this->getRequestData('parent_txn_id'); $this->_importPaymentInformation(); $payment = $this->_order->getPayment(); $payment->setTransactionId($this->getRequestData('txn_id')); $payment->setCurrencyCode($this->getRequestData('mc_currency')); $payment->setPreparedMessage($this->_createIpnComment('')); $payment->setParentTransactionId($parentTransactionId); $payment->setShouldCloseParentTransaction('Completed' === $this->getRequestData('auth_status')); $payment->setIsTransactionClosed(0); if ($this->_order->getState() === Order::STATE_PENDING_PAYMENT) { $this->_order->setState(Order::STATE_PROCESSING); } $payment->registerCaptureNotification( $this->getRequestData('mc_gross'), $skipFraudDetection && $parentTransactionId ); $this->_order->save(); // notify customer $invoice = $payment->getCreatedInvoice(); if ($invoice && !$this->_order->getEmailSent()) { $this->orderSender->send($this->_order); $this->_order->addStatusHistoryComment( __('You notified customer about invoice #%1.', $invoice->getIncrementId()) ) ->setIsCustomerNotified(true) ->save(); } } /** * Process denied payment notification * * @return void * @throws Exception */ protected function _registerPaymentDenial() { try { $this->_importPaymentInformation(); $payment = $this->_order->getPayment(); $payment->setTransactionId($this->getRequestData('txn_id')); $payment->setPreparedMessage($this->_createIpnComment('')); $payment->setNotificationResult(true); $payment->setIsTransactionClosed(true); $payment->deny(false); $this->_order->save(); } catch (LocalizedException $e) { if ($e->getMessage() != __('We cannot cancel this order.')) { throw $e; } } } /** * Treat failed payment as order cancellation * * @return void */ protected function _registerPaymentFailure() { $this->_importPaymentInformation(); $this->_order->registerCancellation($this->_createIpnComment(''))->save(); } /** * Process payment pending notification * * @return void * @throws Exception */ public function _registerPaymentPending() { $reason = $this->getRequestData('pending_reason'); if ('authorization' === $reason) { $this->_registerPaymentAuthorization(); return; } if ('order' === $reason) { // phpcs:ignore Magento2.Exceptions.DirectThrow throw new Exception('The "order" authorizations aren\'t implemented.'); } // case when was placed using PayPal standard if (\Magento\Sales\Model\Order::STATE_PENDING_PAYMENT == $this->_order->getState() && !$this->getRequestData('transaction_entity') ) { $this->_registerPaymentCapture(); return; } $this->_importPaymentInformation(); $this->_order->getPayment() ->setPreparedMessage($this->_createIpnComment($this->_paypalInfo->explainPendingReason($reason))) ->setTransactionId($this->getRequestData('txn_id')) ->setIsTransactionClosed(0) ->update(false); $this->_order->save(); } /** * Register authorized payment * * @return void */ protected function _registerPaymentAuthorization() { /** @var $payment \Magento\Sales\Model\Order\Payment */ $payment = $this->_order->getPayment(); if ($this->_order->canFetchPaymentReviewUpdate()) { $payment->update(true); } else { $this->_importPaymentInformation(); $payment->setPreparedMessage($this->_createIpnComment('')) ->setTransactionId($this->getRequestData('txn_id')) ->setParentTransactionId($this->getRequestData('parent_txn_id')) ->setCurrencyCode($this->getRequestData('mc_currency')) ->setIsTransactionClosed(0) ->registerAuthorizationNotification($this->getRequestData('mc_gross')); } if (!$this->_order->getEmailSent()) { $this->orderSender->send($this->_order); } $this->_order->save(); } /** * The status "Processed" is used when all Masspayments are successful * * @return void */ protected function _registerMasspaymentsSuccess() { $comment = $this->_createIpnComment('', true); $comment->save(); } /** * Process payment reversal and cancelled reversal notification * * @return void */ protected function _registerPaymentReversal() { $reasonCode = $this->getRequestData('reason_code'); $reasonComment = $this->_paypalInfo->explainReasonCode($reasonCode); $notificationAmount = $this->_order->getBaseCurrency() ->formatTxt( $this->getRequestData('mc_gross') + $this->getRequestData('mc_fee') ); $paymentStatus = $this->_filterPaymentStatus($this->getRequestData('payment_status')); $orderStatus = $paymentStatus == Info::PAYMENTSTATUS_REVERSED ? Info::ORDER_STATUS_REVERSED : Info::ORDER_STATUS_CANCELED_REVERSAL; //Change order status to PayPal Reversed/PayPal Cancelled Reversal if it is possible. $message = __( 'IPN "%1". %2 Transaction amount %3. Transaction ID: "%4"', $this->getRequestData('payment_status'), $reasonComment, $notificationAmount, $this->getRequestData('txn_id') ); $this->_order->setStatus($orderStatus); $this->_order->addStatusHistoryComment($message, $orderStatus) ->setIsCustomerNotified(false) ->save(); } /** * Process a refund * * @return void */ protected function _registerPaymentRefund() { $this->_importPaymentInformation(); $reason = $this->getRequestData('reason_code'); $isRefundFinal = !$this->_paypalInfo->isReversalDisputable($reason); $payment = $this->_order->getPayment() ->setPreparedMessage($this->_createIpnComment($this->_paypalInfo->explainReasonCode($reason))) ->setTransactionId($this->getRequestData('txn_id')) ->setParentTransactionId($this->getRequestData('parent_txn_id')) ->setIsTransactionClosed($isRefundFinal) ->registerRefundNotification(-1 * $this->getRequestData('mc_gross')); $this->_order->save(); // TODO: there is no way to close a capture right now $creditMemo = $payment->getCreatedCreditmemo(); if ($creditMemo) { $this->creditmemoSender->send($creditMemo); $this->_order->addStatusHistoryComment( __('You notified customer about creditmemo #%1.', $creditMemo->getIncrementId()) ) ->setIsCustomerNotified(true) ->save(); } } /** * Process voided authorization * * @return void */ protected function _registerPaymentVoid() { $this->_importPaymentInformation(); $parentTxnId = $this->getRequestData('transaction_entity') == 'auth' ? $this->getRequestData('txn_id') : $this->getRequestData('parent_txn_id'); $this->_order->getPayment() ->setPreparedMessage($this->_createIpnComment('')) ->setParentTransactionId($parentTxnId) ->registerVoidNotification(); $this->_order->save(); } /** * Map payment information from IPN to payment object * * Returns true if there were changes in information * * @return bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function _importPaymentInformation() { $payment = $this->_order->getPayment(); $was = $payment->getAdditionalInformation(); // collect basic information $from = []; foreach ([ Info::PAYER_ID, 'payer_email' => Info::PAYER_EMAIL, Info::PAYER_STATUS, Info::ADDRESS_STATUS, Info::PROTECTION_EL, Info::PAYMENT_STATUS, Info::PENDING_REASON, ] as $privateKey => $publicKey) { if (is_int($privateKey)) { $privateKey = $publicKey; } $value = $this->getRequestData($privateKey); if ($value) { $from[$publicKey] = $value; } } if (isset($from['payment_status'])) { $from['payment_status'] = $this->_filterPaymentStatus($this->getRequestData('payment_status')); } // collect fraud filters $fraudFilters = []; $index = 1; while ($value = $this->getRequestData("fraud_management_pending_filters_{$index}")) { $fraudFilters[] = $value; $index++; } if ($fraudFilters) { $from[Info::FRAUD_FILTERS] = $fraudFilters; } $this->_paypalInfo->importToPayment($from, $payment); /** * Detect pending payment, frauds * TODO: implement logic in one place * @see \Magento\Paypal\Model\Pro::importPaymentInfo() */ if (Info::isPaymentReviewRequired($payment)) { $payment->setIsTransactionPending(true); if ($fraudFilters) { $payment->setIsFraudDetected(true); } } if (Info::isPaymentSuccessful($payment)) { $payment->setIsTransactionApproved(true); } elseif (Info::isPaymentFailed($payment)) { $payment->setIsTransactionDenied(true); } return $was != $payment->getAdditionalInformation(); } /** * Generate an "IPN" comment with additional explanation. * * Returns the generated comment or order status history object * * @param string $comment * @param bool $addToHistory * @return string|\Magento\Sales\Model\Order\Status\History */ protected function _createIpnComment($comment = '', $addToHistory = false) { $message = __('IPN "%1"', $this->getRequestData('payment_status')); if ($comment) { $message .= ' ' . $comment; } if ($addToHistory) { $message = $this->_order->addStatusHistoryComment($message); $message->setIsCustomerNotified(null); } return $message; } }