diff --git a/.gitignore b/.gitignore index c8e3044..3b13038 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /docs /.phpintel composer.phar +couscous.phar # -- Bower -- # diff --git a/src/Api/Api.php b/src/Api/Api.php index 5307d69..bf60e2f 100644 --- a/src/Api/Api.php +++ b/src/Api/Api.php @@ -16,15 +16,9 @@ use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; -use GuzzleHttp\Exception\ConnectException; -use GuzzleHttp\Exception\TransferException; -use GuzzleHttp\HandlerStack; -use GuzzleHttp\Middleware; use OVAC\HubtelPayment\Config; -use OVAC\HubtelPayment\ConfigInterface; use OVAC\HubtelPayment\Exception\Handler; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; +use OVAC\HubtelPayment\Utility\HubtelHandler; /** * Api Class @@ -73,7 +67,7 @@ public function injectConfig(Config $config) /** * Change the Default baseUrl defined by hubtel * - * @param string $baseUrl [description] + * @param string $baseUrl The hubtel Resource Base URL * @return self */ public function setBaseUrl($baseUrl) @@ -142,6 +136,9 @@ public function _options($url = null, array $parameters = []) } /** * {@inheritdoc} + * + * @throws \RuntimeException + * @throws \Handler */ public function execute($httpMethod, $url, array $parameters = []) { @@ -150,15 +147,14 @@ public function execute($httpMethod, $url, array $parameters = []) $response = $this->getClient()->{$httpMethod}($url, ['query' => $parameters]); return json_decode((string) $response->getBody(), true); - } catch (RequestInterface $e) { - throw new ClientException($e->getMessage(), $e); + } catch (ClientException $e) { + throw new Handler($e); } return; } throw new \RuntimeException('The API requires a configuration instance.'); - } /** * Returns an Http client instance. @@ -171,50 +167,20 @@ protected function getClient() return new Client( [ - 'base_uri' => $this->baseUrl . $config->getAccountNumber(), 'handler' => $this->createHandler(), + 'base_uri' => $this->baseUrl . $config->getAccountNumber(), + 'handler' => $this->createHandler($this->config), ] ); } + /** * Create the client handler. * - * @return \GuzzleHttp\HandlerStack - * @SuppressWarnings(PHPMD.StaticAccess) + * @param \OVAC\HubtelPayment\Config $config + * @return \GuzzleHttp\HandlerStack */ - protected function createHandler() + protected function createHandler(Config $config) { - $stack = HandlerStack::create(); - - $stack->push( - Middleware::mapRequest( - function (RequestInterface $request) { - $config = $this->config; - $request = $request->withHeader('User-Agent', 'OVAC-Hubtel-Payment' . $config->getPackageVersion()); - $request = $request->withHeader('Authorization', 'Basic ' . base64_encode($config->getClientId() . ':' . $config->getClientSecret())); - - return $request; - } - ) - ); - - $stack->push( - Middleware::retry( - function ( - $retries, - RequestInterface $request, - ResponseInterface $response = null, - TransferException $exception = null - ) { - return $retries < 3 && ($exception instanceof ConnectException || ( - $response && $response->getStatusCode() >= 500 - )); - }, - function ($retries) { - return (int) pow(2, $retries) * 1000; - } - ) - ); - - return $stack; + return (new HubtelHandler($config))->createHandler(); } } diff --git a/src/Api/Transaction/MassAssignable.php b/src/Api/Transaction/MassAssignable.php index 8c201fe..21d163a 100644 --- a/src/Api/Transaction/MassAssignable.php +++ b/src/Api/Transaction/MassAssignable.php @@ -39,9 +39,10 @@ public function make($data = []) /** * This method is used to mass assign the properties required by the Hubtel ReceiveMoney and SendMoney Api - * @param array $data + * + * @param array $data * @example ['amount' => 10, 'customer' => ['name' => 'victor', ...], 'clientReference' => 123, 'callbackOnSuccess' => 'url', 'amount' => 10, 'description' => 'some description'] - * @return self + * @return self */ protected function massAssign($data = []) { diff --git a/src/Api/Transaction/ReceiveMoney.php b/src/Api/Transaction/ReceiveMoney.php index ef6c6ce..a7c4922 100644 --- a/src/Api/Transaction/ReceiveMoney.php +++ b/src/Api/Transaction/ReceiveMoney.php @@ -16,6 +16,7 @@ use OVAC\HubtelPayment\Api\Transaction\MassAssignable; use OVAC\HubtelPayment\Api\Transaction\Transaction; +use OVAC\HubtelPayment\Utility\CanCleanParameters; /** * Class ReceiveMoney @@ -29,6 +30,7 @@ class ReceiveMoney extends Transaction { use MassAssignable; use Transactable; + use CanCleanParameters; /** * The 6 digit unique token required to debit a Vodafone @@ -48,8 +50,30 @@ class ReceiveMoney extends Transaction * @var boolean */ protected $feesOnCustomer; + /** + * {@inheritdoc} + */ + protected $parametersRequired = [ + 'CustomerName', + 'CustomerMsisdn', + 'Amount', + 'PrimaryCallbackURL', + 'Description', + 'Channel', + ]; + /** + * {@inheritdoc} + */ + protected $parametersOptional = [ + 'CustomerEmail', + 'SecondaryCallbackURL', + 'ClientReference', + 'FeesOnCustomer', + 'Token', + ]; /** * Construct for creating a new instance of the ReceiveMoney Api class + * * @param array $data An array with configurations for the receive money class */ public function __construct($data = []) @@ -130,14 +154,6 @@ public function feesOnCustomer($feesOnCustomer) return $this->setFeesOnCustomer($feesOnCustomer); } - /** - * [run description] - * @return [type] [description] - */ - public function run() - { - } - /** * @return string */ @@ -177,4 +193,15 @@ public function setFeesOnCustomer($feesOnCustomer) return $this; } + /** + * The method runs the transaction + * + * @return + */ + public function run() + { + if ($this->propertiesPassRequired()) { + $this->_get('/receive/mobilemoney', $this->propertiesToArray()); + } + } } diff --git a/src/Api/Transaction/SendMoney.php b/src/Api/Transaction/SendMoney.php index 4d19d55..412ca97 100644 --- a/src/Api/Transaction/SendMoney.php +++ b/src/Api/Transaction/SendMoney.php @@ -32,6 +32,7 @@ class SendMoney extends Transaction /** * Construct for creating a new instance of the SendMoney Api class + * * @param array $data An array with configurations for the send money class */ public function __construct($data = []) diff --git a/src/Api/Transaction/Transaction.php b/src/Api/Transaction/Transaction.php index 5d2e9f6..00d0d0e 100644 --- a/src/Api/Transaction/Transaction.php +++ b/src/Api/Transaction/Transaction.php @@ -87,6 +87,27 @@ class Transaction extends Api */ protected $description; + /** + * Defines an array of required properties + * + * Names must correspond with properties/parameters on the class + * and the properties must accessors defined on the + * instance + * + * @var array + */ + protected $parametersRequired = []; + /** + * Defines an array of the names of optional properties/parameters names + * + * Names must correspond with properties on the class + * and the properties must accessors defined on the + * instance + * + * @var array + */ + protected $parametersOptional = []; + /** * returnes the name of the give customer * @@ -161,6 +182,7 @@ public function setCustomerMsisdn($customerMsisdn) /** * returns the transaction channel (Mobile Network) + * * @return string */ public function getChannel() @@ -281,6 +303,7 @@ public function setClientReference($clientReference) /** * gets the description of the transaction. + * * @return string */ public function getDescription() @@ -335,6 +358,7 @@ public function setCustomer($data = []) } /** * This method sests a single callback for both the success and Error + * * @param string $primaryCallbackURL A url for callbacks from the hubtel server * @return self */ @@ -350,7 +374,8 @@ public function callback($primaryCallbackURL) /** * This method sets the callbacks for the Hubtel Payments - * @param array|string $data + * + * @param array|string $data * @return self */ public function setCallback($data = []) diff --git a/src/Config.php b/src/Config.php index 75966dc..8fc4ef6 100644 --- a/src/Config.php +++ b/src/Config.php @@ -36,7 +36,7 @@ class Config implements ConfigInterface * * @var string */ - protected $accoutNumber; + protected $accountNumber; /** * The Hubtel Developer Applicaton Client Id. * diff --git a/src/Exception/BadRequestException.php b/src/Exception/BadRequestException.php new file mode 100644 index 0000000..0482fca --- /dev/null +++ b/src/Exception/BadRequestException.php @@ -0,0 +1,23 @@ + + * @link http://ovac4u.com + * + * @license https://github.com/ovac/hubtel-payment/blob/master/LICENSE + * @copyright (c) 2017, Rescope Inc + */ + +namespace OVAC\HubtelPayment\Exception; + +/** + * Class BadRequestException + * throws OVAC\HubtelPayment\Exception\BadRequestException + */ +class BadRequestException extends HubtelException +{ +} diff --git a/src/Exception/Handler.php b/src/Exception/Handler.php new file mode 100644 index 0000000..e8dad2f --- /dev/null +++ b/src/Exception/Handler.php @@ -0,0 +1,111 @@ + + * @link http://ovac4u.com + * + * @license https://github.com/ovac/hubtel-payment/blob/master/LICENSE + * @copyright (c) 2017, Rescope Inc + */ + +namespace OVAC\HubtelPayment\Exception; + +use GuzzleHttp\Exception\ClientException; + +/** + * Class Handler + * + * Handles Hubtel Exceptions + * throws OVAC\HubtelPayment\Exception\HubtelException + */ +class Handler +{ + /** + * List of mapped exceptions and their corresponding status codes. + * + * @var array + */ + protected $excByStatusCode = [ + // Often missing a required parameter + 400 => 'BadRequest', + // Invalid ClientID and ClientSecret provided + 401 => 'Unauthorized', + // Parameters were valid but request failed + 402 => 'InvalidRequest', + // The requested resource doesn't exist + 404 => 'NotFound', + // Something went wrong on Hubtel's end + 500 => 'ServerError', + 502 => 'ServerError', + 503 => 'ServerError', + 504 => 'ServerError', + ]; + /** + * Constructor. + * + * @param \GuzzleHttp\Exception\ClientException $exception + * @return void + * @throws OVAC\HubtelPayment\Exception\HubtelException + */ + public function __construct(ClientException $exception) + { + $response = $exception->getResponse(); + $statusCode = $response->getStatusCode(); + $rawOutput = json_decode($response->getBody(true), true); + $error = $rawOutput ?: []; + $errorCode = isset($error['ResponseCode']) ? $error['ResponseCode'] : null; + $errorType = isset($error['type']) ? $error['type'] : null; + $message = isset($error['Message']) ? $error['Message'] : null; + $missingParameter = isset($error['Errors']) ? $this->getMissingParameters($error['param']) : null; + $this->handleException( + $message, $statusCode, $errorType, $errorCode, $missingParameter, $rawOutput + ); + } + /** + * Guesses the FQN of the exception to be thrown. + * + * @param string $message + * @param int $statusCode + * @param string $errorType + * @param string $errorCode + * @param string $missingParameter + * @return void + * @throws OVAC\HubtelPayment\Exception\HubtelException + */ + protected function handleException($message, $statusCode, $errorType, $errorCode, $missingParameter, $rawOutput) + { + if ($statusCode === 400 && $errorCode == 4010) { + $class = 'MissingParameter'; + } elseif (array_key_exists($statusCode, $this->excByStatusCode)) { + $class = $this->excByStatusCode[$statusCode]; + } else { + $class = 'Hubtel'; + } + + $class = '\\OVAC\\HubtelPayment\\Exception\\' . $class . 'Exception'; + $instance = new $class($message, $statusCode); + $instance->setErrorCode($errorCode); + $instance->setErrorType($errorType); + $instance->setMissingParameter($missingParameter); + $instance->setRawOutput($rawOutput); + + throw $instance; + } + + protected function getMissingParameters($errors = []) + { + $missingParameters = []; + + foreach ($errors as $err) { + if (isset($err['Field'])) { + $missingParameters[] = $err['Field']; + } + } + + return implode($missingParameters, ', '); + } +} diff --git a/src/Exception/HubtelException.php b/src/Exception/HubtelException.php new file mode 100644 index 0000000..3afd18d --- /dev/null +++ b/src/Exception/HubtelException.php @@ -0,0 +1,131 @@ + + * @link http://ovac4u.com + * + * @license https://github.com/ovac/hubtel-payment/blob/master/LICENSE + * @copyright (c) 2017, Rescope Inc + */ + +namespace OVAC\HubtelPayment\Exception; + +/** + * Class HubtelException + * throws OVAC\HubtelPayment\Exception\HubtelException + */ +class HubtelException extends \Exception +{ + /** + * The error code returned by Hubtel. + * + * @var string + */ + protected $errorCode; + /** + * The error type returned by Hubtel. + * + * @var string + */ + protected $errorType; + /** + * The missing parameter returned by Hubtel. + * + * @var string + */ + protected $missingParameter; + /** + * The raw output returned by Hubtel in case of exception. + * + * @var string + */ + protected $rawOutput; + /** + * Returns the error type returned by Hubtel. + * + * @return string + */ + public function getErrorCode() + { + return $this->errorCode; + } + /** + * Sets the error type returned by Hubtel. + * + * @param string $errorCode + * @return self + */ + public function setErrorCode($errorCode) + { + $this->errorCode = $errorCode; + + return $this; + } + /** + * Returns the error type returned by Hubtel. + * + * @return string + */ + public function getErrorType() + { + return $this->errorType; + } + /** + * Sets the error type returned by Hubtel. + * + * @param string $errorType + * @return self + */ + public function setErrorType($errorType) + { + $this->errorType = $errorType; + + return $this; + } + /** + * Returns missing parameter returned by Hubtel with the error. + * + * @return string + */ + public function getMissingParameter() + { + return $this->missingParameter; + } + /** + * Sets the missing parameter returned by Hubtel with the error. + * + * @param string $missingParameter + * @return self + */ + public function setMissingParameter($missingParameter) + { + $this->missingParameter = $missingParameter; + + return $this; + } + /** + * Returns raw output returned by Hubtel in case of exception. + * + * @return string + */ + public function getRawOutput() + { + return $this->rawOutput; + } + /** + * Sets the raw output parameter returned by Hubtel in case of exception. + * + * @param string $rawOutput + * @return self + */ + public function setRawOutput($rawOutput) + { + $this->rawOutput = $rawOutput; + + return $this; + } +} diff --git a/src/Exception/InvalidRequestException.php b/src/Exception/InvalidRequestException.php new file mode 100644 index 0000000..7497f09 --- /dev/null +++ b/src/Exception/InvalidRequestException.php @@ -0,0 +1,23 @@ + + * @link http://ovac4u.com + * + * @license https://github.com/ovac/hubtel-payment/blob/master/LICENSE + * @copyright (c) 2017, Rescope Inc + */ + +namespace OVAC\HubtelPayment\Exception; + +/** + * Class InvalidRequestException + * throws OVAC\HubtelPayment\Exception\InvalidRequestException + */ +class InvalidRequestException extends HubtelException +{ +} diff --git a/src/Exception/MissingParameterException.php b/src/Exception/MissingParameterException.php new file mode 100644 index 0000000..810694b --- /dev/null +++ b/src/Exception/MissingParameterException.php @@ -0,0 +1,23 @@ + + * @link http://ovac4u.com + * + * @license https://github.com/ovac/hubtel-payment/blob/master/LICENSE + * @copyright (c) 2017, Rescope Inc + */ + +namespace OVAC\HubtelPayment\Exception; + +/** + * Class MissingParameterException + * throws OVAC\HubtelPayment\Exception\MissingParameterException + */ +class MissingParameterException extends HubtelException +{ +} diff --git a/src/Exception/NotFoundException.php b/src/Exception/NotFoundException.php new file mode 100644 index 0000000..13a0a38 --- /dev/null +++ b/src/Exception/NotFoundException.php @@ -0,0 +1,23 @@ + + * @link http://ovac4u.com + * + * @license https://github.com/ovac/hubtel-payment/blob/master/LICENSE + * @copyright (c) 2017, Rescope Inc + */ + +namespace OVAC\HubtelPayment\Exception; + +/** + * Class NotFoundException + * throws OVAC\HubtelPayment\Exception\NotFoundException + */ +class NotFoundException extends HubtelException +{ +} diff --git a/src/Exception/ServerErrorException.php b/src/Exception/ServerErrorException.php new file mode 100644 index 0000000..4c0e30a --- /dev/null +++ b/src/Exception/ServerErrorException.php @@ -0,0 +1,23 @@ + + * @link http://ovac4u.com + * + * @license https://github.com/ovac/hubtel-payment/blob/master/LICENSE + * @copyright (c) 2017, Rescope Inc + */ + +namespace OVAC\HubtelPayment\Exception; + +/** + * Class ServerErrorException + * throws OVAC\HubtelPayment\Exception\ServerErrorException + */ +class ServerErrorException extends HubtelException +{ +} diff --git a/src/Exception/UnauthorizedException.php b/src/Exception/UnauthorizedException.php new file mode 100644 index 0000000..980feff --- /dev/null +++ b/src/Exception/UnauthorizedException.php @@ -0,0 +1,23 @@ + + * @link http://ovac4u.com + * + * @license https://github.com/ovac/hubtel-payment/blob/master/LICENSE + * @copyright (c) 2017, Rescope Inc + */ + +namespace OVAC\HubtelPayment\Exception; + +/** + * Class UnauthorizedException + * throws OVAC\HubtelPayment\Exception\UnauthorizedException + */ +class UnauthorizedException extends HubtelException +{ +} diff --git a/src/Utility/CanCleanParameters.php b/src/Utility/CanCleanParameters.php new file mode 100644 index 0000000..805904d --- /dev/null +++ b/src/Utility/CanCleanParameters.php @@ -0,0 +1,97 @@ + + * @link http://ovac4u.com + * + * @license https://github.com/ovac/hubtel-payment/blob/master/LICENSE + * @copyright (c) 2017, Rescope Inc + */ + +namespace OVAC\HubtelPayment\Utility; + +use OVAC\HubtelPayment\Exception\MissingParameterException; + +/** + * Trait CanCleanParameters + */ +trait CanCleanParameters +{ + /** + * This method checks that all properties marked as + * required have been assigned a value. + * + * A protected $parametersRequired property must contain the names of + * the required parameters on the class that will use this trait method. + * and all parameters must each have a defined get accessor on the + * class object instance + * + * @return void + * @throws OVAC\HubtelPayment\Exception\MissingParameterException + */ + protected function propertiesPassRequired() + { + foreach ($this->parametersRequired as $key) { + if ($this->accessPropertyByKey($key)) { + return true; + } + + throw new MissingParameterException('The ' . $key . ' parameter is required'); + } + } + /** + * This method picks up all the defined properties the + * $parameterRequired|$parameterOptional property + * array list from the class object and returns + * an array containing each list item name as + * a key and the matching property value from + * the class + * + * @return array An array of parameters with values + */ + protected function propertiesToArray() + { + $properties = [ + $this->parametersRequired, + $this->parametersOptional, + ]; + + $cleanProperty = []; + + foreach ($properties as $array) { + foreach ($array as $key) { + if ($this->accessPropertyByKey($key)) { + $cleanProperty[$key] = $this->accessPropertyByKey($key); + } + } + } + + return $cleanProperty; + } + /** + * This method calls the accessors for keys passed in + * and returns back the value it receives from the + * class instance + * + * throws an error if a defined parameter in the + * $parameterRequired|$parameterOptional does + * not have a reachable get[PropertyName] accessor + * defined on the class instance. + * + * @param string $key /$parameterRequired[(*)]|$parameterOptional[(*)]/ + * @return mixed + * @throws \BadMethodCallException + */ + protected function accessPropertyByKey($key) + { + try { + $this->{'get' . ucwords($key)}(); + } catch (BadMethodCallException $e) { + throw new \RunitimeException('The ' . $key . ' parameter must have a defined get' . ucwords($key) . ' method.'); + } + } +} diff --git a/src/Utility/HubtelHandler.php b/src/Utility/HubtelHandler.php new file mode 100644 index 0000000..3fa6e1f --- /dev/null +++ b/src/Utility/HubtelHandler.php @@ -0,0 +1,199 @@ + + * @link http://ovac4u.com + * + * @license https://github.com/ovac/hubtel-payment/blob/master/LICENSE + * @copyright (c) 2017, Rescope Inc + */ + +namespace OVAC\HubtelPayment\Utility; + +use GuzzleHttp\Client; +use GuzzleHttp\Exception\ConnectException; +use GuzzleHttp\Exception\TransferException; +use GuzzleHttp\HandlerStack; +use GuzzleHttp\Middleware; +use OVAC\HubtelPayment\Config; +use OVAC\HubtelPayment\Exception\Handler; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Hubtel Handler Class + * + * This class holds the hubtel handler and can + * be used with any Guzzle client to queue + * requests and middlewares. + * + * @see http://docs.guzzlephp.org/en/stable/handlers-and-middleware.html Guzzle: Handlers and Middlewares. + */ +class HubtelHandler +{ + /** + * Property for holding the handler stack on the instance + * + * @var \GuzzleHttp\HandlerStack + */ + protected $stack; + /** + * Holds the configuration object + * + * @var \OVAC\HubtelPayment\Config + */ + protected $config; + /** + * Constructor for the HubtelHandler class + * + * @param \OVAC\HubtelPayment\Config $config + */ + public function __construct(Config $config) + { + $this->config = $config; + $this->stack = HandlerStack::create(); + } + /** + * Create the client handler. + * + * @return \GuzzleHttp\HandlerStack + * @see http://docs.guzzlephp.org/en/stable/handlers-and-middleware.html Guzzle: Handlers and Middlewares. + */ + public function createHandler() + { + $this->pushHeaderMiddleware( + function (RequestInterface $request) { + return $request->withHeader('User-Agent', 'OVAC-Hubtel-Payment' . $this->config->getPackageVersion()); + } + ); + + $this->pushHeaderMiddleware( + function (RequestInterface $request) { + return $request->withHeader( + 'Authorization', 'Basic ' . base64_encode( + $this->config->getClientId() . ':' . $this->config->getClientSecret() + ) + ); + } + ); + + $this->pushRetryMiddleware(self::decider(), self::delay()); + + return $this->stack; + } + + /** + * Push the Header Middleware to the Handler Stack + * + * @param callable $delay Function that accepts a Guzzle RequestInterface + * and returns a RequestInterface. + * @return \GuzzleHttp\HandlerStack + * @see http://docs.guzzlephp.org/en/stable/handlers-and-middleware.html Guzzle: Handlers and Middlewares. + */ + public function pushHeaderMiddleware(callable $delay) + { + $this->stack->push( + Middleware::mapRequest($delay) + ); + + return $this->stack; + } + + /** + * Pushes a Retry Middleware to the Guzzle Client Handler Stack. + * + * Middleware that retries requests based on the boolean result of + * invoking the provided "decider" function. + * + * If no delay function is provided, a simple implementation of exponential + * backoff will be utilized. + * + * @param callable $decider Function that accepts the number of retries, + * a request, [response], and [exception] and + * returns true if the request is to be retried. + * @param callable $delay Function that accepts the number of retries and + * returns the number of milliseconds to delay. + * @return \GuzzleHttp\HandlerStack + * @see http://docs.guzzlephp.org/en/stable/handlers-and-middleware.html Guzzle: Handlers and Middlewares. + */ + public function pushRetryMiddleware(callable $decider, callable $delay = null) + { + $this->stack->push( + Middleware::retry($decider, $delay) + ); + + return $this->stack; + } + /** + * Function $decider callable + * + * Retuens a function that accepts the number of retries, + * a request, [response], and [exception] and + * returns true if the request is to be retried. + * + * @return callable $decider + * @see http://docs.guzzlephp.org/en/stable/handlers-and-middleware.html Guzzle: Handlers and Middlewares. + */ + protected static function decider() + { + + /** + * Function $decider callable + * + * This Function accepts the number of retries, + * a request, [response], and [exception] and + * returns true if the request is to be retried. + * + * @param number $retries The number of times left to retry + * @param \Psr\Http\Message\RequestInterface $request The request Object + * @param \Psr\Http\Message\ResponseInterface $response The response Object + * @param \GuzzleHttp\Exception\TransferException $exception Guzzle Response Exception Object + * @return boolean This boolean determines if to retry or not. + * @see http://docs.guzzlephp.org/en/stable/handlers-and-middleware.html Guzzle: Handlers and Middlewares. + */ + + return function ( + $retries, + RequestInterface $request, + ResponseInterface $response = null, + TransferException $exception = null + ) { + return ($retries < 3) && ($exception instanceof ConnectException || ( + $response && $response->getStatusCode() >= 500 + )) && $request; + }; + } + /** + * Function $delay + * + * returns a Function that accepts the number of retries and + * returns the number of milliseconds to delay. + * + * The function is passed to the Retry Middleware as the second + * arguement and then pushed into the handler stack. + * + * @see http://docs.guzzlephp.org/en/stable/handlers-and-middleware.html Guzzle: Handlers and Middlewares. + */ + protected static function delay() + { + /** + * A Function that accepts the number of retries and + * returns the number of milliseconds to delay. + * + * This function is passed to the Retry Middle ware as the second + * argument and then pushed into the handler stack. + * + * @param number $retries The number of times left to retry. + * @return number The number of mili-seconds between the delay + * @see http://docs.guzzlephp.org/en/stable/handlers-and-middleware.html Guzzle: Handlers and Middlewares. + */ + + return function ($retries) { + return (int) pow(2, $retries) * 1000; + }; + } +} diff --git a/tests/Unit/Api/ApiTest.php b/tests/Unit/Api/ApiTest.php index a2ecb70..349ae00 100644 --- a/tests/Unit/Api/ApiTest.php +++ b/tests/Unit/Api/ApiTest.php @@ -14,13 +14,12 @@ namespace OVAC\HubtelPayment\Tests\Unit\Api; -use GuzzleHttp\Exception\ClientException; use GuzzleHttp\HandlerStack; use GuzzleHttp\Handler\MockHandler; -use GuzzleHttp\Middleware; use GuzzleHttp\Psr7\Response; use OVAC\HubtelPayment\Api\Api; use OVAC\HubtelPayment\Config; +use OVAC\HubtelPayment\Exception\UnauthorizedException; use OVAC\HubtelPayment\Pay; use PHPUnit\Framework\TestCase; @@ -127,45 +126,33 @@ public function test_api_execute_methods_requires_config() $mock->_get($path, $parameters); } - public function test_api_execute_throws_ClientException_is_server_gives_error() + public function test_api_execute_throws_ClientException_if_server_gives_error() { - // $this->expectException(ClientException::class); + $this->expectException(UnauthorizedException::class); - try { - $httpMock = new MockHandler([ - new Response(401, ['X-Foo' => 'Bar']), - ]); - - $handler = HandlerStack::create($httpMock); - - $path = '/some_path'; - $parameters = ['hello' => 'world']; + $httpMock = new MockHandler([ + new Response(401, ['X-Foo' => 'Bar']), + ]); - $mock = $this->getMockBuilder(Api::class) - ->setConstructorArgs([$this->config]) - ->setMethods(['createHandler']) - ->getMockForAbstractClass(); + $handler = HandlerStack::create($httpMock); - $mock->expects($this->once()) - ->method('createHandler') - ->will($this->returnValue($handler)); + $path = '/some_path'; + $parameters = ['hello' => 'world']; - $mock->_get($path, $parameters); - } catch (ClientException $e) { - $this->assertTrue(!!$e); + $mock = $this->getMockBuilder(Api::class) + ->setConstructorArgs([$this->config]) + ->setMethods(['createHandler']) + ->getMockForAbstractClass(); - return; - } + $mock->expects($this->once()) + ->method('createHandler') + ->will($this->returnValue($handler)); - $this->fail(); + $mock->_get($path, $parameters); } public function test_api_execute_test_successful_call() { - - $container = []; - $history = Middleware::history($container); - $path = '/some_path'; $parameters = ['hello' => 'world']; @@ -187,12 +174,6 @@ public function test_api_execute_test_successful_call() $response = $mock->_get($path, $parameters); $this->assertSame(json_encode($response), json_encode($parameters)); - - // Iterate over the requests and responses - foreach ($container as $transaction) { - $this->assertSame($transaction['request']->getMethod(), 'GET'); - $this->assertSame($transaction['response']->getStatusCode(), 200); - } } public static function callProtectedMethod($object, $method, array $args = array()) @@ -209,7 +190,7 @@ public function test_api_createHandler_method() ->setConstructorArgs([$this->config]) ->getMockForAbstractClass(); - $handlerStack = self::callProtectedMethod($mock, 'createHandler'); + $handlerStack = self::callProtectedMethod($mock, 'createHandler', [$this->config]); self::assertInstanceOf(HandlerStack::class, $handlerStack); } diff --git a/tests/Unit/Api/Transaction/ReceiveMoneyTest.php b/tests/Unit/Api/Transaction/ReceiveMoneyTest.php index a396e47..d07d0d6 100644 --- a/tests/Unit/Api/Transaction/ReceiveMoneyTest.php +++ b/tests/Unit/Api/Transaction/ReceiveMoneyTest.php @@ -16,6 +16,7 @@ use OVAC\HubtelPayment\Api\Transaction\ReceiveMoney; use OVAC\HubtelPayment\Config; +use OVAC\HubtelPayment\Exception\MissingParameterException; use OVAC\HubtelPayment\Pay; use PHPUnit\Framework\TestCase; @@ -338,7 +339,19 @@ public function testSetCalbackExpressiveKeys() 'callbackOnSuccess' => $this->primaryCallbackURL, )); - $this->assertEquals($api->getSecondaryCallbackURL(), $this->secondaryCallbackURL, 'it should be the error callback URL'); - $this->assertEquals($api->getPrimaryCallbackURL(), $this->primaryCallbackURL, 'it should be the success callback URL'); + $this->assertEquals( + $api->getSecondaryCallbackURL(), $this->secondaryCallbackURL, 'it should be the error callback URL' + ); + + $this->assertEquals( + $api->getPrimaryCallbackURL(), $this->primaryCallbackURL, 'it should be the success callback URL' + ); + } + + public function test_run_incomplete_required_throws_error_call() + { + $this->expectException(MissingParameterException::class); + + (new ReceiveMoney)->run(); } } diff --git a/tests/Unit/Utility/CanCleanParametersTest.php b/tests/Unit/Utility/CanCleanParametersTest.php new file mode 100644 index 0000000..5a1e8f1 --- /dev/null +++ b/tests/Unit/Utility/CanCleanParametersTest.php @@ -0,0 +1,78 @@ + + * @link http://ovac4u.com + * + * @license https://github.com/ovac/hubtel-payment/blob/master/LICENSE + * @copyright (c) 2017, Rescope Inc + */ + +namespace OVAC\HubtelPayment\Tests\Unit\Utility; + +use OVAC\HubtelPayment\Config; +use OVAC\HubtelPayment\Pay; +use PHPUnit\Framework\TestCase; + +class CanCleanParametersTest extends TestCase +{ + + /** + * The OVAC/Hubtel-Payment Pay config. + * + * @var \OVAC\Hubtel\Config + */ + protected $config; + + protected function setUp() + { + $this->config = new Config( + Pay::VERSION, + $accountNumber = 12345, + $clientId = 'someClientId', + $clientSecret = 'someClientSecret' + ); + } + + public function test_api_execute_test_successful_call() + { + // $mock = $this->getMockBuilder(HubtelHandler::class) + // ->setConstructorArgs([$this->config]) + // ->setMethods(['pushHeaderMiddleware', 'pushRetryMiddleware']) + // ->getMockForAbstractClass(); + + // $requestMock = $this->createMock(RequestInterface::class, ['withHeader']); + // $responseMock = $this->createMock(ResponseInterface::class); + // $tfException = $this->createMock(TransferException::class); + + // $requestMock->expects($this->exactly(2))->method('withHeader'); + + // $mock->expects($this->exactly(2))->method('pushHeaderMiddleware')->with( + // $this->callback(function (callable $callable) use ($requestMock) { + // $callable($requestMock); + + // return true; + // }) + // ); + + // $mock->expects($this->once())->method('pushRetryMiddleware')->with( + // $this->callback(function (callable $callable) use ($requestMock, $responseMock, $tfException) { + // $callable(10, $requestMock, $responseMock, $tfException); + // return $callable; + // }), + + // $this->callback(function (callable $callable) { + // $result = $callable(10); + // $this->assertTrue(is_int($result), 'Expects the decider function to return a number in mili-seconds'); + + // return true; + // }) + // ); + + // $response = $mock->createHandler(); + } +} diff --git a/tests/Unit/Utility/HubtelHandlerTest.php b/tests/Unit/Utility/HubtelHandlerTest.php new file mode 100644 index 0000000..75eda55 --- /dev/null +++ b/tests/Unit/Utility/HubtelHandlerTest.php @@ -0,0 +1,82 @@ + + * @link http://ovac4u.com + * + * @license https://github.com/ovac/hubtel-payment/blob/master/LICENSE + * @copyright (c) 2017, Rescope Inc + */ + +namespace OVAC\HubtelPayment\Tests\Unit\Utility; + +use GuzzleHttp\Exception\TransferException; +use OVAC\HubtelPayment\Config; +use OVAC\HubtelPayment\Pay; +use OVAC\HubtelPayment\Utility\HubtelHandler; +use PHPUnit\Framework\TestCase; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +class HubtelHandlerTest extends TestCase +{ + + /** + * The OVAC/Hubtel-Payment Pay config. + * + * @var \OVAC\Hubtel\Config + */ + protected $config; + + protected function setUp() + { + $this->config = new Config( + Pay::VERSION, + $accountNumber = 12345, + $clientId = 'someClientId', + $clientSecret = 'someClientSecret' + ); + } + + public function test_createHandler() + { + $mock = $this->getMockBuilder(HubtelHandler::class) + ->setConstructorArgs([$this->config]) + ->setMethods(['pushHeaderMiddleware', 'pushRetryMiddleware']) + ->getMockForAbstractClass(); + + $requestMock = $this->createMock(RequestInterface::class, ['withHeader']); + $responseMock = $this->createMock(ResponseInterface::class); + $tfException = $this->createMock(TransferException::class); + + $requestMock->expects($this->exactly(2))->method('withHeader'); + + $mock->expects($this->exactly(2))->method('pushHeaderMiddleware')->with( + $this->callback(function (callable $callable) use ($requestMock) { + $callable($requestMock); + + return true; + }) + ); + + $mock->expects($this->once())->method('pushRetryMiddleware')->with( + $this->callback(function (callable $callable) use ($requestMock, $responseMock, $tfException) { + $callable(10, $requestMock, $responseMock, $tfException); + return $callable; + }), + + $this->callback(function (callable $callable) { + $result = $callable(10); + $this->assertTrue(is_int($result), 'Expects the decider function to return a number in mili-seconds'); + + return true; + }) + ); + + $response = $mock->createHandler(); + } +}