Skip to content

Commit

Permalink
Improved error handling and added error logging
Browse files Browse the repository at this point in the history
The Rapid Client now accepts a PSR-3 compliant logger instance for logging
errors. It also comes with a very basic logger Eway\Rapid\Service\Logger
which will log to PHP's error log.

Version bumped to v1.2.2
  • Loading branch information
incarnate committed Feb 22, 2016
1 parent 6d0212b commit 55a462d
Show file tree
Hide file tree
Showing 10 changed files with 318 additions and 43 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

All notable changes will be documented in this file

## 1.2.2

- Added support for a PSR-3 logger to log errors
- Very basic input validation for some functions
- Added handling of extra HTTP headers from proxies

## 1.2.1

- Changed create and update customer to use MOTO for TransactionType to support not sending the CVN

## 1.2.0

- Added support for Settlement Search
Expand Down
14 changes: 7 additions & 7 deletions src/Rapid.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* <code>
* $apiKey = 'YOUR-API-KEY';
* $apiPassword = 'YOUR-API-PASSWORD';
* $apiEndpoint = \Eway\Rapid\Contract::MODE_SANDBOX;
* $apiEndpoint = 'Sandbox';
* $client = \Eway\Rapid::createClient($apiKey, $apiPassword, $apiEndpoint);
* </code>
*
Expand All @@ -31,19 +31,19 @@ abstract class Rapid
private static $messages = null;

/**
* Static method to create a new Rapid SDK Client object configured to communicate with a specific instance of the
* Rapid API. In some languages it may be appropriate to use a constructor with parameters instead of a static
* method.
* Static method to create a new Rapid Client object configured to communicate with a specific instance of the
* Rapid API.
*
* @param string $apiKey eWAY Rapid API key
* @param string $apiPassword eWAY Rapid API password
* @param string $endpoint eWAY Rapid API endpoint
* @param string $endpoint eWAY Rapid API endpoint - one of 'Sandbox' or 'Production'
* @param Psr\Log\LoggerInterface $logger PSR-3 logger
*
* @return ClientContract an eWAY Rapid Client
*/
public static function createClient($apiKey, $apiPassword, $endpoint = ClientContract::MODE_SANDBOX)
public static function createClient($apiKey, $apiPassword, $endpoint = ClientContract::MODE_SANDBOX, $logger = null)
{
return new Client($apiKey, $apiPassword, $endpoint);
return new Client($apiKey, $apiPassword, $endpoint, $logger);
}

/**
Expand Down
108 changes: 86 additions & 22 deletions src/Rapid/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Eway\Rapid\Enum\ApiMethod;
use Eway\Rapid\Enum\PaymentMethod;
use Eway\Rapid\Enum\TransactionType;
use Eway\Rapid\Enum\LogLevel;
use Eway\Rapid\Exception\MassAssignmentException;
use Eway\Rapid\Exception\MethodNotImplementedException;
use Eway\Rapid\Exception\RequestException;
Expand Down Expand Up @@ -52,11 +53,9 @@ class Client implements ClientContract
private $apiPassword;

/**
* Possible values ("Production", "Sandbox", or a URL) Production and sandbox
* Possible values ("Production", "Sandbox", or a URL) "Production" and "Sandbox"
* will default to the Global Rapid API Endpoints.
*
* use \Eway\Rapid\Client::MODE_SANDBOX or \Eway\Rapid\Client::MODE_PRODUCTION
*
* @var string
*/
private $endpoint;
Expand All @@ -80,13 +79,22 @@ class Client implements ClientContract
*/
private $httpService;

/**
* @var Psr\Log\LoggerInterface
*/
private $logger;

