File indexing completed on 2025-05-04 05:32:13

0001 <?php
0002 /**
0003  *  ocs-webserver
0004  *
0005  *  Copyright 2016 by pling GmbH.
0006  *
0007  *    This file is part of ocs-webserver.
0008  *
0009  *    This program is free software: you can redistribute it and/or modify
0010  *    it under the terms of the GNU Affero General Public License as
0011  *    published by the Free Software Foundation, either version 3 of the
0012  *    License, or (at your option) any later version.
0013  *
0014  *    This program is distributed in the hope that it will be useful,
0015  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
0016  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017  *    GNU Affero General Public License for more details.
0018  *
0019  *    You should have received a copy of the GNU Affero General Public License
0020  *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0021  **/
0022 
0023 abstract class Local_Payment_PayPal_AdaptivePayment_Ipn extends Local_Payment_PayPal_Base
0024 {
0025 
0026     const VERIFIED = 'VERIFIED';
0027 
0028     /** @var \Zend_Config */
0029     protected $_config;
0030     /** @var \Zend_Log */
0031     protected $_logger;
0032     /** @var  array */
0033     protected $_dataIpn;
0034     /** @var  \Local_Payment_PayPal_PaymentInterface */
0035     protected $_ipnMessage;
0036 
0037     /**
0038      * @param $rawData
0039      * @throws Exception
0040      */
0041     public function processIpn($rawData)
0042     {
0043         if (false === $this->verifyIpnOrigin($rawData)) {
0044             $this->_logger->err(__FUNCTION__ . '::Abort IPN processing. IPN not verified: ' . $rawData);
0045             return;
0046         }
0047 
0048         $this->_dataIpn = $this->_parseRawMessage($rawData);
0049         $this->_logger->info(__FUNCTION__ . '::_dataIpn: ' . print_r($this->_dataIpn, true) . "\n");
0050 
0051         $this->_ipnMessage = Local_Payment_PayPal_Response::buildResponse($this->_dataIpn);
0052         $this->_logger->info(__FUNCTION__ . '::_ipnMessage: ' . print_r($this->_ipnMessage, true) . "\n");
0053 
0054         if (false === $this->validateTransaction()) {
0055             $this->_logger->err(__FUNCTION__ . '::Abort IPN processing. Transaction not valid or unknown:' . $rawData);
0056             return;
0057         }
0058 
0059         $this->processPaymentStatus();
0060 
0061     }
0062 
0063     /**
0064      * @param string $rawDataIpn
0065      * @return bool
0066      */
0067     public function verifyIpnOrigin($rawDataIpn)
0068     {
0069         $rawDataIpnCmd = 'cmd=_notify-validate&';
0070         $requestParams = $rawDataIpnCmd . $rawDataIpn;
0071 
0072         $url = $this->_config->ipn->endpoint . '/webscr';
0073 
0074         $response = $this->_processRequest($requestParams, $url);
0075 
0076         if (strcmp($response, self::VERIFIED) == 0) {
0077             return true;
0078         }
0079 
0080         return false;
0081     }
0082 
0083     /**
0084      * @param string $requestData
0085      * @param string $url
0086      * @throws Local_Payment_Exception
0087      * @return string
0088      */
0089     protected function _processRequest($requestData, $url)
0090     {
0091         $http = new Zend_Http_Client($url);
0092         $http->setMethod(Zend_Http_Client::POST);
0093         $http->setRawData($requestData);
0094 
0095         try {
0096             $response = $http->request();
0097         } catch (Zend_Http_Client_Exception $e) {
0098             throw new Local_Payment_Exception('Error while request PayPal website.', 0, $e);
0099         }
0100 
0101         if (false === $response) {
0102             $this->_logger->err(__FUNCTION__ . "::Error while request PayPal Website.\n Server replay was: " . $http->getLastResponse()->getStatus() . ". " . $http->getLastResponse()->getMessage() . "\n");
0103             $this->_logger->err(__FUNCTION__ . '::Last Request: ' . print_r($http->getLastRequest(), true));
0104             $this->_logger->err(__FUNCTION__ . '::Headers: ' . print_r($response->getHeaders(), true));
0105             $this->_logger->err(__FUNCTION__ . '::Body: ' . print_r($response->getBody(), true) . "\n");
0106         } else {
0107             $this->_logger->debug(__FUNCTION__ . '::Last Request: ' . print_r($http->getLastRequest(), true));
0108             $this->_logger->debug(__FUNCTION__ . '::Headers: ' . print_r($response->getHeaders(), true));
0109             $this->_logger->debug(__FUNCTION__ . '::Body: ' . print_r($response->getBody(), true) . "\n");
0110         }
0111 
0112         return $response->getBody();
0113     }
0114 
0115     /**
0116      * @return bool
0117      */
0118     protected function validateTransaction()
0119     {
0120         // Make sure the receiver email address is one of yours and the
0121         // amount of money is correct
0122 
0123         return $this->_checkEmail() AND $this->_checkTxnId() AND $this->_checkAmount();
0124     }
0125 
0126     /**
0127      * Check email address for validity.
0128      * Override this method to make sure you are the one being paid.
0129      * Throw an Exception if data is invalid or other things go wrong.
0130      *
0131      * $this->_dataIpn['receiver_email'] = The email who is about to receive payment.
0132      */
0133     protected function _checkEmail()
0134     {
0135         // check that receiver_email is your Primary PayPal email
0136 
0137         $this->_logger->info('Not doing _checkEmail(' . $this->_dataIpn['receiver_email'] . ')');
0138 
0139         return false;
0140     }
0141 
0142     /**
0143      * Check txnId has not already been used.
0144      * Override this method to ensure txnId is not a duplicate.
0145      * Throw an Exception if data is invalid or other things go wrong.
0146      *
0147      * $this->_dataIpn['txn_id'] = The transaction ID from paypal.
0148      */
0149     protected function _checkTxnId()
0150     {
0151         // check that txn_id has not been previously processed
0152 
0153         $this->_logger->info('Not doing _checkTxnId(' . $this->_ipnMessage->getTransactionId() . ')');
0154 
0155         return false;
0156     }
0157 
0158     /**
0159      * Check that the amount/currency is correct for item_id.
0160      * You should override this method to ensure the amount is correct.
0161      * Throw an Exception if data is invalid or other things go wrong.
0162      *
0163      * $this->_dataIpn['item_number'] = The item number
0164      * $this->_dataIpn['mc_gross']    = The amount being paid
0165      * $this->_dataIpn['mc_currency'] = Currency code of amount
0166      */
0167     protected function _checkAmount()
0168     {
0169         // check that payment_amount/payment_currency are correct
0170 
0171         $this->_logger->info('Not doing _checkAmount(' . $this->_dataIpn['mc_gross'] . ', ' . $this->_dataIpn['mc_currency'] . ')');
0172 
0173         return false;
0174     }
0175 
0176     protected function processPaymentStatus()
0177     {
0178         switch ($this->_dataIpn['status']) {
0179             case 'COMPLETED':
0180                 $this->_statusCompleted();
0181                 break;
0182             case 'INCOMPLETE':
0183                 $this->_statusIncomplete();
0184                 break;
0185             case 'CREATED':
0186                 $this->_statusCreated();
0187                 break;
0188             case 'ERROR':
0189                 $this->_statusError();
0190                 break;
0191             case 'REVERSALERROR':
0192                 $this->_statusReversalError();
0193                 break;
0194             case 'PROCESSING':
0195                 $this->_statusProcessing();
0196                 break;
0197             case 'PENDING':
0198                 $this->_statusPending();
0199                 break;
0200             default:
0201                 throw new Local_Payment_Exception('Unknown status from PayPal: ' . $this->_ipnMessage->getStatus());
0202         }
0203     }
0204 
0205     /**
0206      * Transaction/Payment completed.
0207      *
0208      * This is typically the most important method you'll need to override to perform
0209      * some sort of action when a successful transaction has been completed.
0210      *
0211      * You could override the other status's (such as reverse or denied) to
0212      * reverse whatever was done, but that could interfere if you're denying a
0213      * payment or refunding someone for a good reason. In those cases, it's
0214      * probably best to simply do whatever steps are required manually.
0215      */
0216     protected function _statusCompleted()
0217     {
0218         $this->_logger->info('Not doing anything in statusCompleted ' . $this->_ipnMessage->getPaymentId());
0219     }
0220 
0221     /**
0222      * Some transfers succeeded and some failed for a parallel payment or, for a delayed chained payment, secondary receivers have not been paid
0223      */
0224     protected function _statusIncomplete()
0225     {
0226         $this->_logger->info('Not doing anything in _statusIncomplete ' . $this->_ipnMessage->getPaymentId());
0227     }
0228 
0229     /**
0230      * The payment request was received; funds will be transferred once the payment is approved
0231      */
0232     protected function _statusCreated()
0233     {
0234         $this->_logger->info('Not doing anything in _statusCreated ' . $this->_ipnMessage->getPaymentId());
0235     }
0236     /**
0237      * The payment request was denied; 
0238      */
0239     protected function _statusDenied()
0240     {
0241       $this->_logger->info('Not doing anything in _statusDenied ' . $this->_ipnMessage->getPaymentId());
0242     }
0243     /**
0244      * The payment request was reserved; 
0245      */
0246     protected function _statusReserved()
0247     {
0248       $this->_logger->info('Not doing anything in _statusReserved ' . $this->_ipnMessage->getPaymentId());
0249     }
0250     /**
0251      * The payment request was refunded;
0252      */
0253     protected function _statusRefunded()
0254     {
0255       $this->_logger->info('Not doing anything in _statusRefunded ' . $this->_ipnMessage->getPaymentId());
0256     }
0257     /**
0258      * The payment request was failed;
0259      */
0260     protected function _statusFailed()
0261     {
0262       $this->_logger->info('Not doing anything in _statusFailed ' . $this->_ipnMessage->getPaymentId());
0263     }
0264     /**
0265      * The payment request was Partially Refunded;
0266      */
0267     protected function _statusPartiallyRefunded()
0268     {
0269       $this->_logger->info('Not doing anything in _statusPartiallyRefunded ' . $this->_ipnMessage->getPaymentId());
0270     }
0271     
0272 
0273     /**
0274      * The payment failed and all attempted transfers failed or all completed transfers were successfully reversed
0275      */
0276     protected function _statusError()
0277     {
0278         $this->_logger->info('Not doing anything in _statusError ' . $this->_ipnMessage->getPaymentId());
0279     }
0280 
0281     /**
0282      * One or more transfers failed when attempting to reverse a payment
0283      *
0284      */
0285     protected function _statusReversalError()
0286     {
0287         $this->_logger->info('Not doing anything in _statusReversalError ' . $this->_ipnMessage->getPaymentId() . '::' . $this->_dataIpn['reason_code']);
0288     }
0289 
0290     /**
0291      * The payment is in progress
0292      */
0293     protected function _statusProcessing()
0294     {
0295         $this->_logger->info('Not doing anything in _statusProcessing ' . $this->_ipnMessage->getPaymentId());
0296     }
0297 
0298     /**
0299      * Pending state
0300      * Look at $this->_dataIpn['pending_reason']
0301      */
0302     protected function _statusPending()
0303     {
0304         $this->_logger->info('Not doing anything in _statusPending: ' . $this->_ipnMessage->getPaymentId() . '::' . $this->_dataIpn['pending_reason']);
0305     }
0306 
0307     public function getCharset($rawDataIpn)
0308     {
0309         $matches = array();
0310 
0311         preg_match('|charset=(.*?)\&|', $rawDataIpn, $matches);
0312 
0313         return $matches[1];
0314     }
0315 
0316     protected function processTransactionStatus()
0317     {
0318         switch (strtoupper($this->_ipnMessage->getTransactionStatus())) {
0319             case 'COMPLETED':
0320                 $this->_processTransactionStatusCompleted();
0321                 break;
0322             case 'PENDING':
0323                 $this->_processTransactionStatusPending();
0324                 break;
0325             case 'REFUNDED':
0326                 $this->_processTransactionStatusRefunded();
0327                 break;
0328             case 'DENIED':
0329                 $this->_processTransactionStatusDenied();
0330                 break;
0331             default:
0332                 throw new Local_Payment_Exception('Unknown transaction status from PayPal: ' . $this->_ipnMessage->getTransactionStatus());
0333         }
0334     }
0335 
0336     protected function _processTransactionStatusCompleted()
0337     {
0338         $this->_logger->info('Not doing anything in processTransactionStatusCompleted: ' . $this->_ipnMessage->getTransactionId());
0339     }
0340 
0341     protected function _processTransactionStatusPending()
0342     {
0343         $this->_logger->info('Not doing anything in processTransactionStatusPending: ' . $this->_ipnMessage->getTransactionId());
0344     }
0345 
0346     protected function _processTransactionStatusRefunded()
0347     {
0348         $this->_logger->info('Not doing anything in processTransactionStatusRefunded: ' . $this->_ipnMessage->getTransactionId());
0349     }
0350 
0351     protected function _processTransactionStatusDenied()
0352     {
0353         $this->_logger->info('Not doing anything in processTransactionStatusDenied: ' . $this->_ipnMessage->getTransactionId());
0354     }
0355 
0356 }