File indexing completed on 2024-12-22 05:36:23
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_Dwolla_Callback 0024 { 0025 0026 /** @var \Zend_Config */ 0027 protected $_config; 0028 /** @var \Zend_Log */ 0029 protected $_logger; 0030 /** @var array */ 0031 protected $_dataCallback; 0032 /** @var Local_Payment_Dwolla_ResponseInterface */ 0033 protected $_transactionMessage; 0034 0035 0036 /** 0037 * @param $config 0038 * @param null $logger 0039 * @throws Exception 0040 */ 0041 function __construct($config, $logger = null) 0042 { 0043 if (is_array($config)) { 0044 $this->_config = new Zend_Config($config); 0045 } else { 0046 if ($config instanceof Zend_Config) { 0047 $this->_config = $config; 0048 } 0049 } 0050 if (is_null($logger)) { 0051 $this->_logger = Zend_Registry::get('logger'); 0052 } else { 0053 if ($logger instanceof Zend_Log) { 0054 $this->_logger = $logger; 0055 } else { 0056 throw new Exception('Logger must be an instance of Zend_Log'); 0057 } 0058 } 0059 } 0060 0061 /** 0062 * @param string $rawData 0063 */ 0064 public function processCallback($rawData) 0065 { 0066 $this->_dataCallback = $this->_decodeData($rawData); 0067 0068 $this->_transactionMessage = Local_Payment_Dwolla_Response::buildResponse($this->_dataCallback); 0069 $this->_transactionMessage->setMsgBody($rawData); 0070 if (APPLICATION_ENV != 'production') { 0071 $this->_logger->debug(__METHOD__ . ' - _transactionMessage - ' . print_r($this->_transactionMessage, true) . PHP_EOL); 0072 } 0073 0074 if (false === $this->_transactionMessage->verifySignature($this->_config->consumer->access_secret)) { 0075 $this->_logger->err(__METHOD__ . ' - Abort Dwolla callback processing. Message not valid - ' . print_r($rawData, true) . PHP_EOL); 0076 return; 0077 } 0078 0079 if (false === $this->validateTransaction()) { 0080 $this->_logger->err(__METHOD__ . ' - Abort Dwolla callback processing. Transaction not valid - ' . print_r($rawData, true) . PHP_EOL); 0081 return; 0082 } 0083 0084 $this->_processPaymentStatus(); 0085 } 0086 0087 /** 0088 * @param $rawData 0089 * @return array 0090 */ 0091 protected function _decodeData($rawData) 0092 { 0093 return json_decode($rawData, true); 0094 } 0095 0096 protected function validateTransaction() 0097 { 0098 0099 return $this->_checkCheckoutId() AND $this->_checkAmount(); 0100 0101 } 0102 0103 /** 0104 * Check CheckoutId has not already been used. 0105 * Override this method to ensure CheckoutId is not a duplicate. 0106 * Throw an Exception if data is invalid or other things go wrong. 0107 */ 0108 protected function _checkCheckoutId() 0109 { 0110 // check that CheckoutId has not been previously processed 0111 0112 $this->_logger->err(__METHOD__ . ' - not implemented - ' . PHP_EOL); 0113 0114 return false; 0115 } 0116 0117 /** 0118 * Check that the amount is correct for item_id. 0119 * You should override this method to ensure the amount is correct. 0120 * Throw an Exception if data is invalid or other things go wrong. 0121 */ 0122 protected function _checkAmount() 0123 { 0124 // check that payment_amount/payment_currency are correct 0125 0126 $this->_logger->err(__METHOD__ . ' - not implemented - ' . PHP_EOL); 0127 0128 return false; 0129 } 0130 0131 protected function _processPaymentStatus() 0132 { 0133 switch ($this->_transactionMessage->getStatus()) { 0134 case 'COMPLETED': 0135 $this->_statusCompleted(); 0136 break; 0137 case 'FAILED': 0138 $this->_statusError(); 0139 break; 0140 case 'PENDING': 0141 $this->_statusPending(); 0142 break; 0143 case 'PROCESSED': 0144 $this->_statusProcessed(); 0145 break; 0146 case 'CANCELLED': 0147 $this->_statusCancelled(); 0148 break; 0149 default: 0150 throw new Exception('Unknown status from dwolla: ' . $this->_dataCallback['Status']); 0151 } 0152 0153 } 0154 0155 /** 0156 * Transaction/Payment completed. 0157 * 0158 * This is typically the most important method you'll need to override to perform 0159 * some sort of action when a successful transaction has been completed. 0160 * 0161 * You could override the other status's (such as reverse or denied) to 0162 * reverse whatever was done, but that could interfere if you're denying a 0163 * payment or refunding someone for a good reason. In those cases, it's 0164 * probably best to simply do whatever steps are required manually. 0165 */ 0166 protected function _statusCompleted() 0167 { 0168 $this->_logger->err(__METHOD__ . ' - not implemented - ' . PHP_EOL); 0169 } 0170 0171 /** 0172 * The payment failed and all attempted transfers failed or all completed transfers were successfully reversed 0173 */ 0174 protected function _statusError() 0175 { 0176 $this->_logger->err(__METHOD__ . ' - not implemented - ' . PHP_EOL); 0177 } 0178 0179 protected function _statusPending() 0180 { 0181 $this->_logger->err(__METHOD__ . ' - not implemented - ' . PHP_EOL); 0182 } 0183 0184 protected function _statusProcessed() 0185 { 0186 $this->_logger->err(__METHOD__ . ' - not implemented - ' . PHP_EOL); 0187 } 0188 0189 protected function _statusCancelled() 0190 { 0191 $this->_logger->err(__METHOD__ . ' - not implemented - ' . PHP_EOL); 0192 } 0193 0194 /** 0195 * @param $proposedSignature 0196 * @param $checkoutId 0197 * @param $amount 0198 * @return bool 0199 */ 0200 protected function verifyGatewaySignature($proposedSignature, $checkoutId, $amount) 0201 { 0202 $amount = number_format($amount, 2); 0203 $signature = hash_hmac("sha1", "{$checkoutId}&{$amount}", $this->_config->consumer->access_secret); 0204 0205 return $signature == $proposedSignature; 0206 } 0207 0208 protected function verifyWebhookSignature($body) 0209 { 0210 // Get Dwolla's signature 0211 $headers = getallheaders(); 0212 $signature = $headers['X-Dwolla-Signature']; 0213 0214 // Calculate hash, and compare to the signature 0215 $hash = hash_hmac('sha1', $body, $this->_config->consumer->access_secret); 0216 0217 return ($hash == $signature); 0218 } 0219 0220 private function fetchDetailedTransactionInformation() 0221 { 0222 // Create request body 0223 $requestBody = array(); 0224 0225 $response = $this->_makeRequest($requestBody, 'rest/transactions', false); 0226 if (APPLICATION_ENV != 'production') { 0227 $this->_logger->debug(__METHOD__ . ' - response - ' . print_r($response, true) . PHP_EOL); 0228 } 0229 0230 $lastResponse = new Local_Payment_Dwolla_ResponsePayRequest($response); 0231 0232 if (false === $lastResponse->isSuccessful()) { 0233 throw new Local_Payment_Exception('Dwolla payment request failed. Request response:' . print_r($lastResponse->getRawMessage(), 0234 true)); 0235 } 0236 0237 return $lastResponse; 0238 } 0239 0240 /** 0241 * @param array $request 0242 * @param string $apiNameOperation 0243 * @param bool $withAuthHeader 0244 * @throws Local_Payment_Exception 0245 * @return array 0246 */ 0247 protected function _makeRequest($request, $apiNameOperation, $withAuthHeader = true) 0248 { 0249 $url = $this->_config->api->endpoint . '/' . $apiNameOperation; 0250 $http = new Zend_Http_Client($url); 0251 if (true === $withAuthHeader) { 0252 $http->setHeaders($this->_buildHeader($this->_config)); 0253 } 0254 $http->setHeaders('Content-Type', 'application/json'); 0255 $http->setMethod(Zend_Http_Client::POST); 0256 $http->setRawData(json_encode($request)); 0257 0258 try { 0259 $response = $http->request(); 0260 } catch (Zend_Http_Client_Exception $e) { 0261 throw new Local_Payment_Exception('Error while request Dwolla website.', 0, $e); 0262 } 0263 0264 if (false === $response) { 0265 $logMsg = __METHOD__ . "::Error while request Dwolla Website.\n Server replay was: " . $http->getLastResponse()->getStatus() . PHP_EOL . $http->getLastResponse()->getMessage() . PHP_EOL; 0266 $logMsg .= __METHOD__ . '::' . print_r($http->getLastRequest(), true) . PHP_EOL; 0267 $logMsg .= __METHOD__ . '::' . print_r($response->getHeaders(), true) . PHP_EOL; 0268 $logMsg .= __METHOD__ . '::' . print_r($response->getBody(), true) . PHP_EOL; 0269 $this->_logger->err($logMsg); 0270 throw new Local_Payment_Exception('Error while request Dwolla website.'); 0271 } else { 0272 $logMsg = __METHOD__ . '::' . print_r($http->getLastRequest(), true) . PHP_EOL; 0273 $logMsg .= __METHOD__ . '::' . print_r($response->getHeaders(), true) . PHP_EOL; 0274 $logMsg .= __METHOD__ . '::' . print_r($response->getBody(), true) . PHP_EOL; 0275 $this->_logger->debug($logMsg); 0276 } 0277 0278 return json_decode($response->getBody(), true); 0279 } 0280 0281 }