Laragear/Transbank

View on GitHub
src/Http/Client.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

namespace Laragear\Transbank\Http;

use Illuminate\Contracts\Config\Repository as ConfigContract;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\Factory as HttpFactory;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\Response;
use JetBrains\PhpStorm\ArrayShape;
use Laragear\Transbank\ApiRequest;
use Laragear\Transbank\Exceptions\ClientException;
use Laragear\Transbank\Exceptions\NetworkException;
use Laragear\Transbank\Exceptions\ServerException;
use Laragear\Transbank\Exceptions\UnknownException;
use Laragear\Transbank\Transbank;
use Throwable;
use function str_replace;

class Client
{
    /**
     * Current API Version to use on Transbank Servers.
     *
     * @var string
     */
    public const API_VERSION = 'v1.2';

    /**
     * Transbank API Key header name.
     *
     * @var string
     */
    public const HEADER_KEY = 'Tbk-Api-Key-Id';

    /**
     * Transbank API Shared Secret header name.
     *
     * @var string
     */
    public const HEADER_SECRET = 'Tbk-Api-Key-Secret';

    /**
     * Production endpoint server.
     *
     * @var string
     */
    public const PRODUCTION_ENDPOINT = 'https://webpay3g.transbank.cl/';

    /**
     * Integration endpoint server.
     *
     * @var string
     */
    public const INTEGRATION_ENDPOINT = 'https://webpay3gint.transbank.cl/';

    /**
     * Create a new HTTP Client instance.
     *
     * @param  \Illuminate\Contracts\Config\Repository  $config
     * @param  \Illuminate\Http\Client\Factory  $http
     */
    public function __construct(protected ConfigContract $config, protected HttpFactory $http)
    {
        //
    }

    /**
     * Sends a transaction to Transbank servers.
     *
     * @param  string  $method
     * @param  string  $endpoint
     * @param  \Laragear\Transbank\ApiRequest  $request
     * @return \Illuminate\Http\Client\Response
     */
    public function send(string $method, string $endpoint, ApiRequest $request): Response
    {
        $pendingRequest = $this->http
            ->withoutRedirecting()
            ->retry($this->config->get('transbank.http.retries'))
            ->timeout($this->config->get('transbank.http.timeout'))
            ->withHeaders($this->getHeadersKeysForService($request->service))
            ->baseUrl($this->getTransbankBaseEndpoint())
            ->withUserAgent('php:laragear/transbank/'.Transbank::VERSION)
            ->withOptions($this->config->get('transbank.http.options'));

        $response = $this->toTransbank($pendingRequest, $request, $method, $endpoint);

        $this->throwExceptionOnResponseError($request, $response);

        return $response;
    }

    /**
     * Returns the headers for the service with its key and secret.
     *
     * @param  string  $service
     * @return array
     */
    #[ArrayShape([self::HEADER_KEY => 'string', self::HEADER_SECRET => 'string'])]
    protected function getHeadersKeysForService(string $service): array
    {
        return [
            static::HEADER_KEY => $this->config->get("transbank.credentials.$service.key"),
            static::HEADER_SECRET => $this->config->get("transbank.credentials.$service.secret"),
        ];
    }

    /**
     * Sends the request to Transbank servers.
     *
     * @param  \Illuminate\Http\Client\PendingRequest  $request
     * @param  \Laragear\Transbank\ApiRequest  $api
     * @param  string  $method
     * @param  string  $endpoint
     * @return \Illuminate\Http\Client\Response
     */
    protected function toTransbank(PendingRequest $request, ApiRequest $api, string $method, string $endpoint): Response
    {
        try {
            return $request->send($method, $this->setApiVersion($endpoint), ['json' => $api->attributes]);
        } catch (ConnectionException $exception) {
            throw new NetworkException('Could not establish connection with Transbank.', $api, null, $exception);
        } catch (Throwable $exception) {
            throw new UnknownException('An error occurred when communicating with Transbank.', $api, null, $exception);
        }
    }

    /**
     * Replace the API Version from the endpoint.
     *
     * @param  string  $endpoint
     *
     * @return string
     */
    protected function setApiVersion(string $endpoint): string
    {
        return str_replace('{api_version}', static::API_VERSION, $endpoint);
    }

    /**
     * Checks if the Response is an error or not.
     *
     * @param  \Laragear\Transbank\ApiRequest  $apiRequest
     * @param  \Illuminate\Http\Client\Response  $response
     * @return void
     */
    protected function throwExceptionOnResponseError(ApiRequest $apiRequest, Response $response): void
    {
        // Bail out if the response is present but is not JSON.
        if ($response->header('Content-Type') !== 'application/json' ||
            !$response->toPsrResponse()->getBody()->getSize()) {
            throw new ServerException('Non-JSON response received.', $apiRequest, $response);
        }

        if ($response->redirect()) {
            throw new ServerException('A redirection was returned.', $apiRequest, $response);
        }

        if ($response->serverError()) {
            throw new ServerException($this->getErrorMessage($response), $apiRequest, $response);
        }

        if ($response->clientError()) {
            throw new ClientException($this->getErrorMessage($response), $apiRequest, $response);
        }
    }

    /**
     * Returns the error message from the Transbank response.
     *
     * @param  \Illuminate\Http\Client\Response  $response
     * @return string
     */
    protected function getErrorMessage(Response $response): string
    {
        return $response->json('error_message') ?? $response->body();
    }

    /**
     * Return the string used to reach Transbank servers.
     *
     * @return string
     */
    protected function getTransbankBaseEndpoint(): string
    {
        return $this->config->get('transbank.environment') === 'production'
            ? static::PRODUCTION_ENDPOINT
            : static::INTEGRATION_ENDPOINT;
    }
}