Passed
Push — develop ( b1c5cd...814934 )
by Fabian
01:28
created

Client   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 234
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 19
eloc 73
c 4
b 0
f 0
dl 0
loc 234
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
A makeGeneralRequestParameters() 0 21 2
A checkForErrorResponse() 0 7 2
A sendHttpRequest() 0 6 2
A sendRequest() 0 26 2
A readParametersFromHttpResponse() 0 20 4
A createHttpRequest() 0 25 3
A applyGeneralRequestParameters() 0 7 2
A makeKeyHash() 0 5 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cakasim\Payone\Sdk\Api\Client;
6
7
use Cakasim\Payone\Sdk\Api\Format\DecoderExceptionInterface;
8
use Cakasim\Payone\Sdk\Api\Format\DecoderInterface;
9
use Cakasim\Payone\Sdk\Api\Format\EncoderExceptionInterface;
10
use Cakasim\Payone\Sdk\Api\Format\EncoderInterface;
11
use Cakasim\Payone\Sdk\Api\Message\Parameter\SubAccountIdAwareInterface;
12
use Cakasim\Payone\Sdk\Api\Message\RequestInterface;
13
use Cakasim\Payone\Sdk\Api\Message\ResponseInterface;
14
use Cakasim\Payone\Sdk\Config\ConfigExceptionInterface;
15
use Cakasim\Payone\Sdk\Config\ConfigInterface;
16
use Cakasim\Payone\Sdk\Sdk;
17
use Psr\Http\Client\ClientExceptionInterface as HttpClientExceptionInterface;
18
use Psr\Http\Client\ClientInterface as HttpClientInterface;
19
use Psr\Http\Message\RequestFactoryInterface as HttpRequestFactoryInterface;
20
use Psr\Http\Message\RequestInterface as HttpRequestInterface;
21
use Psr\Http\Message\ResponseInterface as HttpResponseInterface;
22
23
/**
24
 * The API client implementation.
25
 *
26
 * @author Fabian Böttcher <[email protected]>
27
 * @since 0.1.0
28
 */
