Completed
Push — develop ( 2f02d0...2651d0 )
by Fabian
09:40
created

Client::makeGeneralRequestParameters()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
c 0
b 0
f 0
dl 0
loc 21
rs 9.7998
cc 2
nc 2
nop 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->config->get('api.key_hash'),
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
     * @inheritDoc
111
     */
112
    public function sendRequest(RequestInterface $request, ResponseInterface $response): void
113
    {
114
        // Applies the general request parameters.
115
        $this->applyGeneralRequestParameters($request);
116
117
        // Make the parameter array from the request.
118
        $requestParameters = $request->makeParameterArray();
119
120
        // Create ready-to-send HTTP request from parameter array.
121
        $httpRequest = $this->createHttpRequest($requestParameters);
122
123
        // Send the HTTP request to PAYONE.
124
        $httpResponse = $this->sendHttpRequest($httpRequest);
125
126
        $responseData = $this->readParametersFromHttpResponse($httpResponse);
127
128
        if ($responseData) {
129
            // Check if response parameters represent an API error.
130
            $this->checkForErrorResponse($responseData);
131
        } else {
132
            // Use raw response body if no response parameters could be read.
133
            $responseData = $httpResponse->getBody();
134
        }
135
136
        // Delegate further parsing to provided API response.
137
        $response->parseResponseData($responseData);
138
    }
139
140
    /**
141
     * Applies the general request parameters to the API request.
142
     *
143
     * @param RequestInterface $request The API request to which the parameters will be applied.
144
     * @throws ClientException If the general parameters cannot be applied to the API request.
145
     */
146
    protected function applyGeneralRequestParameters(RequestInterface $request): void
147
    {
148
        try {
149
            // Apply general parameters to the request.
150
            $request->applyGeneralParameters($this->makeGeneralRequestParameters($request));
151
        } catch (ConfigExceptionInterface $e) {
152
            throw new ClientException("Cannot apply general request parameters.", 0, $e);
153
        }
154
    }
155
156
    /**
157
     * Creates the HTTP request.
158
     *
159
     * @param array $parameters The API parameters of the request.
160
     * @return HttpRequestInterface The created HTTP request.
161
     * @throws ClientException If the HTTP request cannot be created.
162
     */
163
    protected function createHttpRequest(array $parameters): HttpRequestInterface
164
    {
165
        try {
166
            // Get PAYONE API endpoint from config.
167
            $endpoint = $this->config->get('api.endpoint');
168
        } catch (ConfigExceptionInterface $e) {
169
            throw new ClientException("Cannot create HTTP request.", 0, $e);
170
        }
171
172
        // Create HTTP request via PSR-17 factory.
173
        $request = $this->httpRequestFactory->createRequest('POST', $endpoint)
174
            ->withHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
175
176
        try {
177
            // Encode the request parameters to API format.
178
            $body = $this->encoder->encode($parameters);
179
        } catch (EncoderExceptionInterface $e) {
180
            throw new ClientException("Cannot create HTTP request.", 0, $e);
181
        }
182
183
        // Write body contents to the request body and rewind the stream.
184
        $request->getBody()->write($body);
185
        $request->getBody()->rewind();
186
187
        return $request;
188
    }
189
190
    /**
191
     * Sends the HTTP request.
192
     *
193
     * @param HttpRequestInterface $request The HTTP request to send.
194
     * @return HttpResponseInterface The resulting HTTP response.
195
     * @throws ClientException If sending of the HTTP request fails.
196
     */
197
    protected function sendHttpRequest(HttpRequestInterface $request): HttpResponseInterface
198
    {
199
        try {
200
            return $this->httpClient->sendRequest($request);
201
        } catch (HttpClientExceptionInterface $e) {
202
            throw new ClientException("Failed to send API request.", 0, $e);
203
        }
204
    }
205
206
    /**
207
     * Reads the API response parameters if possible.
208
     *
209
     * @param HttpResponseInterface $response The HTTP response to read the parameters from.
210
     * @return array|null The response parameters or null if no parameters can be read.
211
     * @throws ClientException If reading the response parameters fails.
212
     */
213
    protected function readParametersFromHttpResponse(HttpResponseInterface $response): ?array
214
    {
215
        // Ensure text/plain content type which indicates a parameter response.
216
        if (stristr($response->getHeaderLine('Content-Type'), 'text/plain') === false) {
217
            return null;
218
        }
219
220
        // Get whole body contents of the response.
221
        $bodyContents = $response->getBody()->getContents();
222
223
        // Expect a non-empty response body.
224
        if (empty($bodyContents)) {
225
            throw new ClientException("Cannot read response parameters. The response body is empty.");
226
        }
227
228
        try {
229
            // Decode response body to parameter array.
230
            return $this->decoder->decode($bodyContents);
231
        } catch (DecoderExceptionInterface $e) {
232
            throw new ClientException("Cannot read response parameters. Failed decoding the response body.", 0, $e);
233
        }
234
    }
235
236
    /**
237
     * Checks for PAYONE API error.
238
     *
239
     * @param array $parameters The response parameters to check.
240
     * @throws ErrorResponseException If the response parameters represent a PAYONE API error.
241
     */
242
    protected function checkForErrorResponse(array $parameters): void
243
    {
244
        if (($parameters['status'] ?? 'ERROR') === 'ERROR') {
245
            throw new ErrorResponseException(
246
                (int) ($parameters['errorcode'] ?? 0),
247
                $parameters['errormessage'] ?? '',
248
                $parameters['customermessage'] ?? ''
249
            );
250
        }
251
    }
252
}
253