diff --git a/example/get-my-balance.php b/example/get-my-balance.php index 962c81a..1ce8c22 100644 --- a/example/get-my-balance.php +++ b/example/get-my-balance.php @@ -15,4 +15,4 @@ // $response = $client->getOpenOrders('THB_BTC'); // $response = $client->getUserLimits(); -dd($response->json()); +dd($response->json('result')); diff --git a/src/Authorizer.php b/src/Authorizer.php index d0d62f8..0c23398 100644 --- a/src/Authorizer.php +++ b/src/Authorizer.php @@ -10,6 +10,9 @@ class Authorizer * The signature is in hex format. * The user has to attach the signature via the Request Header You must get a new timestamp in millisecond from /api/v3/servertime. * The old one is in second. + * + * + * @return string $signature */ public function generateSignature(string $secretKey, int $timestamp, string $method, string $path, string $query = '', string $payload = ''): string { diff --git a/src/Client.php b/src/Client.php index 8242ec4..c889e28 100644 --- a/src/Client.php +++ b/src/Client.php @@ -2,6 +2,7 @@ namespace Farzai\Bitkub; +use Farzai\Bitkub\Responses\ResponseWithValidateErrorCode; use Farzai\Transport\Contracts\ResponseInterface; use Farzai\Transport\Request; use Farzai\Transport\Response; @@ -10,6 +11,7 @@ use GuzzleHttp\Psr7\Uri; use Psr\Http\Client\ClientInterface as PsrClientInterface; use Psr\Http\Message\RequestInterface as PsrRequestInterface; +use Psr\Http\Message\ResponseInterface as PsrResponseInterface; use Psr\Log\LoggerInterface; class Client @@ -125,16 +127,57 @@ protected function get(string $path, array $options = []): ResponseInterface { $request = $this->createRequest('GET', $path, $options); - return new Response($request, $this->transport->sendRequest($request)); + return $this->createResponse($request, $this->transport->sendRequest($request)); } protected function post(string $path, array $options = []): ResponseInterface { $request = $this->createRequest('POST', $path, $options); - return new Response($request, $this->transport->sendRequest($request)); + return $this->createResponse($request, $this->transport->sendRequest($request)); + } + + protected function ensureRequestAreSecure(PsrRequestInterface $request): PsrRequestInterface + { + if ($this->isSecureEndpoint($request->getMethod(), $request->getUri()->getPath())) { + $authorizer = new Authorizer(); + + $uri = $request->getUri(); + $query = $uri->getQuery(); + $body = $request->getBody()->getContents() ?: ''; + $timestamp = (int) (microtime(true) * 1000); + + $signature = $authorizer->generateSignature( + $this->config['secret'], + $timestamp, + $request->getMethod(), + $uri->getPath(), + $query, + $body + ); + + $request = $request + ->withHeader('X-BTK-APIKEY', $this->config['api_key']) + ->withHeader('X-BTK-SIGN', $signature) + ->withHeader('X-BTK-TIMESTAMP', $timestamp); + } + + return $request; + } + + /** + * Determine if the given path is secure. + */ + protected function isSecureEndpoint(string $method, string $path): bool + { + $path = strtoupper($method).' '.$path; + + return in_array($path, $this->secureEndpoints); } + /** + * Create a new request instance. + */ protected function createRequest(string $method, string $path, array $options = []): PsrRequestInterface { // Normalize path @@ -171,42 +214,15 @@ protected function createRequest(string $method, string $path, array $options = return $request; } - protected function ensureRequestAreSecure(PsrRequestInterface $request): PsrRequestInterface - { - if ($this->isSecureEndpoint($request->getMethod(), $request->getUri()->getPath())) { - $authorizer = new Authorizer(); - - $uri = $request->getUri(); - $query = $uri->getQuery(); - $body = $request->getBody()->getContents() ?: ''; - $timestamp = (int) (microtime(true) * 1000); - - $signature = $authorizer->generateSignature( - $this->config['secret'], - $timestamp, - $request->getMethod(), - $uri->getPath(), - $query, - $body - ); - - $request = $request - ->withHeader('X-BTK-APIKEY', $this->config['api_key']) - ->withHeader('X-BTK-SIGN', $signature) - ->withHeader('X-BTK-TIMESTAMP', $timestamp); - } - - return $request; - } - /** - * Determine if the given path is secure. + * Create a new response instance. */ - protected function isSecureEndpoint(string $method, string $path): bool + protected function createResponse(PsrRequestInterface $request, PsrResponseInterface $baseResponse): ResponseInterface { - $path = strtoupper($method).' '.$path; + $response = new Response($request, $baseResponse); + $response = new ResponseWithValidateErrorCode($response); - return in_array($path, $this->secureEndpoints); + return $response; } private function ensureConfigIsValid(): void diff --git a/src/Constants/ErrorCodes.php b/src/Constants/ErrorCodes.php index fe83dec..df1fa04 100644 --- a/src/Constants/ErrorCodes.php +++ b/src/Constants/ErrorCodes.php @@ -145,6 +145,9 @@ class ErrorCodes self::SERVER_ERROR => 'Server error (please contact support)', ]; + /** + * Get all error codes. + */ public static function all(): array { $reflection = new \ReflectionClass(__CLASS__); @@ -154,8 +157,11 @@ public static function all(): array }, array_keys($reflection->getConstants())); } + /** + * Get the description of the error code. + */ public static function getDescription(int $code): string { - return $descriptions[$code] ?? 'Unknown error'; + return self::DESCRIPTIONS[$code] ?? "Unknown error code: {$code}"; } } diff --git a/src/Responses/ResponseDecorator.php b/src/Responses/ResponseDecorator.php new file mode 100644 index 0000000..f42861f --- /dev/null +++ b/src/Responses/ResponseDecorator.php @@ -0,0 +1,82 @@ +response = $response; + } + + /** + * Return the response status code. + */ + public function statusCode(): int + { + return $this->response->statusCode(); + } + + /** + * Return the response body. + */ + public function body(): string + { + return $this->response->body(); + } + + /** + * Return the response headers. + */ + public function headers(): array + { + return $this->response->headers(); + } + + /** + * Check if the response is successfull. + */ + public function isSuccessfull(): bool + { + return $this->response->isSuccessfull(); + } + + /** + * Return the json decoded response. + */ + public function json(string $key = null): mixed + { + return $this->response->json($key); + } + + /** + * Throw an exception if the response is not successfull. + * + * @param callable<\Farzai\Transport\Contracts\ResponseInterface> $callback Custom callback to throw an exception. + * + * @throws \Psr\Http\Client\ClientExceptionInterface + */ + public function throw(callable $callback = null) + { + return $this->response->throw($callback); + } + + /** + * Return the psr request. + */ + public function getPsrRequest(): PsrRequestInterface + { + return $this->response->getPsrRequest(); + } +} diff --git a/src/Responses/ResponseWithValidateErrorCode.php b/src/Responses/ResponseWithValidateErrorCode.php new file mode 100644 index 0000000..433193a --- /dev/null +++ b/src/Responses/ResponseWithValidateErrorCode.php @@ -0,0 +1,29 @@ + $callback Custom callback to throw an exception. + * + * @throws \Psr\Http\Client\ClientExceptionInterface + */ + public function throw(callable $callback = null) + { + $callback = $callback ?? function (ResponseInterface $response, ?\Exception $e) use ($callback) { + if ($this->json('error') !== 0) { + throw new \Exception(ErrorCodes::getDescription($this->json('error'))); + } + + return $callback ? $callback($response, $e) : $response; + }; + + return parent::throw($callback); + } +} diff --git a/tests/AuthorizerTest.php b/tests/AuthorizerTest.php index c4bf3c2..4c54bbe 100644 --- a/tests/AuthorizerTest.php +++ b/tests/AuthorizerTest.php @@ -1,5 +1,19 @@ generateSignature($secret, $timestamp, $method, $path, $query, $payload); + + expect($signature)->toBe('b8403c345ce41b25b47885254fb8aeed9ad7ceb9e30425b86a9a151dd6ac2e35'); });