/**
* @param string $apiKey
* @param string $apiPassword
* @param string $endpoint
* @param Psr\Log\LoggerInterface $logger PSR-3 logger
*/
public function __construct($apiKey, $apiPassword, $endpoint)
public function __construct($apiKey, $apiPassword, $endpoint, $logger = null)
{
if (isset($logger)) {
$this->setLogger($logger);
}
$this->setHttpService(new Http());
$this->setCredential($apiKey, $apiPassword);
$this->setEndpoint($endpoint);
Expand Down Expand Up @@ -131,8 +139,7 @@ public function setEndpoint($endpoint)

$this->endpoint = $endpoint;
$this->getHttpService()->setBaseUrl($endpoint);
$this->emptyErrors();
$this->validate();
$this->validateEndpoint();

return $this;
}
Expand All @@ -146,8 +153,17 @@ public function setCredential($apiKey, $apiPassword)
$this->apiPassword = $apiPassword;
$this->getHttpService()->setKey($apiKey);
$this->getHttpService()->setPassword($apiPassword);
$this->emptyErrors();
$this->validate();
$this->validateCredentials();

return $this;
}

/**
* @inheritdoc
*/
public function setLogger($logger)
{
$this->logger = $logger;

return $this;
}
Expand Down Expand Up @@ -254,8 +270,6 @@ public function settlementSearch($query)
public function setHttpService(HttpServiceContract $httpService)
{
$this->httpService = $httpService;
$this->emptyErrors();
$this->validate();

return $this;
}
Expand Down Expand Up @@ -579,15 +593,17 @@ private function wrapResponse($class, $httpResponse = null)
$this->checkResponse($httpResponse);
$body = (string)$httpResponse->getBody();
if (!$this->isJson($body)) {
$this->log('error', "Response is not valid JSON");
$this->addError(self::ERROR_INVALID_JSON);
} else {
$data = json_decode($body, true);
}
} else {
$this->log('error', "Response from gateway is empty");
$this->addError(self::ERROR_EMPTY_RESPONSE);
}
} catch (RequestException $e) {
// An error code is already provided by _checkResponse
// An error code is already provided by checkResponse
}

/** @var AbstractResponse $response */
Expand Down Expand Up @@ -620,19 +636,40 @@ private function emptyErrors()
}