29
class Client implements ClientInterface
30
{
31
    /**
32
     * @var ConfigInterface The SDK config.
33
     */
34
    protected $config;
35
36
    /**
37
     * @var HttpClientInterface The HTTP client.
38
     */
39
    protected $httpClient;
40
41
    /**
42
     * @var HttpRequestFactoryInterface The HTTP request factory.
43
     */
44
    protected $httpRequestFactory;
45
46
    /**
47
     * @var EncoderInterface The API format encoder.
48
     */
49
    protected $encoder;
50
51
    /**
52
     * @var DecoderInterface The API format decoder.
53
     */
54
    protected $decoder;
55
56
    /**
57
     * Constructs the API client with dependencies.
58
     *
59
     * @param ConfigInterface $config The SDK config.
60
     * @param HttpClientInterface $httpClient The HTTP client.
61
     * @param HttpRequestFactoryInterface $httpRequestFactory The HTTP request factory.
62
     * @param EncoderInterface $encoder The API format encoder.
63
     * @param DecoderInterface $decoder The APi format decoder.
64
     */
65
    public function __construct(
66
        ConfigInterface $config,
67
        HttpClientInterface $httpClient,
68
        HttpRequestFactoryInterface $httpRequestFactory,
69
        EncoderInterface $encoder,
70
        DecoderInterface $decoder
71
    ) {
72
        $this->config = $config;
73
        $this->httpClient = $httpClient;
74
        $this->httpRequestFactory = $httpRequestFactory;
75
        $this->encoder = $encoder;
76
        $this->decoder = $decoder;
77
    }
78
79
    /**
80
     * Returns the general request parameters that
81
     * will be applied to each request.
82
     *
83
     * @return array The general request parameter array.
84
     * @throws ConfigExceptionInterface If the required configuration is incomplete.
85
     */
86
    protected function makeGeneralRequestParameters(RequestInterface $request): array
87
    {
88
        $parameters = [
89
            'api_version'        => '3.11',
90
            'encoding'           => 'UTF-8',
91
            'mid'                => $this->config->get('api.merchant_id'),
92
            'portalid'           => $this->config->get('api.portal_id'),
93
            'key'                => $this->makeKeyHash(),
94
            'mode'               => $this->config->get('api.mode'),
95
            'solution_name'      => Sdk::API_SOLUTION_NAME,
96
            'solution_version'   => Sdk::API_SOLUTION_VERSION,
97
            'integrator_name'    => $this->config->get('api.integrator_name'),
98
            'integrator_version' => $this->config->get('api.integrator_version'),
99
        ];
100
101
        // Check if the request supports the sub account ID (aid) parameter.
102
        if ($request instanceof SubAccountIdAwareInterface) {
103
            $parameters['aid'] = $this->config->get('api.sub_account_id');
104
        }
105
106
        return $parameters;
107
    }
108
109
    /**
110
     * Returns the hash value of the API key.
111
     *
112
     * @return string The API key hash.
113
     * @throws ConfigExceptionInterface If the required configuration is incomplete.
114
     */
115
    protected function makeKeyHash(): string
116
    {
117
        return hash(
118
            $this->config->get('api.key_hash_type'),
119
            $this->config->get('api.key')
120
        );
121
    }
122
123
    /**
124
     * @inheritDoc
125
     */
126
    public function sendRequest(RequestInterface $request, ResponseInterface $response): void
127
    {
128
        // Applies the general request parameters.
129
        $this->applyGeneralRequestParameters($request);
130
131
        // Make the parameter array from the request.
132
        $requestParameters = $request->makeParameterArray();
133
134
        // Create ready-to-send HTTP request from parameter array.
135
        $httpRequest = $this->createHttpRequest($requestParameters);
136
137
        // Send the HTTP request to PAYONE.
138
        $httpResponse = $this->sendHttpRequest($httpRequest);
139
140
        $responseData = $this->readParametersFromHttpResponse($httpResponse);
141
142
        if ($responseData) {
143
            // Check if response parameters represent an API error.
144
            $this->checkForErrorResponse($responseData);
145
        } else {
146
            // Use raw response body if no response parameters could be read.
147
            $responseData = $httpResponse->getBody();
148
        }
149
150
        // Delegate further parsing to provided API response.
151
        $response->parseResponseData($responseData);
152
    }
153
154
    /**
155
     * Applies the general request parameters to the API request.
156
     *
157
     * @param RequestInterface $request The API request to which the parameters will be applied.
158
     * @throws ClientException If the general parameters cannot be applied to the API request.
159
     */
160
    protected function applyGeneralRequestParameters(RequestInterface $request): void
161
    {
162
        try {
163
            // Apply general parameters to the request.
164
            $request->applyGeneralParameters($this->makeGeneralRequestParameters($request));
165
        } catch (ConfigExceptionInterface $e) {
166
            throw new ClientException("Cannot apply general request parameters.", 0, $e);
167
        }
168
    }
169
170
    /**
171
     * Creates the HTTP request.
172
     *
173
     * @param array $parameters The API parameters of the request.
174
     * @return HttpRequestInterface The created HTTP request.
175
     * @throws ClientException If the HTTP request cannot be created.
176
     */
177
    protected function createHttpRequest(array $parameters): HttpRequestInterface
178
    {
179
        try {
180
            // Get PAYONE API endpoint from config.
181
            $endpoint = $this->config->get('api.endpoint');
182
        } catch (ConfigExceptionInterface $e) {
183
            throw new ClientException("Cannot create HTTP request.", 0, $e);
184
        }
185
186
        // Create HTTP request via PSR-17 factory.
187
        $request = $this->httpRequestFactory->createRequest('POST', $endpoint)
188
            ->withHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
189
190
        try {
191
            // Encode the request parameters to API format.
192
            $body = $this->encoder->encode($parameters);
193
        } catch (EncoderExceptionInterface $e) {
194
            throw new ClientException("Cannot create HTTP request.", 0, $e);
195
        }
196
197
        // Write body contents to the request body and rewind the stream.
198
        $request->getBody()->write($body);
199
        $request->getBody()->rewind();
200
201
        return $request;
202
    }
203
204
    /**
205
     * Sends the HTTP request.
206
     *
207
     * @param HttpRequestInterface $request The HTTP request to send.
208
     * @return HttpResponseInterface The resulting HTTP response.
209
     * @throws ClientException If sending of the HTTP request fails.
210
     */
211
    protected function sendHttpRequest(HttpRequestInterface $request): HttpResponseInterface
212
    {
213
        try {
214
            return $this->httpClient->sendRequest($request);
215
        } catch (HttpClientExceptionInterface $e) {
216
            throw new ClientException("Failed to send API request.", 0, $e);
217
        }
218
    }
219
220
    /**
221
     * Reads the API response parameters if possible.
222
     *
223
     * @param HttpResponseInterface $response The HTTP response to read the parameters from.
224
     * @return array|null The response parameters or null if no parameters can be read.
225
     * @throws ClientException If reading the response parameters fails.
226
     */
227
    protected function readParametersFromHttpResponse(HttpResponseInterface $response): ?array
228
    {
229
        // Ensure text/plain content type which indicates a parameter response.
230
        if (stristr($response->getHeaderLine('Content-Type'), 'text/plain') === false) {
231
            return null;
232
        }
233
234
        // Get whole body contents of the response.
235
        $bodyContents = $response->getBody()->getContents();
236
237
        // Expect a non-empty response body.
238
        if (empty($bodyContents)) {
239
            throw new ClientException("Cannot read response parameters. The response body is empty.");
240
        }
241
242
        try {
243
            // Decode response body to parameter array.
244
            return $this->decoder->decode($bodyContents);
245
        } catch (DecoderExceptionInterface $e) {
246
            throw new ClientException("Cannot read response parameters. Failed decoding the response body.", 0, $e);
247
        }
248
    }
249
250
    /**
251
     * Checks for PAYONE API error.
252
     *
253
     * @param array $parameters The response parameters to check.
254
     * @throws ErrorResponseException If the response parameters represent a PAYONE API error.
255
     */
256
    protected function checkForErrorResponse(array $parameters): void
257
    {
258
        if (($parameters['status'] ?? 'ERROR') === 'ERROR') {
259
            throw new ErrorResponseException(
260
                (int) ($parameters['errorcode'] ?? 0),
261
                $parameters['errormessage'] ?? '',
262
                $parameters['customermessage'] ?? ''
263
            );
264
        }
265
    }
266
}
267