'Transaction was approved.', 200 => 'Transaction was declined by processor.', 201 => 'Do not honor.', 202 => 'Insufficient funds.', 203 => 'Over limit.', 204 => 'Transaction not allowed.', 220 => 'Incorrect payment information.', 221 => 'No such card issuer.', 222 => 'No card number on file with issuer.', 223 => 'Expired card.', 224 => 'Invalid expiration date.', 225 => 'Invalid card security code.240 Call issuer for further information.', 250 => 'Pick up card.', 251 => 'Lost card.', 252 => 'Stolen card.', 253 => 'Fraudulent card.', 260 => 'Declined with further instructions available.', 261 => 'Declined-Stop all recurring payments.', 262 => 'Declined-Stop this recurring program.', 263 => 'Declined-Update cardholder data available.', 264 => 'Declined-Retry in a few days.', 300 => 'Transaction was rejected by gateway.', 400 => 'Transaction error returned by processor.', 410 => 'Invalid merchant configuration.', 411 => 'Merchant account is inactive.', 420 => 'Communication error.', 421 => 'Communication error with issuer.', 430 => 'Duplicate transaction at processor.', 440 => 'Processor format error.', 441 => 'Invalid transaction information.', 460 => 'Processor feature not available.', 461 => 'Unsupported card type.', ]; public function __construct(array $options = []) { Qs_Options::setOptions($this, $options); $this->getHttpClient()->setOptions([ 'maxredirects' => 0, 'timeout' => 180, 'keepalive' => false, 'adapter' => 'Zend\Http\Client\Adapter\Curl', ]); } /** * @param array $query * @return string transactionId */ public function sale(array $query) { $query = array_merge(['type' => self::TYPE_SALE], $query); $response = $this->post($query); return $response['transactionid']; } private function post(array $query) { return $this->request(HttpRequest::METHOD_POST, $query); } /** * @param $method * @param array $query * @return string $transactionId * @throws Exception */ private function request($method, array $query = []) { $this->getHttpClient()->setUri(self::GATEWAY)->setMethod($method); $query = array_merge([ 'username' => $this->getUsername(), 'password' => $this->getPassword(), ], $query); if (HttpRequest::METHOD_POST == $method) { $this->getHttpClient()->setParameterPost($query); } else { $this->getHttpClient()->setParameterGet($query); } try { $httpResponse = $this->getHttpClient()->send(); } catch (PhpException $e) { $this->emergency($e->getMessage() . $this->httpDump()); throw new Exception($e->getMessage()); } if ($httpResponse->getStatusCode() != HttpResponse::STATUS_CODE_200) { $message = 'Invalid response payment gateway: code ' . $httpResponse->getStatusCode() . '; message: ' . $httpResponse->getReasonPhrase(); $this->emergency($message . $this->httpDump()); throw new Exception($message); } $body = $httpResponse->getBody(); $response = []; parse_str($body, $response); if (1 != Qs_Array::get($response, 'response')) { $message = ''; if (($code = Qs_Array::get($response, 'response_code')) && ($_message = $this->getResponseMessage($code))) { $message = $_message . ' '; } if (!empty($response['responsetext'])) { $message .= $response['responsetext']; } if (empty($message)) { $message = 'Invalid response form payment gateway'; } $this->emergency($message . $this->httpDump()); throw new Exception($message); } return $response; } public function getUsername() { return $this->username; } public function setUsername($username) { $this->username = $username; return $this; } public function getPassword() { return $this->password; } public function setPassword($password) { $this->password = $password; return $this; } public function getResponseMessage($code) { return Qs_Array::get($this->responseCodes, $code); } public function getLogFilePattern() { return $this->logFilePattern; } public function setLogFilePattern($logFilePattern) { $this->logFilePattern = $logFilePattern; return $this; } public function log($level, $message, array $context = []) { $file = Qs_String::fill($this->getLogFilePattern(), [ 'year' => idate('Y'), 'month' => date('m'), 'date' => date('Y-m-d'), ]); $dir = dirname($file); if (!file_exists($dir) && false === mkdir($dir, 0755, true)) { trigger_error('Failed to create directory: ' . $dir); return; } $handle = fopen($file, 'a+'); $indent = "\t"; $text = date('Y-m-d H:i:s') . $indent . strtoupper($level) . $indent . Qs_String::fill($message, $context) . PHP_EOL; fwrite($handle, $text); fclose($handle); } private function maskSecureData($message) { $matches = []; if (preg_match('/ccnumber=([\d]+)/', $message, $matches)) { $ccNumber = $matches[1]; $maskedCcNumber = preg_replace('/[\d]/', 'X', substr($ccNumber, 0, -4)) . substr($ccNumber, -4); $message = str_replace($ccNumber, $maskedCcNumber, $message); } $message = preg_replace('/ccexp=[\d]+/', 'ccexp=XXXX', $message); $message = preg_replace_callback('/cvv=([\d]+)/', function ($matches) { return 'cvv=' . str_repeat('X', strlen($matches[1])); }, $message); return $message; } private function httpDump() { if (!($request = $this->getHttpClient()->getLastRawRequest())) { return null; } $line = str_repeat('-', 120) . PHP_EOL; $doubleLine = str_repeat('=', 120) . PHP_EOL; return PHP_EOL . $doubleLine . "Request\n{$line}" . $this->maskSecureData($request) . PHP_EOL . "{$line}Response\n{$line}" . $this->getHttpClient()->getLastRawResponse() . PHP_EOL . $doubleLine; } }