/**
*
* @return $this
*/
private function validate()
public function validateCredentials()
{
$this->isValid = true;
$this->removeError(self::ERROR_INVALID_CREDENTIAL);
if (empty($this->apiKey) || empty($this->apiPassword)) {
$this->addError(self::ERROR_INVALID_CREDENTIAL);
$this->log('error', "Missing API key or password");
$this->addError(self::ERROR_INVALID_CREDENTIAL, false);
}
if (empty($this->endpoint) || strpos($this->endpoint, 'https') !== 0) {
$this->addError(self::ERROR_INVALID_ENDPOINT);

if (empty($this->errors)) {
$this->isValid = true;
}
if (count($this->getErrors()) > 0) {
$this->isValid = false;

return $this;
}

/**
*
* @return $this
*/
public function validateEndpoint()
{
$this->removeError(self::ERROR_INVALID_ENDPOINT);
if (empty($this->endpoint)
|| strpos($this->endpoint, 'https') !== 0
|| substr($this->endpoint, -1) != '/') {
$this->log('error', "Missing or invalid endpoint");
$this->addError(self::ERROR_INVALID_ENDPOINT, false);
}

if (empty($this->errors)) {
$this->isValid = true;
}

return $this;
Expand All @@ -643,14 +680,26 @@ private function validate()
*
* @return $this
*/
private function addError($errorCode)
private function addError($errorCode, $valid = true)
{
$this->isValid = false;
$this->isValid = $valid;
$this->errors[] = $errorCode;

return $this;
}

/**
* @param string $errorCode
*
* @return $this
*/
private function removeError($errorCode)
{
$this->errors = array_diff($this->errors, [$errorCode]);

return $this;
}

/**
* @param $responseClass
*
Expand All @@ -672,12 +721,15 @@ private function checkResponse($response)
{
$hasRequestError = false;
if (preg_match('/4\d\d/', $response->getStatusCode())) {
$this->addError(self::ERROR_HTTP_AUTHENTICATION_ERROR);
$this->log('error', "Invalid API key or password");
$this->addError(self::ERROR_HTTP_AUTHENTICATION_ERROR, false);
$hasRequestError = true;
} elseif (preg_match('/5\d\d/', $response->getStatusCode())) {
$this->log('error', "Gateway error - HTTP " . $response->getStatusCode());
$this->addError(self::ERROR_HTTP_SERVER_ERROR);
$hasRequestError = true;
} elseif ($response->getStatusCode() == 0) {
} elseif (!empty($response->getError())) {
$this->log('error', "Connection error: " . $response->getError());
$this->addError(self::ERROR_CONNECTION_ERROR);
$hasRequestError = true;
}
Expand All @@ -687,5 +739,17 @@ private function checkResponse($response)
}
}

/**
*
* @param string $level
* @param string $message
*/
private function log($level, $message)
{
if (isset($this->logger) && LogLevel::isValidValue($level)) {
$this->logger->$level($message);
}
}

#endregion
}
11 changes: 10 additions & 1 deletion src/Rapid/Contract/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ interface Client
/**
* Rapid SDK Version.
*/
const VERSION = '1.2.1';
const VERSION = '1.2.2';

/**
* Sandbox mode.
Expand Down Expand Up @@ -135,6 +135,15 @@ public function setEndpoint($endpoint);
*/
public function setCredential($apiKey, $apiPassword);

/**
* Sets the PSR-3 compliant logger
*
* @param Psr\Log\LoggerInterface $logger
*
* @return $this
*/
public function setLogger($logger);

/**
* This Method is used to create a transaction for the merchant in their eWAY account.
*
Expand Down
7 changes: 7 additions & 0 deletions src/Rapid/Contract/Http/ResponseInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,11 @@ public function getBody();
* @return int Status code.
*/
public function getStatusCode();

/**
* Gets the error message if one occurred
*
* @return string
*/
public function getError();
}
19 changes: 12 additions & 7 deletions src/Rapid/Enum/LogLevel.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
namespace Eway\Rapid\Enum;

/**
* Class ApiMethod.
* Class LogLevel.
*
* PSR-3 log levels
*/
abstract class ApiMethod extends AbstractEnum
abstract class LogLevel extends AbstractEnum
{
const DIRECT = 'Direct';
const RESPONSIVE_SHARED = 'ResponsiveShared';
const TRANSPARENT_REDIRECT = 'TransparentRedirect';
const WALLET = 'Wallet';
const AUTHORISATION = 'Authorisation';
const EMERGENCY = 'emergency';
const ALERT = 'alert';
const CRITICAL = 'critical';
const ERROR = 'error';
const WARNING = 'warning';
const NOTICE = 'notice';
const INFO = 'info';
const DEBUG = 'debug';
}
1 change: 0 additions & 1 deletion src/Rapid/Model/Support/HasAttributesTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Eway\Rapid\Model\Support;

use Eway\Rapid\Contract\Arrayable;
use Eway\Rapid\Exception\MassAssignmentException;

trait HasAttributesTrait
{
Expand Down
11 changes: 7 additions & 4 deletions src/Rapid/Service/Http.php
Original file line number Diff line number Diff line change
Expand Up @@ -368,13 +368,16 @@ private function request($method, $uri, $data = [])

$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$responseBody = $this->parseResponse($rawResponse, $headerSize);

if ($rawResponse === false) {
$responseBody = curl_error($ch);
if (curl_errno($ch)) {
$responseError = curl_error($ch);
$responseBody = '';
} else {
$responseError = '';
$responseBody = $this->parseResponse($rawResponse, $headerSize);
}

$response = new Response($statusCode, $responseBody);
$response = new Response($statusCode, $responseBody, $responseError);

curl_close($ch);

Expand Down
8 changes: 7 additions & 1 deletion src/Rapid/Service/Http/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ class Response implements ResponseInterface
* @param int $status Status code for the response, if any.
* @param string $body Response body.
*/
public function __construct($status = 200, $body = null)
public function __construct($status = 200, $body = null, $error = null)
{
$this->statusCode = (int)$status;
$this->body = $body;
$this->error = $error;
}

public function getBody()
Expand All @@ -34,4 +35,9 @@ public function getStatusCode()
{
return $this->statusCode;
}

public function getError()
{
return $this->error;
}
}
Loading

0 comments on commit 55a462d

Please sign in to comment.