1: <?php
2: namespace Worldline\Acquiring\Sdk;
3:
4: use Exception;
5: use UnexpectedValueException;
6: use Worldline\Acquiring\Sdk\Authentication\Authenticator;
7: use Worldline\Acquiring\Sdk\Communication\Connection;
8: use Worldline\Acquiring\Sdk\Communication\ConnectionResponse;
9: use Worldline\Acquiring\Sdk\Communication\DefaultConnection;
10: use Worldline\Acquiring\Sdk\Communication\ErrorResponseException;
11: use Worldline\Acquiring\Sdk\Communication\MetadataProvider;
12: use Worldline\Acquiring\Sdk\Communication\MultipartDataObject;
13: use Worldline\Acquiring\Sdk\Communication\MultipartFormDataObject;
14: use Worldline\Acquiring\Sdk\Communication\RequestObject;
15: use Worldline\Acquiring\Sdk\Communication\ResponseBuilder;
16: use Worldline\Acquiring\Sdk\Communication\ResponseClassMap;
17: use Worldline\Acquiring\Sdk\Communication\ResponseFactory;
18: use Worldline\Acquiring\Sdk\Communication\UuidGenerator;
19: use Worldline\Acquiring\Sdk\Domain\DataObject;
20: use Worldline\Acquiring\Sdk\Logging\CommunicatorLogger;
21:
22: /**
23: * Class Communicator
24: *
25: * @package Worldline\Acquiring\Sdk
26: */
27: class Communicator
28: {
29: const MIME_APPLICATION_JSON = 'application/json';
30:
31: /** @var string */
32: private $apiEndpoint;
33:
34: /** @var Authenticator */
35: private $authenticator;
36:
37: /** @var Connection */
38: private $connection;
39:
40: /** @var MetadataProvider */
41: private $metadataProvider;
42:
43: /** @var ResponseFactory|null */
44: private $responseFactory = null;
45:
46: /**
47: * @param CommunicatorConfiguration $communicatorConfiguration
48: * @param Authenticator $authenticator
49: * @param Connection|null $connection
50: */
51: public function __construct(
52: CommunicatorConfiguration $communicatorConfiguration,
53: Authenticator $authenticator,
54: Connection $connection = null
55: ) {
56: $this->apiEndpoint = $communicatorConfiguration->getApiEndpoint();
57: $this->authenticator = $authenticator;
58: $this->connection = $connection != null ? $connection : new DefaultConnection($communicatorConfiguration);
59: $this->metadataProvider = new MetadataProvider($communicatorConfiguration);
60: }
61:
62: /**
63: * @param CommunicatorLogger $communicatorLogger
64: */
65: public function enableLogging(CommunicatorLogger $communicatorLogger)
66: {
67: $this->connection->enableLogging($communicatorLogger);
68: }
69:
70: /**
71: *
72: */
73: public function disableLogging()
74: {
75: $this->connection->disableLogging();
76: }
77:
78: /**
79: * @param ResponseClassMap $responseClassMap
80: * @param string $relativeUriPath
81: * @param RequestObject|null $requestParameters
82: * @param CallContext|null $callContext
83: * @return DataObject
84: * @throws Exception
85: */
86: public function get(
87: ResponseClassMap $responseClassMap,
88: $relativeUriPath,
89: RequestObject $requestParameters = null,
90: CallContext $callContext = null
91: ) {
92: $relativeUriPathWithRequestParameters =
93: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
94: $requestHeaders =
95: $this->getRequestHeaders('GET', $relativeUriPathWithRequestParameters, static::MIME_APPLICATION_JSON, $callContext);
96:
97: $responseBuilder = new ResponseBuilder();
98: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder) {
99: $responseBuilder->setHttpStatusCode($httpStatusCode);
100: $responseBuilder->setHeaders($headers);
101: $responseBuilder->appendBody($data);
102: };
103:
104: $this->connection->get(
105: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
106: $requestHeaders,
107: $responseHandler
108: );
109: $connectionResponse = $responseBuilder->getResponse();
110: $this->updateCallContext($connectionResponse, $callContext);
111: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
112: $httpStatusCode = $connectionResponse->getHttpStatusCode();
113: if ($httpStatusCode >= 400) {
114: throw new ErrorResponseException($httpStatusCode, $response);
115: }
116: return $response;
117: }
118:
119: /**
120: * @param callable $bodyHandler Callable accepting a response body chunk and the response headers
121: * @param ResponseClassMap $responseClassMap Used for error handling
122: * @param string $relativeUriPath
123: * @param RequestObject|null $requestParameters
124: * @param CallContext|null $callContext
125: * @throws Exception
126: */
127: public function getWithBinaryResponse(
128: callable $bodyHandler,
129: ResponseClassMap $responseClassMap,
130: $relativeUriPath,
131: RequestObject $requestParameters = null,
132: CallContext $callContext = null
133: ) {
134: $relativeUriPathWithRequestParameters =
135: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
136: $requestHeaders =
137: $this->getRequestHeaders('GET', $relativeUriPathWithRequestParameters, static::MIME_APPLICATION_JSON, $callContext);
138:
139: $responseBuilder = new ResponseBuilder();
140: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder, $bodyHandler) {
141: $responseBuilder->setHttpStatusCode($httpStatusCode);
142: $responseBuilder->setHeaders($headers);
143: if ($httpStatusCode >= 400) {
144: $responseBuilder->appendBody($data);
145: } else {
146: call_user_func($bodyHandler, $data, $headers);
147: }
148: };
149:
150: $this->connection->get(
151: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
152: $requestHeaders,
153: $responseHandler
154: );
155: $connectionResponse = $responseBuilder->getResponse();
156: $this->updateCallContext($connectionResponse, $callContext);
157: $httpStatusCode = $connectionResponse->getHttpStatusCode();
158: if ($httpStatusCode >= 400) {
159: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
160: throw new ErrorResponseException($httpStatusCode, $response);
161: }
162: }
163:
164: /**
165: * @param ResponseClassMap $responseClassMap
166: * @param string $relativeUriPath
167: * @param RequestObject|null $requestParameters
168: * @param CallContext|null $callContext
169: * @return DataObject
170: * @throws Exception
171: */
172: public function delete(
173: ResponseClassMap $responseClassMap,
174: $relativeUriPath,
175: RequestObject $requestParameters = null,
176: CallContext $callContext = null
177: ) {
178: $relativeUriPathWithRequestParameters =
179: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
180: $requestHeaders =
181: $this->getRequestHeaders('DELETE', $relativeUriPathWithRequestParameters, static::MIME_APPLICATION_JSON, $callContext);
182:
183: $responseBuilder = new ResponseBuilder();
184: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder) {
185: $responseBuilder->setHttpStatusCode($httpStatusCode);
186: $responseBuilder->setHeaders($headers);
187: $responseBuilder->appendBody($data);
188: };
189:
190: $this->connection->delete(
191: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
192: $requestHeaders,
193: $responseHandler
194: );
195: $connectionResponse = $responseBuilder->getResponse();
196: $this->updateCallContext($connectionResponse, $callContext);
197: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
198: $httpStatusCode = $connectionResponse->getHttpStatusCode();
199: if ($httpStatusCode >= 400) {
200: throw new ErrorResponseException($httpStatusCode, $response);
201: }
202: return $response;
203: }
204:
205: /**
206: * @param callable $bodyHandler Callable accepting a response body chunk and the response headers
207: * @param ResponseClassMap $responseClassMap Used for error handling
208: * @param string $relativeUriPath
209: * @param RequestObject|null $requestParameters
210: * @param CallContext|null $callContext
211: * @throws Exception
212: */
213: public function deleteWithBinaryResponse(
214: callable $bodyHandler,
215: ResponseClassMap $responseClassMap,
216: $relativeUriPath,
217: RequestObject $requestParameters = null,
218: CallContext $callContext = null
219: ) {
220: $relativeUriPathWithRequestParameters =
221: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
222: $requestHeaders =
223: $this->getRequestHeaders('DELETE', $relativeUriPathWithRequestParameters, static::MIME_APPLICATION_JSON, $callContext);
224:
225: $responseBuilder = new ResponseBuilder();
226: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder, $bodyHandler) {
227: $responseBuilder->setHttpStatusCode($httpStatusCode);
228: $responseBuilder->setHeaders($headers);
229: if ($httpStatusCode >= 400) {
230: $responseBuilder->appendBody($data);
231: } else {
232: call_user_func($bodyHandler, $data, $headers);
233: }
234: };
235:
236: $this->connection->delete(
237: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
238: $requestHeaders,
239: $responseHandler
240: );
241: $connectionResponse = $responseBuilder->getResponse();
242: $this->updateCallContext($connectionResponse, $callContext);
243: $httpStatusCode = $connectionResponse->getHttpStatusCode();
244: if ($httpStatusCode >= 400) {
245: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
246: throw new ErrorResponseException($httpStatusCode, $response);
247: }
248: }
249:
250: /**
251: * @param ResponseClassMap $responseClassMap
252: * @param string $relativeUriPath
253: * @param DataObject|MultipartDataObject|MultipartFormDataObject|null $requestBodyObject
254: * @param RequestObject|null $requestParameters
255: * @param CallContext|null $callContext
256: * @return DataObject
257: * @throws Exception
258: */
259: public function post(
260: ResponseClassMap $responseClassMap,
261: $relativeUriPath,
262: $requestBodyObject = null,
263: RequestObject $requestParameters = null,
264: CallContext $callContext = null
265: ) {
266: $relativeUriPathWithRequestParameters =
267: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
268: if ($requestBodyObject instanceof MultipartFormDataObject) {
269: $contentType = $requestBodyObject->getContentType();
270: $requestBody = $requestBodyObject;
271: } else if ($requestBodyObject instanceof MultipartDataObject) {
272: $multipart = $requestBodyObject->toMultipartFormDataObject();
273: $contentType = $multipart->getContentType();
274: $requestBody = $multipart;
275: } else if ($requestBodyObject instanceof DataObject || is_null($requestBodyObject)) {
276: $contentType = static::MIME_APPLICATION_JSON;
277: $requestBody = $requestBodyObject ? $requestBodyObject->toJson() : '';
278: } else {
279: throw new UnexpectedValueException('Unsupported request body');
280: }
281: $requestHeaders =
282: $this->getRequestHeaders('POST', $relativeUriPathWithRequestParameters, $contentType, $callContext);
283:
284: $responseBuilder = new ResponseBuilder();
285: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder) {
286: $responseBuilder->setHttpStatusCode($httpStatusCode);
287: $responseBuilder->setHeaders($headers);
288: $responseBuilder->appendBody($data);
289: };
290:
291: $this->connection->post(
292: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
293: $requestHeaders,
294: $requestBody,
295: $responseHandler
296: );
297: $connectionResponse = $responseBuilder->getResponse();
298: $this->updateCallContext($connectionResponse, $callContext);
299: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
300: $httpStatusCode = $connectionResponse->getHttpStatusCode();
301: if ($httpStatusCode >= 400) {
302: throw new ErrorResponseException($httpStatusCode, $response);
303: }
304: return $response;
305: }
306:
307: /**
308: * @param callable $bodyHandler Callable accepting a response body chunk and the response headers
309: * @param ResponseClassMap $responseClassMap Used for error handling
310: * @param string $relativeUriPath
311: * @param DataObject|MultipartDataObject|MultipartFormDataObject|null $requestBodyObject
312: * @param RequestObject|null $requestParameters
313: * @param CallContext|null $callContext
314: * @throws Exception
315: */
316: public function postWithBinaryResponse(
317: callable $bodyHandler,
318: ResponseClassMap $responseClassMap,
319: $relativeUriPath,
320: $requestBodyObject = null,
321: RequestObject $requestParameters = null,
322: CallContext $callContext = null
323: ) {
324: $relativeUriPathWithRequestParameters =
325: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
326: if ($requestBodyObject instanceof MultipartFormDataObject) {
327: $contentType = $requestBodyObject->getContentType();
328: $requestBody = $requestBodyObject;
329: } else if ($requestBodyObject instanceof MultipartDataObject) {
330: $multipart = $requestBodyObject->toMultipartFormDataObject();
331: $contentType = $multipart->getContentType();
332: $requestBody = $multipart;
333: } else if ($requestBodyObject instanceof DataObject || is_null($requestBodyObject)) {
334: $contentType = static::MIME_APPLICATION_JSON;
335: $requestBody = $requestBodyObject ? $requestBodyObject->toJson() : '';
336: } else {
337: throw new UnexpectedValueException('Unsupported request body');
338: }
339: $requestHeaders =
340: $this->getRequestHeaders('POST', $relativeUriPathWithRequestParameters, $contentType, $callContext);
341:
342: $responseBuilder = new ResponseBuilder();
343: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder, $bodyHandler) {
344: $responseBuilder->setHttpStatusCode($httpStatusCode);
345: $responseBuilder->setHeaders($headers);
346: if ($httpStatusCode >= 400) {
347: $responseBuilder->appendBody($data);
348: } else {
349: call_user_func($bodyHandler, $data, $headers);
350: }
351: };
352:
353: $this->connection->post(
354: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
355: $requestHeaders,
356: $requestBody,
357: $responseHandler
358: );
359: $connectionResponse = $responseBuilder->getResponse();
360: $this->updateCallContext($connectionResponse, $callContext);
361: $httpStatusCode = $connectionResponse->getHttpStatusCode();
362: if ($httpStatusCode >= 400) {
363: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
364: throw new ErrorResponseException($httpStatusCode, $response);
365: }
366: }
367:
368: /**
369: * @param ResponseClassMap $responseClassMap
370: * @param string $relativeUriPath
371: * @param DataObject|MultipartDataObject|MultipartFormDataObject|null $requestBodyObject
372: * @param RequestObject|null $requestParameters
373: * @param CallContext|null $callContext
374: * @return DataObject
375: * @throws Exception
376: */
377: public function put(
378: ResponseClassMap $responseClassMap,
379: $relativeUriPath,
380: $requestBodyObject = null,
381: RequestObject $requestParameters = null,
382: CallContext $callContext = null
383: ) {
384: $relativeUriPathWithRequestParameters =
385: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
386: if ($requestBodyObject instanceof MultipartFormDataObject) {
387: $contentType = $requestBodyObject->getContentType();
388: $requestBody = $requestBodyObject;
389: } else if ($requestBodyObject instanceof MultipartDataObject) {
390: $multipart = $requestBodyObject->toMultipartFormDataObject();
391: $contentType = $multipart->getContentType();
392: $requestBody = $multipart;
393: } else if ($requestBodyObject instanceof DataObject || is_null($requestBodyObject)) {
394: $contentType = static::MIME_APPLICATION_JSON;
395: $requestBody = $requestBodyObject ? $requestBodyObject->toJson() : '';
396: } else {
397: throw new UnexpectedValueException('Unsupported request body');
398: }
399: $requestHeaders =
400: $this->getRequestHeaders('PUT', $relativeUriPathWithRequestParameters, $contentType, $callContext);
401:
402: $responseBuilder = new ResponseBuilder();
403: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder) {
404: $responseBuilder->setHttpStatusCode($httpStatusCode);
405: $responseBuilder->setHeaders($headers);
406: $responseBuilder->appendBody($data);
407: };
408:
409: $this->connection->put(
410: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
411: $requestHeaders,
412: $requestBody,
413: $responseHandler
414: );
415: $connectionResponse = $responseBuilder->getResponse();
416: $this->updateCallContext($connectionResponse, $callContext);
417: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
418: $httpStatusCode = $connectionResponse->getHttpStatusCode();
419: if ($httpStatusCode >= 400) {
420: throw new ErrorResponseException($httpStatusCode, $response);
421: }
422: return $response;
423: }
424:
425: /**
426: * @param callable $bodyHandler Callable accepting a response body chunk and the response headers
427: * @param ResponseClassMap $responseClassMap Used for error handling
428: * @param string $relativeUriPath
429: * @param DataObject|MultipartDataObject|MultipartFormDataObject|null $requestBodyObject
430: * @param RequestObject|null $requestParameters
431: * @param CallContext|null $callContext
432: * @throws Exception
433: */
434: public function putWithBinaryResponse(
435: callable $bodyHandler,
436: ResponseClassMap $responseClassMap,
437: $relativeUriPath,
438: $requestBodyObject = null,
439: RequestObject $requestParameters = null,
440: CallContext $callContext = null
441: ) {
442: $relativeUriPathWithRequestParameters =
443: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
444: if ($requestBodyObject instanceof MultipartFormDataObject) {
445: $contentType = $requestBodyObject->getContentType();
446: $requestBody = $requestBodyObject;
447: } else if ($requestBodyObject instanceof MultipartDataObject) {
448: $multipart = $requestBodyObject->toMultipartFormDataObject();
449: $contentType = $multipart->getContentType();
450: $requestBody = $multipart;
451: } else if ($requestBodyObject instanceof DataObject || is_null($requestBodyObject)) {
452: $contentType = static::MIME_APPLICATION_JSON;
453: $requestBody = $requestBodyObject ? $requestBodyObject->toJson() : '';
454: } else {
455: throw new UnexpectedValueException('Unsupported request body');
456: }
457: $requestHeaders =
458: $this->getRequestHeaders('PUT', $relativeUriPathWithRequestParameters, $contentType, $callContext);
459:
460: $responseBuilder = new ResponseBuilder();
461: $responseHandler = function ($httpStatusCode, $data, $headers) use ($responseBuilder, $bodyHandler) {
462: $responseBuilder->setHttpStatusCode($httpStatusCode);
463: $responseBuilder->setHeaders($headers);
464: if ($httpStatusCode >= 400) {
465: $responseBuilder->appendBody($data);
466: } else {
467: call_user_func($bodyHandler, $data, $headers);
468: }
469: };
470:
471: $this->connection->put(
472: $this->apiEndpoint . $relativeUriPathWithRequestParameters,
473: $requestHeaders,
474: $requestBody,
475: $responseHandler
476: );
477: $connectionResponse = $responseBuilder->getResponse();
478: $this->updateCallContext($connectionResponse, $callContext);
479: $httpStatusCode = $connectionResponse->getHttpStatusCode();
480: if ($httpStatusCode >= 400) {
481: $response = $this->getResponseFactory()->createResponse($connectionResponse, $responseClassMap);
482: throw new ErrorResponseException($httpStatusCode, $response);
483: }
484: }
485:
486: /**
487: * @param ConnectionResponse $response
488: * @param CallContext|null $callContext
489: */
490: protected function updateCallContext(ConnectionResponse $response, CallContext $callContext = null)
491: {
492: // no context specific headers at this time
493: }
494:
495: /**
496: * @param $relativeUriPath
497: * @param RequestObject|null $requestParameters
498: * @return string
499: * @throws Exception
500: */
501: protected function getRequestUri($relativeUriPath, RequestObject $requestParameters = null)
502: {
503: return
504: $this->apiEndpoint .
505: $this->getRelativeUriPathWithRequestParameters($relativeUriPath, $requestParameters);
506: }
507:
508: /**
509: * @param string $httpMethod
510: * @param string $relativeUriPathWithRequestParameters
511: * @param string $contentType
512: * @param CallContext|null $callContext
513: * @return string[]
514: */
515: protected function getRequestHeaders(
516: $httpMethod,
517: $relativeUriPathWithRequestParameters,
518: $contentType,
519: CallContext $callContext = null
520: ) {
521: $rfc2616Date = self::getRfc161Date();
522: $requestHeaders = array();
523: $requestHeaders['Content-Type'] = $contentType;
524: $requestHeaders['Date'] = $rfc2616Date;
525: $requestHeaders['X-WL-ServerMetaInfo'] = $this->metadataProvider->getServerMetaInfoValue();
526: // no context specific headers at this time
527: $requestHeaders['Trace-Id'] = UuidGenerator::generatedUuid();
528: $requestHeaders['Authorization'] = $this->authenticator->getAuthorization($httpMethod, $relativeUriPathWithRequestParameters, $requestHeaders);
529: return $requestHeaders;
530: }
531:
532: /**
533: * @return string
534: */
535: protected static function getRfc161Date()
536: {
537: return gmdate('D, d M Y H:i:s T');
538: }
539:
540: /**
541: * @param string $relativeUriPath
542: * @param RequestObject|null $requestParameters
543: * @return string
544: */
545: protected function getRelativeUriPathWithRequestParameters(
546: $relativeUriPath,
547: RequestObject $requestParameters = null
548: ) {
549: if (is_null($requestParameters)) {
550: return $relativeUriPath;
551: }
552: $requestParameterObjectVars = get_object_vars($requestParameters);
553: if (count($requestParameterObjectVars) == 0) {
554: return $relativeUriPath;
555: }
556: $httpQuery = http_build_query($requestParameterObjectVars);
557: // remove [0], [1] etc that are added if properties are arrays
558: $httpQuery = preg_replace('/%5B[0-9]+%5D/simU', '', $httpQuery);
559: return $relativeUriPath . '?' . $httpQuery;
560: }
561:
562: /** @return ResponseFactory */
563: protected function getResponseFactory()
564: {
565: if (is_null($this->responseFactory)) {
566: $this->responseFactory = new ResponseFactory();
567: }
568: return $this->responseFactory;
569: }
570: }
571: