From fa89f75a729943c6265bdf760f95d306d2876277 Mon Sep 17 00:00:00 2001
From: Grzegorz Rola <grola@seostudio.pl>
Date: Thu, 31 Jan 2019 15:49:21 +0100
Subject: [PATCH] Init

---
 .gitattributes                                |  15 ++
 .gitignore                                    |   5 +
 .gitlab-ci.yml                                |   6 +
 README.md                                     |   6 +-
 changelog.txt                                 |   2 +
 composer.json                                 |  35 +++
 phpunit-integration.xml                       |  28 +++
 phpunit-unit.xml                              |  21 ++
 src/ApiClient/CachedClient.php                | 111 +++++++++
 src/ApiClient/Client.php                      |  41 ++++
 src/ApiClient/ClientFactory.php               |  35 +++
 src/ApiClient/ClientImplementation.php        | 215 ++++++++++++++++++
 src/ApiClient/RequestCacheInfoResolver.php    | 139 +++++++++++
 src/Request/AuthRequest.php                   |  53 +++++
 .../Authentication/ConnectKeyInfoRequest.php  |  28 +++
 .../Authentication/ConnectKeyResetRequest.php |  23 ++
 .../Authentication/RegisterRequest.php        |  29 +++
 src/Request/Authentication/TokenRequest.php   |  26 +++
 src/Request/BasicRequest.php                  |  54 +++++
 src/Request/Cancel/PostCancelRequest.php      |  39 ++++
 src/Request/Fields/GetFieldsRequest.php       |  45 ++++
 src/Request/Label/PostLabelRequest.php        |  36 +++
 src/Request/Rate/PostRateRequest.php          |  36 +++
 src/Request/Request.php                       |  27 +++
 .../Shipment/GetUserListShipmentRequest.php   |  27 +++
 src/Request/Shipment/PostShipmentRequest.php  |  36 +++
 .../ShippingPlan/PutShippingPlanRequest.php   |  30 +++
 .../ShippingServices/GetListRequest.php       |  14 ++
 .../ShippingServices/GetServiceRequest.php    |  29 +++
 .../GetSettingsRequest.php                    |  38 ++++
 .../PostSettingsRequest.php                   |  30 +++
 .../PutSettingsRequest.php                    |  36 +++
 .../Test/GetSettingsTestRequest.php           |  37 +++
 src/Request/Status/GetStatusRequest.php       |  49 ++++
 .../Traits/SettingsItemRequestHelper.php      |  28 +++
 src/Request/Users/GetUserRequest.php          |  28 +++
 src/Response/ApiResponse.php                  |  35 +++
 src/Response/AuthApiResponse.php              |  14 ++
 .../Authentication/ConnectKeyInfoResponse.php |  37 +++
 .../ConnectKeyResetResponse.php               |  22 ++
 .../Authentication/RegisterResponse.php       |  59 +++++
 src/Response/Authentication/TokenResponse.php |  56 +++++
 src/Response/Cancel/PostCancelResponse.php    |  11 +
 .../Exception/EmptyMessageFromResponse.php    |  19 ++
 .../Exception/EmptyStatusFromResponse.php     |  19 ++
 .../TriedExtractDataFromErrorResponse.php     |  19 ++
 src/Response/Fields/GetFieldsResponse.php     |  19 ++
 src/Response/Label/PostLabelResponse.php      |  20 ++
 .../MaintenanceResponseContext.php            |  50 ++++
 src/Response/ProtectedResponse.php            |  27 +++
 src/Response/Rate/PostRateResponse.php        |  31 +++
 src/Response/RawResponse.php                  | 113 +++++++++
 src/Response/Response.php                     |  43 ++++
 .../GetListShipmentAdminContextResponse.php   |  29 +++
 .../Shipment/PostShipmentResponse.php         |  30 +++
 .../GetShippingServiceResponse.php            |  20 ++
 .../GetShippingServicesListResponse.php       |  30 +++
 .../GetSettingsResponse.php                   |  32 +++
 .../Test/GetSettingsTestResponse.php          |  36 +++
 src/Response/Status/GetStatusResponse.php     |  20 ++
 src/Response/Traits/ApiResponseDecorator.php  | 145 ++++++++++++
 .../Traits/AuthApiResponseDecorator.php       |  38 ++++
 .../Traits/PagedListImplementation.php        |  44 ++++
 src/Response/Users/GetUserResponse.php        |  20 ++
 tests/docker-compose.yaml                     | 172 ++++++++++++++
 tests/integration/bootstrap.php               |  28 +++
 tests/unit/bootstrap.php                      |   9 +
 67 files changed, 2683 insertions(+), 1 deletion(-)
 create mode 100644 .gitattributes
 create mode 100644 .gitignore
 create mode 100644 .gitlab-ci.yml
 create mode 100644 changelog.txt
 create mode 100644 composer.json
 create mode 100644 phpunit-integration.xml
 create mode 100644 phpunit-unit.xml
 create mode 100644 src/ApiClient/CachedClient.php
 create mode 100644 src/ApiClient/Client.php
 create mode 100644 src/ApiClient/ClientFactory.php
 create mode 100644 src/ApiClient/ClientImplementation.php
 create mode 100644 src/ApiClient/RequestCacheInfoResolver.php
 create mode 100644 src/Request/AuthRequest.php
 create mode 100644 src/Request/Authentication/ConnectKeyInfoRequest.php
 create mode 100644 src/Request/Authentication/ConnectKeyResetRequest.php
 create mode 100644 src/Request/Authentication/RegisterRequest.php
 create mode 100644 src/Request/Authentication/TokenRequest.php
 create mode 100644 src/Request/BasicRequest.php
 create mode 100644 src/Request/Cancel/PostCancelRequest.php
 create mode 100644 src/Request/Fields/GetFieldsRequest.php
 create mode 100644 src/Request/Label/PostLabelRequest.php
 create mode 100644 src/Request/Rate/PostRateRequest.php
 create mode 100644 src/Request/Request.php
 create mode 100644 src/Request/Shipment/GetUserListShipmentRequest.php
 create mode 100644 src/Request/Shipment/PostShipmentRequest.php
 create mode 100644 src/Request/ShippingPlan/PutShippingPlanRequest.php
 create mode 100644 src/Request/ShippingServices/GetListRequest.php
 create mode 100644 src/Request/ShippingServices/GetServiceRequest.php
 create mode 100644 src/Request/ShippingServicesSettings/GetSettingsRequest.php
 create mode 100644 src/Request/ShippingServicesSettings/PostSettingsRequest.php
 create mode 100644 src/Request/ShippingServicesSettings/PutSettingsRequest.php
 create mode 100644 src/Request/ShippingServicesSettings/Test/GetSettingsTestRequest.php
 create mode 100644 src/Request/Status/GetStatusRequest.php
 create mode 100644 src/Request/Traits/SettingsItemRequestHelper.php
 create mode 100644 src/Request/Users/GetUserRequest.php
 create mode 100644 src/Response/ApiResponse.php
 create mode 100644 src/Response/AuthApiResponse.php
 create mode 100644 src/Response/Authentication/ConnectKeyInfoResponse.php
 create mode 100644 src/Response/Authentication/ConnectKeyResetResponse.php
 create mode 100644 src/Response/Authentication/RegisterResponse.php
 create mode 100644 src/Response/Authentication/TokenResponse.php
 create mode 100644 src/Response/Cancel/PostCancelResponse.php
 create mode 100644 src/Response/Exception/EmptyMessageFromResponse.php
 create mode 100644 src/Response/Exception/EmptyStatusFromResponse.php
 create mode 100644 src/Response/Exception/TriedExtractDataFromErrorResponse.php
 create mode 100644 src/Response/Fields/GetFieldsResponse.php
 create mode 100644 src/Response/Label/PostLabelResponse.php
 create mode 100644 src/Response/Maintenance/MaintenanceResponseContext.php
 create mode 100644 src/Response/ProtectedResponse.php
 create mode 100644 src/Response/Rate/PostRateResponse.php
 create mode 100644 src/Response/RawResponse.php
 create mode 100644 src/Response/Response.php
 create mode 100644 src/Response/Shipment/GetListShipmentAdminContextResponse.php
 create mode 100644 src/Response/Shipment/PostShipmentResponse.php
 create mode 100644 src/Response/ShippingServices/GetShippingServiceResponse.php
 create mode 100644 src/Response/ShippingServices/GetShippingServicesListResponse.php
 create mode 100644 src/Response/ShippingServicesSettings/GetSettingsResponse.php
 create mode 100644 src/Response/ShippingServicesSettings/Test/GetSettingsTestResponse.php
 create mode 100644 src/Response/Status/GetStatusResponse.php
 create mode 100644 src/Response/Traits/ApiResponseDecorator.php
 create mode 100644 src/Response/Traits/AuthApiResponseDecorator.php
 create mode 100644 src/Response/Traits/PagedListImplementation.php
 create mode 100644 src/Response/Users/GetUserResponse.php
 create mode 100644 tests/docker-compose.yaml
 create mode 100644 tests/integration/bootstrap.php
 create mode 100644 tests/unit/bootstrap.php

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..a612c0c
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,15 @@
+tests/ export-ignore
+vendor/ export-ignore
+.editorconfig export-ignore
+.gitattributes export-ignore
+.gitignore export-ignore
+.git/ export-ignore
+.gitlab-ci.yml export-ignore
+.idea export-ignore
+apigen.neon export-ignore
+build-coverage/ export-ignore
+docs/ export-ignore
+LICENSE.md export-ignore
+phpcs.xml.dist export-ignore
+phpunit-integration.xml export-ignore
+phpunit-unit.xml export-ignore
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4b86223
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+/vendor/
+.idea
+composer.lock
+build-coverage
+swagger
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..db521c1
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,6 @@
+variables:
+  DISABLE_ACCEPTANCE: "1"
+  DISABLE_FUNCTIONAL: "1"
+
+include: 'https://gitlab.com/wpdesk/gitlab-ci/raw/master/gitlab-ci-1.2.yml'
+
diff --git a/README.md b/README.md
index b6395ce..a8ccb86 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,6 @@
-# wp-api-client
+[![pipeline status](https://gitlab.com/wpdesk/wp-api-client/badges/master/pipeline.svg)](https://gitlab.com/wpdesk/wp-api-client/commits/master) 
+Integration: [![coverage report](https://gitlab.com/wpdesk/wp-api-client/badges/master/coverage.svg?job=integration+test+lastest+coverage)](https://gitlab.com/wpdesk/wp-api-client/commits/master)
+Unit: [![coverage report](https://gitlab.com/wpdesk/wp-api-client/badges/master/coverage.svg?job=unit+test+lastest+coverage)](https://gitlab.com/wpdesk/wp-api-client/commits/master)
 
+API Client
+==========
diff --git a/changelog.txt b/changelog.txt
new file mode 100644
index 0000000..03dc550
--- /dev/null
+++ b/changelog.txt
@@ -0,0 +1,2 @@
+= 1.0 - 2019-01-31 =
+* First release
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..6980cec
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,35 @@
+{
+  "name": "wpdesk/wp-api-client",
+  "authors": [
+    {
+      "name": "Krzysiek",
+      "email": "krzysiek@wpdesk.pl"
+    }
+  ],
+  "require": {
+    "php": ">=5.5",
+    "psr/log": "^1.0.1",
+    "wpdesk/wp-http-client": "dev-master",
+    "psr/simple-cache": "^1.0"
+  },
+  "require-dev": {
+    "phpunit/phpunit": "<7",
+    "wp-coding-standards/wpcs": "^0.14.1",
+    "squizlabs/php_codesniffer": "^3.0.2",
+    "mockery/mockery": "*",
+    "10up/wp_mock": "*"
+  },
+  "autoload": {
+    "psr-4": {
+      "WPDesk\\ApiClient\\": "src/"
+    }
+  },
+  "autoload-dev": {
+  },
+  "scripts": {
+    "phpunit-unit": "phpunit --configuration phpunit-unit.xml --coverage-text --colors=never",
+    "phpunit-unit-fast": "phpunit --configuration phpunit-unit.xml --no-coverage",
+    "phpunit-integration": "phpunit --configuration phpunit-integration.xml --coverage-text --colors=never",
+    "phpunit-integration-fast": "phpunit --configuration phpunit-integration.xml --no-coverage"
+  }
+}
diff --git a/phpunit-integration.xml b/phpunit-integration.xml
new file mode 100644
index 0000000..4a342ab
--- /dev/null
+++ b/phpunit-integration.xml
@@ -0,0 +1,28 @@
+<phpunit bootstrap="tests/integration/bootstrap.php"
+         backupGlobals="false"
+     >
+    <testsuites>
+        <testsuite>
+            <directory prefix="Test" suffix=".php">./tests/integration</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory suffix=".php">src</directory>
+        </whitelist>
+    </filter>
+
+    <logging>
+        <log type="junit" target="build-coverage/report.junit.xml"/>
+        <log type="coverage-html" target="build-coverage/coverage" charset="UTF-8" yui="true" highlight="true"/>
+        <log type="coverage-text" target="build-coverage/coverage.txt"/>
+        <log type="coverage-clover" target="build-coverage/clover.xml"/>
+    </logging>
+
+    <php>
+        <env name="WP_DEVELOP_DIR" value="/tmp/wordpress-develop"/>
+        <env name="WC_DEVELOP_DIR" value="/tmp/woocommerce"/>
+    </php>
+
+</phpunit>
\ No newline at end of file
diff --git a/phpunit-unit.xml b/phpunit-unit.xml
new file mode 100644
index 0000000..31e5c9f
--- /dev/null
+++ b/phpunit-unit.xml
@@ -0,0 +1,21 @@
+<phpunit bootstrap="tests/unit/bootstrap.php">
+    <testsuites>
+        <testsuite>
+            <directory prefix="Test" suffix=".php">./tests/unit/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory suffix=".php">src</directory>
+        </whitelist>
+    </filter>
+
+    <logging>
+        <log type="junit" target="build-coverage/report.junit.xml"/>
+        <log type="coverage-html" target="build-coverage/coverage" charset="UTF-8" yui="true" highlight="true"/>
+        <log type="coverage-text" target="build-coverage/coverage.txt"/>
+        <log type="coverage-clover" target="build-coverage/clover.xml"/>
+    </logging>
+
+</phpunit>
diff --git a/src/ApiClient/CachedClient.php b/src/ApiClient/CachedClient.php
new file mode 100644
index 0000000..a7f512c
--- /dev/null
+++ b/src/ApiClient/CachedClient.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\ApiClient;
+
+use Psr\SimpleCache\CacheInterface;
+use WPDesk\SaasPlatformClient\Cache\CacheDispatcher;
+use WPDesk\SaasPlatformClient\Cache\CacheItemCreator;
+use WPDesk\SaasPlatformClient\Cache\CacheItemVerifier;
+use WPDesk\SaasPlatformClient\HttpClient\HttpClient;
+use WPDesk\SaasPlatformClient\Request\Request;
+use WPDesk\SaasPlatformClient\Response\Response;
+use WPDesk\SaasPlatformClient\Serializer\Serializer;
+
+class CachedClient implements Client, CacheItemCreator, CacheItemVerifier
+{
+
+    /** @var Client */
+    private $client;
+
+    /** @var CacheInterface */
+    private $cache;
+
+    /**
+     * @var CacheDispatcher
+     */
+    private $cacheDispatcher;
+
+    /**
+     * CachedClient constructor.
+     * @param Client $decorated Decorated client
+     * @param CacheInterface $cache
+     */
+    public function __construct(Client $decorated, CacheInterface $cache)
+    {
+        $this->client = $decorated;
+        $this->cache = $cache;
+        $this->cacheDispatcher = new CacheDispatcher($cache, [new RequestCacheInfoResolver()]);
+    }
+
+    /**
+     * Create item to cache.
+     *
+     * @param Request $request
+     * @return Response
+     */
+    public function createCacheItem($request)
+    {
+        return $this->client->sendRequest($request);
+    }
+
+    /**
+     * Verify cache item.
+     *
+     * @param $object
+     * @return Response;
+     */
+    public function getVerifiedItemOrNull($object)
+    {
+        if ($object instanceof Response) {
+            return $object;
+        }
+        return null;
+    }
+
+    /**
+     * Send request.
+     *
+     * @param Request $request
+     * @return mixed|Response
+     * @throws \Psr\SimpleCache\InvalidArgumentException
+     */
+    public function sendRequest(Request $request)
+    {
+        $response = $this->cacheDispatcher->dispatch($request, $this, $this);
+        return $response;
+    }
+
+    /**
+     * @return HttpClient
+     */
+    public function getHttpClient()
+    {
+        return $this->client->getHttpClient();
+    }
+
+    /**
+     * @param HttpClient $client
+     * @return mixed
+     */
+    public function setHttpClient(HttpClient $client)
+    {
+        return $this->client->setHttpClient($client);
+    }
+
+    /**
+     * @return Serializer
+     */
+    public function getSerializer()
+    {
+        return $this->client->getSerializer();
+    }
+
+    /**
+     * @return string
+     */
+    public function getApiUrl()
+    {
+        return $this->client->getApiUrl();
+    }
+
+}
\ No newline at end of file
diff --git a/src/ApiClient/Client.php b/src/ApiClient/Client.php
new file mode 100644
index 0000000..993f5ca
--- /dev/null
+++ b/src/ApiClient/Client.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\ApiClient;
+
+use WPDesk\SaasPlatformClient\HttpClient\HttpClient;
+use WPDesk\SaasPlatformClient\Request\Request;
+use WPDesk\SaasPlatformClient\Response\Response;
+use WPDesk\SaasPlatformClient\Serializer\Serializer;
+
+interface Client
+{
+    /**
+     * Send given request trough HttpClient
+     *
+     * @param Request $request
+     * @return Response
+     */
+    public function sendRequest(Request $request);
+
+    /**
+     * @return HttpClient
+     */
+    public function getHttpClient();
+
+    /**
+     * @param HttpClient $client
+     */
+    public function setHttpClient(HttpClient $client);
+
+    /**
+     * @return Serializer
+     */
+    public function getSerializer();
+
+    /**
+     * Returns api url. Always without ending /
+     *
+     * @return string
+     */
+    public function getApiUrl();
+}
\ No newline at end of file
diff --git a/src/ApiClient/ClientFactory.php b/src/ApiClient/ClientFactory.php
new file mode 100644
index 0000000..a5b45e6
--- /dev/null
+++ b/src/ApiClient/ClientFactory.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\ApiClient;
+
+use WPDesk\SaasPlatformClient\Cache\WordpressCache;
+use WPDesk\SaasPlatformClient\HttpClient\HttpClientFactory;
+use WPDesk\SaasPlatformClient\PlatformFactoryOptions;
+use WPDesk\SaasPlatformClient\Serializer\SerializerFactory;
+
+class ClientFactory
+{
+    /**
+     * @param PlatformFactoryOptions $options
+     * @return Client
+     */
+    public function createClient(PlatformFactoryOptions $options)
+    {
+        $httpClientFactory = new HttpClientFactory();
+        $serializerFactory = new SerializerFactory();
+
+        $client = new ClientImplementation(
+            $httpClientFactory->createClient($options),
+            $serializerFactory->createSerializer($options),
+            $options->getLogger(),
+            $options->getApiUrl(),
+            $options->getDefaultRequestHeaders()
+        );
+
+        if ($options->isCachedClient()) {
+            $client = new CachedClient($client, new WordpressCache());
+        }
+
+        return $client;
+    }
+}
\ No newline at end of file
diff --git a/src/ApiClient/ClientImplementation.php b/src/ApiClient/ClientImplementation.php
new file mode 100644
index 0000000..6153d4c
--- /dev/null
+++ b/src/ApiClient/ClientImplementation.php
@@ -0,0 +1,215 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\ApiClient;
+
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use WPDesk\SaasPlatformClient\HttpClient\HttpClient;
+use WPDesk\SaasPlatformClient\HttpClient\HttpClientResponse;
+use WPDesk\SaasPlatformClient\Platform;
+use WPDesk\SaasPlatformClient\Request\Request;
+use WPDesk\SaasPlatformClient\Response\RawResponse;
+use WPDesk\SaasPlatformClient\Response\Response;
+use WPDesk\SaasPlatformClient\Serializer\Serializer;
+use WPDesk\SaasPlatformClient\HttpClient\HttpClientRequestException;
+
+class ClientImplementation implements Client, LoggerAwareInterface
+{
+    const CLIENT_VERSION = '1.6.5';
+
+    const DEFAULT_TIMEOUT = 10;
+
+    /** @var HttpClient */
+    private $client;
+
+    /** @var Serializer */
+    private $serializer;
+
+    /** @var LoggerInterface */
+    private $logger;
+
+    /** @var string */
+    private $apiUrl;
+
+    /** @var array */
+    private $defaultRequestHeaders;
+
+    /**
+     * Client constructor.
+     * @param HttpClient $client
+     * @param Serializer $serializer
+     * @param LoggerInterface $logger
+     * @param string $apiUri
+     * @param array $defaultRequestHeaders
+     */
+    public function __construct(
+        HttpClient $client,
+        Serializer $serializer,
+        LoggerInterface $logger,
+        $apiUri,
+        array $defaultRequestHeaders
+    ) {
+        $this->client = $client;
+        $this->serializer = $serializer;
+        $this->logger = $logger;
+        $this->apiUrl = $apiUri;
+        $this->defaultRequestHeaders = $defaultRequestHeaders;
+    }
+
+    /**
+     * Send given request trough HttpClient
+     *
+     * @param Request $request
+     * @throws HttpClientRequestException
+     * @return Response
+     */
+    public function sendRequest(Request $request)
+    {
+        $this->logger->debug("Sends request with METHOD: {$request->getMethod()}; to ENDPOINT {$request->getEndpoint()}",
+            $this->getLoggerContext());
+        try {
+            $httpResponse = $this->client->send(
+                $fullUrl = $this->prepareFullUrl($request),
+                $method = $request->getMethod(),
+                $body = $this->prepareRequestBody($request),
+                $headers = $this->prepareRequestHeaders($request), self::DEFAULT_TIMEOUT
+            );
+
+            $this->logger->debug(
+                "Sent request with: URL: {$fullUrl};\n METHOD: {$method};\n BODY: {$body};\n"
+                . "HEADERS: " . json_encode($headers) . "\n\n and got response as CODE: {$httpResponse->getResponseCode()};\n"
+                . "with RESPONSE BODY {$httpResponse->getBody()}",
+                $this->getLoggerContext());
+
+
+            return $this->mapHttpResponseToApiResponse($httpResponse);
+        } catch (HttpClientRequestException $e) {
+            $this->logger->error("Exception {$e->getMessage()}; {$e->getCode()} occurred while sending request");
+            throw $e;
+        }
+    }
+
+    /**
+     * Returns full request url with endpoint
+     *
+     * @param Request $request
+     * @return string
+     */
+    private function prepareFullUrl(Request $request)
+    {
+        $endpoint = $request->getEndpoint();
+        if (strpos('http', $endpoint) === 0) {
+            return $endpoint;
+        }
+        return $this->getApiUrl() . $endpoint;
+    }
+
+    /**
+     * Map response from http client to api response using serializer
+     *
+     * @param HttpClientResponse $response
+     * @return RawResponse
+     */
+    private function mapHttpResponseToApiResponse(HttpClientResponse $response)
+    {
+        $apiResponse = new RawResponse(
+            $this->serializer->unserialize($response->getBody()),
+            $response->getResponseCode(),
+            $response->getHeaders()
+        );
+
+        return $apiResponse;
+    }
+
+    /**
+     * Prepare serialized request body
+     *
+     * @param Request $request
+     * @return string
+     */
+    private function prepareRequestBody(Request $request)
+    {
+        return $this->serializer->serialize($request->getBody());
+    }
+
+    /**
+     * Prepares array of http headers
+     *
+     * @param Request $request
+     * @return array
+     */
+    private function prepareRequestHeaders(Request $request)
+    {
+        $headers = array(
+            'User-Agent' => 'saas-client-' . self::CLIENT_VERSION,
+            'Accept-Encoding' => '*',
+            'Content-Type' => $this->serializer->getMime()
+        );
+        $headers = array_merge($this->defaultRequestHeaders, $headers);
+        return array_merge($headers, $request->getHeaders());
+    }
+
+    /**
+     * @return HttpClient
+     */
+    public function getHttpClient()
+    {
+        return $this->client;
+    }
+
+    /**
+     * @param HttpClient $client
+     */
+    public function setHttpClient(HttpClient $client)
+    {
+        $this->client = $client;
+    }
+
+    /**
+     * @return Serializer
+     */
+    public function getSerializer()
+    {
+        return $this->serializer;
+    }
+
+    /**
+     * Returns api url. Always without ending /
+     *
+     * @return string
+     */
+    public function getApiUrl()
+    {
+        return trim($this->apiUrl, '/');
+    }
+
+    /**
+     * Sets logger
+     *
+     * @param LoggerInterface $logger
+     */
+    public function setLogger(LoggerInterface $logger)
+    {
+        $this->logger = $logger;
+    }
+
+    /**
+     * Returns logger context for
+     *
+     * @param string $additional_context Optional additional context
+     * @return array
+     */
+    protected function getLoggerContext($additional_context = '')
+    {
+        $context = [
+            Platform::LIBARY_LOGIN_CONTEXT,
+            self::class
+        ];
+        if ($additional_context !== '') {
+            $context[] = $additional_context;
+        }
+        return $context;
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/ApiClient/RequestCacheInfoResolver.php b/src/ApiClient/RequestCacheInfoResolver.php
new file mode 100644
index 0000000..65e2014
--- /dev/null
+++ b/src/ApiClient/RequestCacheInfoResolver.php
@@ -0,0 +1,139 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\ApiClient;
+
+use WPDesk\SaasPlatformClient\Cache\CacheInfoResolver;
+use WPDesk\SaasPlatformClient\Cache\HowToCache;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+use WPDesk\SaasPlatformClient\Request\BasicRequest;
+use WPDesk\SaasPlatformClient\Request\Request;
+use WPDesk\SaasPlatformClient\Request\ShippingServicesSettings\PutSettingsRequest;
+use WPDesk\SaasPlatformClient\Request\Status\GetStatusRequest;
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\RawResponse;
+
+class RequestCacheInfoResolver implements CacheInfoResolver
+{
+
+    const DEFAULT_CACHE_TTL    = 86400; //24 hours
+    const CACHE_TTL_ONE_MINUTE = 60;
+
+    const OPTION_FS_SAAS_PLATFORM_VERSION_HASH = 'fs-saas-platform-version-hash';
+
+    /**
+     *
+     * @param Request $request
+     *
+     * @return bool
+     */
+    private function prepareCacheKey($request)
+    {
+        return md5($request->getEndpoint());
+    }
+
+    /**
+     *
+     * @param Request $request
+     *
+     * @return bool
+     */
+    public function isSupported($request)
+    {
+        if ($request instanceof BasicRequest) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     *
+     * @param Request $request
+     *
+     * @return bool
+     */
+    public function shouldCache($request)
+    {
+        if ($request instanceof ConnectKeyInfoRequest) {
+            return false;
+        }
+        if ($request instanceof GetStatusRequest) {
+            return false;
+        }
+        if ($request instanceof BasicRequest) {
+            if ('GET' === $request->getMethod()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     *
+     * @param Request $request
+     *
+     * @return HowToCache
+     */
+    public function prepareHowToCache($request)
+    {
+        $howToCache = new HowToCache($this->prepareCacheKey($request), self::DEFAULT_CACHE_TTL);
+        return $howToCache;
+    }
+
+    /**
+     * @param ApiResponse $response
+     *
+     * @return bool
+     */
+    private function isPlatformVersionFromResponseChanged(ApiResponse $response)
+    {
+        $stored_hash = get_option(self::OPTION_FS_SAAS_PLATFORM_VERSION_HASH, '');
+        if ($stored_hash !== $response->getPlatformVersionHash()) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @param ApiResponse $response
+     */
+    private function storePlatformVersionHashFromResponse(ApiResponse $response)
+    {
+        update_option(self::OPTION_FS_SAAS_PLATFORM_VERSION_HASH, $response->getPlatformVersionHash());
+    }
+
+    /**
+     *
+     * @param Request $request
+     * @param mixed $item
+     *
+     * @return bool
+     */
+    public function shouldClearCache($request, $item)
+    {
+        if ($request instanceof PutSettingsRequest) {
+            return true;
+        }
+        if ($item instanceof ApiResponse && $this->isPlatformVersionFromResponseChanged($item)) {
+            $this->storePlatformVersionHashFromResponse($item);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     *
+     * @param Request $request
+     * @param mixed $item
+     *
+     * @return string[]
+     */
+    public function shouldClearKeys($request, $item)
+    {
+        if ('GET' !== $request->getMethod()) {
+            return [$this->prepareCacheKey($request)];
+        }
+        return [];
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/Request/AuthRequest.php b/src/Request/AuthRequest.php
new file mode 100644
index 0000000..3415812
--- /dev/null
+++ b/src/Request/AuthRequest.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+
+class AuthRequest extends BasicRequest
+{
+    /** @var Token */
+    private $token;
+
+    public function __construct(Token $token)
+    {
+        $this->setToken($token);
+    }
+
+    /**
+     * @param Token $token
+     */
+    public function setToken(Token $token)
+    {
+        $this->token = $token;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isTokenExpired()
+    {
+        return $this->token->isExpired();
+    }
+
+    /**
+     * @return bool
+     */
+    public function isTokenInvalid()
+    {
+        return !$this->token->isSignatureValid();
+    }
+
+    /**
+     * Returns array of http headers
+     *
+     * @return array
+     */
+    public function getHeaders()
+    {
+        $headers = array(
+            'Authorization' => $this->token->getAuthString()
+        );
+        return array_merge($headers, parent::getHeaders());
+    }
+}
\ No newline at end of file
diff --git a/src/Request/Authentication/ConnectKeyInfoRequest.php b/src/Request/Authentication/ConnectKeyInfoRequest.php
new file mode 100644
index 0000000..fe611c6
--- /dev/null
+++ b/src/Request/Authentication/ConnectKeyInfoRequest.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\Authentication;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+
+final class ConnectKeyInfoRequest extends AuthRequest
+{
+    /** @var string */
+    protected $method = 'GET';
+
+    /** @var string */
+    protected $endPoint = '/connectkeyinfo/{connectKey}';
+
+
+    /**
+     * ConnectKeyInfoRequest constructor.
+     * @param $connectKey
+     * @param Token $token
+     */
+    public function __construct($connectKey, Token $token)
+    {
+        parent::__construct($token);
+
+        $this->endPoint = str_replace('{connectKey}', $connectKey, $this->endPoint);
+    }
+}
\ No newline at end of file
diff --git a/src/Request/Authentication/ConnectKeyResetRequest.php b/src/Request/Authentication/ConnectKeyResetRequest.php
new file mode 100644
index 0000000..0c2cd8c
--- /dev/null
+++ b/src/Request/Authentication/ConnectKeyResetRequest.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\Authentication;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+
+final class ConnectKeyResetRequest extends AuthRequest
+{
+    /** @var string */
+    protected $method = 'POST';
+
+    /** @var string */
+    protected $endPoint = '/users/{id}/connectKeyReset';
+
+
+    public function __construct($user_id, Token $token)
+    {
+        parent::__construct($token);
+
+        $this->endPoint = str_replace('{id}', $user_id, $this->endPoint);
+    }
+}
\ No newline at end of file
diff --git a/src/Request/Authentication/RegisterRequest.php b/src/Request/Authentication/RegisterRequest.php
new file mode 100644
index 0000000..3a231d6
--- /dev/null
+++ b/src/Request/Authentication/RegisterRequest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\Authentication;
+
+use WPDesk\SaasPlatformClient\Request\BasicRequest;
+
+final class RegisterRequest extends BasicRequest
+{
+    /** @var string */
+    protected $method = 'POST';
+
+    /** @var string */
+    protected $endPoint = '/register';
+
+    /**
+     * RegisterRequest constructor.
+     * @param string $email
+     * @param string $domain
+     * @param string $locale
+     * @param ?string $admin_url
+     */
+    public function __construct($email, $domain, $locale, $admin_url = null)
+    {
+        $this->data = compact('email', 'domain', 'locale');
+        if ($admin_url !== null) {
+            $this->data['adminUrl'] = $admin_url;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Request/Authentication/TokenRequest.php b/src/Request/Authentication/TokenRequest.php
new file mode 100644
index 0000000..289011b
--- /dev/null
+++ b/src/Request/Authentication/TokenRequest.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\Authentication;
+
+
+use WPDesk\SaasPlatformClient\Request\BasicRequest;
+
+final class TokenRequest extends BasicRequest
+{
+    /** @var string */
+    protected $method = 'POST';
+
+    /** @var string */
+    protected $endPoint = '/token';
+
+    /**
+     * TokenRequest constructor.
+     * @param string $connectKey
+     * @param string $domain
+     * @param string $locale
+     */
+    public function __construct($connectKey, $domain, $locale)
+    {
+        $this->data = compact(['connectKey', 'domain', 'locale']);
+    }
+}
\ No newline at end of file
diff --git a/src/Request/BasicRequest.php b/src/Request/BasicRequest.php
new file mode 100644
index 0000000..c185d1e
--- /dev/null
+++ b/src/Request/BasicRequest.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request;
+
+
+class BasicRequest implements Request
+{
+    /** @var string */
+    protected $method;
+
+    /** @var array */
+    protected $data;
+
+    /** @var string */
+    protected $endPoint;
+
+    /**
+     * @return string
+     */
+    public function getMethod()
+    {
+        return $this->method;
+    }
+
+    /**
+     * Return endpoint in format /[^/]+/
+     *
+     * @return string
+     */
+    public function getEndpoint()
+    {
+        return '/' . trim($this->endPoint, '/');
+    }
+
+    /**
+     * Returns array of http headers
+     *
+     * @return array
+     */
+    public function getHeaders()
+    {
+        return array();
+    }
+
+    /**
+     * Return unserialized request body as array
+     *
+     * @return array
+     */
+    public function getBody()
+    {
+        return $this->data;
+    }
+}
\ No newline at end of file
diff --git a/src/Request/Cancel/PostCancelRequest.php b/src/Request/Cancel/PostCancelRequest.php
new file mode 100644
index 0000000..a780ee2
--- /dev/null
+++ b/src/Request/Cancel/PostCancelRequest.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\Cancel;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+
+final class PostCancelRequest extends AuthRequest
+{
+    /** @var string */
+    protected $method = 'POST';
+
+    /** @var string */
+    protected $endPoint = '/shops/{shop}/shipping_services/{service}/shipments/{shipment}/cancel';
+
+    /**
+     * PostShipmentRequest constructor.
+     * @param Token $token
+     * @param int $shippingServiceId
+     * @param int $shopId
+     * @param int $shipmentId
+     */
+    public function __construct(
+        Token $token,
+        $shippingServiceId,
+        $shopId,
+        $shipmentId
+    ) {
+        parent::__construct($token);
+
+        $this->endPoint = str_replace(
+            ['{shop}', '{service}', '{shipment}'],
+            [$shopId, $shippingServiceId, $shipmentId],
+            $this->endPoint
+        );
+
+        $this->data = [];
+    }
+}
\ No newline at end of file
diff --git a/src/Request/Fields/GetFieldsRequest.php b/src/Request/Fields/GetFieldsRequest.php
new file mode 100644
index 0000000..2e5b881
--- /dev/null
+++ b/src/Request/Fields/GetFieldsRequest.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\Fields;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+
+final class GetFieldsRequest extends AuthRequest
+{
+    /** @var string */
+    protected $method = 'GET';
+
+    /** @var string */
+    protected $endPoint = '/shops/{shop}/shipping_services/{service}/dynamic_fields/{zone_targets}';
+
+    const ZONE_TARGETS_DELIMITER = ',';
+
+    /**
+     * @param array $zoneTargets
+     * @return string
+     */
+    private function serializeZoneTargets(array $zoneTargets)
+    {
+        return implode(self::ZONE_TARGETS_DELIMITER, $zoneTargets);
+    }
+
+    /**
+     * PostShipmentRequest constructor.
+     * @param Token $token
+     * @param int $shippingServiceId
+     * @param int $shopId
+     * @param string[] $zoneTargets
+     */
+    public function __construct(
+        Token $token,
+        $shippingServiceId,
+        $shopId,
+        array $zoneTargets
+    ) {
+        parent::__construct($token);
+
+        $this->endPoint = str_replace(['{shop}', '{service}', '{zone_targets}'],
+            [$shopId, $shippingServiceId, $this->serializeZoneTargets($zoneTargets)], $this->endPoint);
+    }
+}
\ No newline at end of file
diff --git a/src/Request/Label/PostLabelRequest.php b/src/Request/Label/PostLabelRequest.php
new file mode 100644
index 0000000..e34dcd7
--- /dev/null
+++ b/src/Request/Label/PostLabelRequest.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\Label;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+
+final class PostLabelRequest extends AuthRequest
+{
+    /** @var string */
+    protected $method = 'POST';
+
+    /** @var string */
+    protected $endPoint = '/shops/{shop}/shipping_services/{service}/shipments/{shipment}/labels';
+
+    /**
+     * PostShipmentRequest constructor.
+     * @param Token $token
+     * @param int $shippingServiceId
+     * @param int $shopId
+     * @param int $shipmentId
+     */
+    public function __construct(
+        Token $token,
+        $shippingServiceId,
+        $shopId,
+        $shipmentId
+    ) {
+        parent::__construct($token);
+
+        $this->endPoint = str_replace(['{shop}', '{service}', '{shipment}'], [$shopId, $shippingServiceId, $shipmentId],
+            $this->endPoint);
+
+        $this->data = [];
+    }
+}
\ No newline at end of file
diff --git a/src/Request/Rate/PostRateRequest.php b/src/Request/Rate/PostRateRequest.php
new file mode 100644
index 0000000..5352104
--- /dev/null
+++ b/src/Request/Rate/PostRateRequest.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\Rate;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Model\Rate\RateRequest;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+
+final class PostRateRequest extends AuthRequest
+{
+    /** @var string */
+    protected $method = 'POST';
+
+    /** @var string */
+    protected $endPoint = '/shops/{shop}/shipping_services/{service}/rates';
+
+    /**
+     * PostRateRequest constructor.
+     * @param Token $token
+     * @param int $shippingServiceId
+     * @param int $shopId
+     * @param RateRequest $request
+     */
+    public function __construct(
+        Token $token,
+        $shippingServiceId,
+        $shopId,
+        RateRequest $request
+    ) {
+        parent::__construct($token);
+
+        $this->endPoint = str_replace(['{shop}', '{service}'], [$shopId, $shippingServiceId], $this->endPoint);
+
+        $this->data = $request->toArray();
+    }
+}
\ No newline at end of file
diff --git a/src/Request/Request.php b/src/Request/Request.php
new file mode 100644
index 0000000..2810176
--- /dev/null
+++ b/src/Request/Request.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request;
+
+
+interface Request
+{
+    /**
+     * @return string
+     */
+    public function getMethod();
+
+    /**
+     * @return array
+     */
+    public function getHeaders();
+
+    /**
+     * @return array
+     */
+    public function getBody();
+
+    /**
+     * @return string
+     */
+    public function getEndpoint();
+}
\ No newline at end of file
diff --git a/src/Request/Shipment/GetUserListShipmentRequest.php b/src/Request/Shipment/GetUserListShipmentRequest.php
new file mode 100644
index 0000000..176c4ba
--- /dev/null
+++ b/src/Request/Shipment/GetUserListShipmentRequest.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\Shipment;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Model\Shipment\ShipmentRequest;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+
+final class GetUserListShipmentRequest extends AuthRequest
+{
+    /** @var string */
+    protected $method = 'GET';
+
+    /** @var string */
+    protected $endPoint = '/users/{id}/shipments';
+
+    /**
+     * GetListShipmentRequest constructor.
+     * @param Token $token
+     */
+    public function __construct($user_id, Token $token)
+{
+        parent::__construct($token);
+
+        $this->endPoint = str_replace('{id}', $user_id, $this->endPoint);
+    }
+}
\ No newline at end of file
diff --git a/src/Request/Shipment/PostShipmentRequest.php b/src/Request/Shipment/PostShipmentRequest.php
new file mode 100644
index 0000000..0913827
--- /dev/null
+++ b/src/Request/Shipment/PostShipmentRequest.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\Shipment;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Model\Shipment\ShipmentRequest;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+
+final class PostShipmentRequest extends AuthRequest
+{
+    /** @var string */
+    protected $method = 'POST';
+
+    /** @var string */
+    protected $endPoint = '/shops/{shop}/shipping_services/{service}/shipments';
+
+    /**
+     * PostShipmentRequest constructor.
+     * @param Token $token
+     * @param int $shippingServiceId
+     * @param int $shopId
+     * @param ShipmentRequest $request
+     */
+    public function __construct(
+        Token $token,
+        $shippingServiceId,
+        $shopId,
+        ShipmentRequest $request
+    ) {
+        parent::__construct($token);
+
+        $this->endPoint = str_replace(['{shop}', '{service}'], [$shopId, $shippingServiceId], $this->endPoint);
+
+        $this->data = $request->toArray();
+    }
+}
\ No newline at end of file
diff --git a/src/Request/ShippingPlan/PutShippingPlanRequest.php b/src/Request/ShippingPlan/PutShippingPlanRequest.php
new file mode 100644
index 0000000..010e09d
--- /dev/null
+++ b/src/Request/ShippingPlan/PutShippingPlanRequest.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\ShippingPlan;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Model\ShippingPlan;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+
+
+final class PutShippingPlanRequest extends AuthRequest
+{
+    /** @var string */
+    protected $method = 'PUT';
+
+    /** @var ShippingPlan */
+    private $plan;
+
+    /** @var string */
+    protected $endPoint = '/shipping_plans/{id}';
+
+    public function __construct(Token $token, ShippingPlan $plan)
+    {
+        parent::__construct($token);
+        $this->plan = $plan;
+
+        $this->data = $plan->toArray();
+
+        $this->endPoint = str_replace('{id}', $plan->getId(), $this->endPoint);
+    }
+}
\ No newline at end of file
diff --git a/src/Request/ShippingServices/GetListRequest.php b/src/Request/ShippingServices/GetListRequest.php
new file mode 100644
index 0000000..e23bba6
--- /dev/null
+++ b/src/Request/ShippingServices/GetListRequest.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\ShippingServices;
+
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+
+final class GetListRequest extends AuthRequest
+{
+    /** @var string */
+    protected $method = 'GET';
+
+    /** @var string */
+    protected $endPoint = '/shipping_services';
+}
\ No newline at end of file
diff --git a/src/Request/ShippingServices/GetServiceRequest.php b/src/Request/ShippingServices/GetServiceRequest.php
new file mode 100644
index 0000000..91785ab
--- /dev/null
+++ b/src/Request/ShippingServices/GetServiceRequest.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\ShippingServices;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+
+final class GetServiceRequest extends AuthRequest
+{
+    /** @var string */
+    protected $method = 'GET';
+
+    /** @var string */
+    protected $endPoint = '/shipping_services/{id}';
+
+    /**
+     * PutSettingsRequest constructor.
+     * @param Token $token
+     * @param int $shippingServiceId
+     */
+    public function __construct(
+        Token $token,
+        $shippingServiceId
+    ) {
+        parent::__construct($token);
+
+        $this->endPoint = str_replace('{id}', $shippingServiceId, $this->endPoint);
+    }
+}
\ No newline at end of file
diff --git a/src/Request/ShippingServicesSettings/GetSettingsRequest.php b/src/Request/ShippingServicesSettings/GetSettingsRequest.php
new file mode 100644
index 0000000..9040b31
--- /dev/null
+++ b/src/Request/ShippingServicesSettings/GetSettingsRequest.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\ShippingServicesSettings;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Model\ShippingServiceSetting;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+use WPDesk\SaasPlatformClient\Request\Traits\SettingsItemRequestHelper;
+
+final class GetSettingsRequest extends AuthRequest
+{
+    use SettingsItemRequestHelper;
+
+    /** @var string */
+    protected $method = 'GET';
+
+    /** @var string */
+    protected $endPoint = '/shipping_services/settings/{id}';
+
+    /**
+     * PutSettingsRequest constructor.
+     * @param Token $token
+     * @param int $shippingServiceId
+     * @param int $shopId
+     * @param string $settingType
+     */
+    public function __construct(
+        Token $token,
+        $shippingServiceId,
+        $shopId,
+        $settingType = ShippingServiceSetting::TYPE_CONNECTION_SETTINGS
+    ) {
+        parent::__construct($token);
+
+        $params = $this->buildSettingsId($shippingServiceId, $shopId, $settingType);
+        $this->endPoint = str_replace('{id}', $params, $this->endPoint);
+    }
+}
\ No newline at end of file
diff --git a/src/Request/ShippingServicesSettings/PostSettingsRequest.php b/src/Request/ShippingServicesSettings/PostSettingsRequest.php
new file mode 100644
index 0000000..20e9af3
--- /dev/null
+++ b/src/Request/ShippingServicesSettings/PostSettingsRequest.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\ShippingServicesSettings;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Model\ShippingServiceSetting;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+use WPDesk\SaasPlatformClient\Request\Traits\SettingsItemRequestHelper;
+
+final class PostSettingsRequest extends AuthRequest
+{
+    use SettingsItemRequestHelper;
+
+    /** @var string */
+    protected $method = 'POST';
+
+    /** @var ShippingServiceSetting */
+    private $setting;
+
+    /** @var string */
+    protected $endPoint = '/shipping_services/settings';
+
+    public function __construct(Token $token, ShippingServiceSetting $setting)
+    {
+        parent::__construct($token);
+        $this->setting = $setting;
+
+        $this->data = $setting->toArray();
+    }
+}
\ No newline at end of file
diff --git a/src/Request/ShippingServicesSettings/PutSettingsRequest.php b/src/Request/ShippingServicesSettings/PutSettingsRequest.php
new file mode 100644
index 0000000..ea60793
--- /dev/null
+++ b/src/Request/ShippingServicesSettings/PutSettingsRequest.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\ShippingServicesSettings;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Model\ShippingServiceSetting;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+use WPDesk\SaasPlatformClient\Request\Traits\SettingsItemRequestHelper;
+
+final class PutSettingsRequest extends AuthRequest
+{
+    use SettingsItemRequestHelper;
+
+    /** @var string */
+    protected $method = 'PUT';
+
+    /** @var string */
+    protected $endPoint = '/shipping_services/settings/{id}';
+
+    /**
+     * PutSettingsRequest constructor.
+     * @param Token $token
+     * @param int $shippingServiceId
+     * @param int $shopId
+     * @param string $settingType
+     */
+    public function __construct(Token $token, ShippingServiceSetting $setting)
+    {
+        parent::__construct($token);
+
+        $params = $this->buildSettingsId($setting->getShippingService(), $setting->getShop(), $setting->getType());
+        $this->endPoint = str_replace('{id}', $params, $this->endPoint);
+
+        $this->data = $setting->toArray();
+    }
+}
\ No newline at end of file
diff --git a/src/Request/ShippingServicesSettings/Test/GetSettingsTestRequest.php b/src/Request/ShippingServicesSettings/Test/GetSettingsTestRequest.php
new file mode 100644
index 0000000..45009ad
--- /dev/null
+++ b/src/Request/ShippingServicesSettings/Test/GetSettingsTestRequest.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\ShippingServicesSettings\Test;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Model\ShippingServiceSetting;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+
+final class GetSettingsTestRequest extends AuthRequest
+{
+    /** @var string */
+    protected $method = 'GET';
+
+    /** @var string */
+    protected $endPoint = '/shops/{shop}/shipping_services/{service}/settings/{type}/test';
+
+    /**
+     * PutSettingsRequest constructor.
+     * @param Token $token
+     * @param int $shippingServiceId
+     * @param int $shopId
+     */
+    public function __construct(
+        Token $token,
+        $shippingServiceId,
+        $shopId
+    ) {
+        parent::__construct($token);
+
+        $this->endPoint = str_replace(
+            ['{shop}', '{service}', '{type}'],
+            [$shopId, $shippingServiceId, ShippingServiceSetting::TYPE_CONNECTION_SETTINGS],
+            $this->endPoint
+        );
+    }
+
+}
\ No newline at end of file
diff --git a/src/Request/Status/GetStatusRequest.php b/src/Request/Status/GetStatusRequest.php
new file mode 100644
index 0000000..59b9a17
--- /dev/null
+++ b/src/Request/Status/GetStatusRequest.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\Status;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+use WPDesk\SaasPlatformClient\Request\BasicRequest;
+
+final class GetStatusRequest extends BasicRequest
+{
+
+    /** @var string */
+    protected $method = 'GET';
+
+    /** @var string */
+    protected $endPoint = '/status';
+
+    /** @var Token */
+    private $token;
+
+    /**
+     * GetStatusRequest constructor.
+     *
+     * @param null|Token $token
+     */
+    public function __construct(
+        Token $token = null
+    ) {
+        $this->token = $token;
+    }
+
+    /**
+     * Returns array of http headers
+     *
+     * @return array
+     */
+    public function getHeaders()
+    {
+        if (null !== $this->token) {
+            $headers = array(
+                'Authorization' => $this->token->getAuthString()
+            );
+            return array_merge($headers, parent::getHeaders());
+        } else {
+            return parent::getHeaders();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/Request/Traits/SettingsItemRequestHelper.php b/src/Request/Traits/SettingsItemRequestHelper.php
new file mode 100644
index 0000000..9b97fad
--- /dev/null
+++ b/src/Request/Traits/SettingsItemRequestHelper.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\Traits;
+
+trait SettingsItemRequestHelper
+{
+
+    /**
+     * Build multiple compound key id in api-platform
+     *
+     * @param int $shippingServiceId
+     * @param int $shopId
+     * @param string|null $settingType
+     * @return string
+     */
+    private function buildSettingsId($shippingServiceId, $shopId, $settingType = null)
+    {
+        $query_data = [
+            'shippingService' => $shippingServiceId,
+            'shop' => $shopId,
+            'type' => $settingType
+        ];
+        if (null !== $settingType) {
+            $query_data['type'] = $settingType;
+        }
+        return http_build_query($query_data, null, ';');
+    }
+}
\ No newline at end of file
diff --git a/src/Request/Users/GetUserRequest.php b/src/Request/Users/GetUserRequest.php
new file mode 100644
index 0000000..62b0c7a
--- /dev/null
+++ b/src/Request/Users/GetUserRequest.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Request\Users;
+
+use WPDesk\SaasPlatformClient\Authentication\Token;
+use WPDesk\SaasPlatformClient\Request\AuthRequest;
+
+final class GetUserRequest extends AuthRequest
+{
+    /** @var string */
+    protected $method = 'GET';
+
+    /** @var string */
+    protected $endPoint = '/users/{id}';
+
+    /**
+     * getUserRequest constructor.
+     *
+     * @param $user_id
+     * @param Token $token
+     */
+    public function __construct($user_id, Token $token)
+    {
+        parent::__construct($token);
+
+        $this->endPoint = str_replace('{id}', $user_id, $this->endPoint);
+    }
+}
\ No newline at end of file
diff --git a/src/Response/ApiResponse.php b/src/Response/ApiResponse.php
new file mode 100644
index 0000000..486e321
--- /dev/null
+++ b/src/Response/ApiResponse.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response;
+
+
+interface ApiResponse extends Response
+{
+    /**
+     * Get links structure to the other request
+     *
+     * @return array
+     */
+    public function getLinks();
+
+    /**
+     * Is it a BAD REQUEST response
+     *
+     * @return bool
+     */
+    public function isBadRequest();
+
+    /**
+     * Is it a FATAL ERROR response
+     *
+     * @return bool
+     */
+    public function isServerFatalError();
+
+    /**
+     * Is requested resource exists
+     *
+     * @return bool
+     */
+    public function isNotExists();
+}
\ No newline at end of file
diff --git a/src/Response/AuthApiResponse.php b/src/Response/AuthApiResponse.php
new file mode 100644
index 0000000..05fee14
--- /dev/null
+++ b/src/Response/AuthApiResponse.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response;
+
+use WPDesk\SaasPlatformClient\Response\Traits\AuthApiResponseDecorator;
+
+class AuthApiResponse implements ApiResponse
+{
+    const RESPONSE_CODE_BAD_CREDENTIALS = 401;
+
+    const RESPONSE_CODE_NOT_EXISTS = 404;
+
+    use AuthApiResponseDecorator;
+}
\ No newline at end of file
diff --git a/src/Response/Authentication/ConnectKeyInfoResponse.php b/src/Response/Authentication/ConnectKeyInfoResponse.php
new file mode 100644
index 0000000..d89aba8
--- /dev/null
+++ b/src/Response/Authentication/ConnectKeyInfoResponse.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Authentication;
+
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\ApiResponseDecorator;
+
+final class ConnectKeyInfoResponse implements ApiResponse
+{
+    use ApiResponseDecorator;
+
+    /**
+     * @return int
+     */
+    public function getUserId()
+    {
+        return (int)$this->getResponseBody()['owner_id'];
+    }
+
+    /**
+     * @return \DateTimeImmutable
+     * @throws \Exception
+     */
+    public function getLastUsed()
+    {
+        $lastUsed = $this->getResponseBody()['lastUsed'];
+        return new \DateTimeImmutable($lastUsed['date']);
+    }
+
+    /**
+     * @return array[string]
+     */
+    public function getDomains()
+    {
+        return $this->getResponseBody()['domains'];
+    }
+}
\ No newline at end of file
diff --git a/src/Response/Authentication/ConnectKeyResetResponse.php b/src/Response/Authentication/ConnectKeyResetResponse.php
new file mode 100644
index 0000000..4f78a30
--- /dev/null
+++ b/src/Response/Authentication/ConnectKeyResetResponse.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Authentication;
+
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\AuthApiResponseDecorator;
+
+final class ConnectKeyResetResponse implements ApiResponse
+{
+    use AuthApiResponseDecorator;
+
+    /**
+     * Returns newly generated connect key
+     *
+     * @return string
+     */
+    public function getNewConnectKey()
+    {
+        $body = $this->getResponseBody();
+        return (string)$body['connectKey'];
+    }
+}
\ No newline at end of file
diff --git a/src/Response/Authentication/RegisterResponse.php b/src/Response/Authentication/RegisterResponse.php
new file mode 100644
index 0000000..046a70f
--- /dev/null
+++ b/src/Response/Authentication/RegisterResponse.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Authentication;
+
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\RawResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\ApiResponseDecorator;
+
+final class RegisterResponse implements ApiResponse
+{
+    const API_KEY_FIELD = 'password';
+
+    const RESPONSE_CODE_USER_ALREADY_EXISTS = 460;
+
+    const RESPONSE_CODE_DOMAIN_ALREADY_EXISTS = 461;
+
+    use ApiResponseDecorator;
+
+    /**
+     * Returns true if user is registered
+     *
+     * @return bool
+     */
+    public function isUserRegistered()
+    {
+        return in_array($this->getResponseCode(),
+            [RawResponse::RESPONSE_CODE_CREATED, RawResponse::RESPONSE_CODE_SUCCESS]);
+    }
+
+    /**
+     * Returns true if user is was already registered
+     *
+     * @return bool
+     */
+    public function isUserAlreadyExists()
+    {
+        return $this->getResponseCode() === self::RESPONSE_CODE_USER_ALREADY_EXISTS;
+    }
+
+    /**
+     * Returns true if user cannot be registered because domain is occupied
+     *
+     * @return bool
+     */
+    public function isDomainOccupied()
+    {
+        return $this->getResponseCode() === self::RESPONSE_CODE_DOMAIN_ALREADY_EXISTS;
+    }
+
+    /**
+     * Returns apiKey from register response
+     *
+     * @return string
+     */
+    public function getApiKey()
+    {
+        return $this->getResponseBody()[self::API_KEY_FIELD];
+    }
+}
\ No newline at end of file
diff --git a/src/Response/Authentication/TokenResponse.php b/src/Response/Authentication/TokenResponse.php
new file mode 100644
index 0000000..e7a7247
--- /dev/null
+++ b/src/Response/Authentication/TokenResponse.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Authentication;
+
+use WPDesk\SaasPlatformClient\Authentication\JWTToken;
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\AuthApiResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\ApiResponseDecorator;
+
+final class TokenResponse implements ApiResponse
+{
+    use ApiResponseDecorator;
+
+    const RESPONSE_CODE_LOGIN_SUCCESS = 200;
+
+    /**
+     * Returns token if exists
+     *
+     * @return JWTToken
+     */
+    public function getToken()
+    {
+        $body = $this->getResponseBody();
+        return new JWTToken($body['token']);
+    }
+
+    /**
+     * @return bool
+     */
+    public function isBadCredentials()
+    {
+        return $this->getResponseCode() === AuthApiResponse::RESPONSE_CODE_BAD_CREDENTIALS;
+    }
+
+    /**
+     * Returns true if login is a success and token is available
+     *
+     * @return bool
+     */
+    public function isLoginSuccess()
+    {
+        return $this->getResponseCode() === self::RESPONSE_CODE_LOGIN_SUCCESS;
+    }
+
+    /**
+     * Returns true if user cannot be registered because domain is occupied
+     *
+     * @return bool
+     */
+    public function isDomainOccupied()
+    {
+        return $this->getResponseCode() === RegisterResponse::RESPONSE_CODE_DOMAIN_ALREADY_EXISTS;
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/Response/Cancel/PostCancelResponse.php b/src/Response/Cancel/PostCancelResponse.php
new file mode 100644
index 0000000..091c610
--- /dev/null
+++ b/src/Response/Cancel/PostCancelResponse.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Cancel;
+
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\AuthApiResponseDecorator;
+
+class PostCancelResponse implements ApiResponse
+{
+    use AuthApiResponseDecorator;
+}
diff --git a/src/Response/Exception/EmptyMessageFromResponse.php b/src/Response/Exception/EmptyMessageFromResponse.php
new file mode 100644
index 0000000..870a00c
--- /dev/null
+++ b/src/Response/Exception/EmptyMessageFromResponse.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Exception;
+
+class EmptyMessageFromResponse extends \RuntimeException
+{
+    /**
+     * Exception factory
+     *
+     * @param string $request
+     * @param int $errorCode
+     * @return TriedExtractDataFromErrorResponse
+     */
+    public static function createWithClassInfo($request, $errorCode)
+    {
+        return new EmptyStatusFromResponse("Tried to extract message from {$request} when an error occured. Response code: {$errorCode}",
+            $errorCode);
+    }
+}
\ No newline at end of file
diff --git a/src/Response/Exception/EmptyStatusFromResponse.php b/src/Response/Exception/EmptyStatusFromResponse.php
new file mode 100644
index 0000000..a077eca
--- /dev/null
+++ b/src/Response/Exception/EmptyStatusFromResponse.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Exception;
+
+class EmptyStatusFromResponse extends \RuntimeException
+{
+    /**
+     * Exception factory
+     *
+     * @param string $request
+     * @param int $errorCode
+     * @return TriedExtractDataFromErrorResponse
+     */
+    public static function createWithClassInfo($request, $errorCode)
+    {
+        return new EmptyStatusFromResponse("Tried to extract status from {$request} when an error occured. Response code: {$errorCode}",
+            $errorCode);
+    }
+}
\ No newline at end of file
diff --git a/src/Response/Exception/TriedExtractDataFromErrorResponse.php b/src/Response/Exception/TriedExtractDataFromErrorResponse.php
new file mode 100644
index 0000000..38d21ff
--- /dev/null
+++ b/src/Response/Exception/TriedExtractDataFromErrorResponse.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Exception;
+
+class TriedExtractDataFromErrorResponse extends \RuntimeException
+{
+    /**
+     * Exception factory
+     *
+     * @param string $request
+     * @param int $errorCode
+     * @return TriedExtractDataFromErrorResponse
+     */
+    public static function createWithClassInfo($request, $errorCode)
+    {
+        return new TriedExtractDataFromErrorResponse("Tried to extract data from {$request} when an error occured. Response code: {$errorCode}",
+            $errorCode);
+    }
+}
\ No newline at end of file
diff --git a/src/Response/Fields/GetFieldsResponse.php b/src/Response/Fields/GetFieldsResponse.php
new file mode 100644
index 0000000..710e5e6
--- /dev/null
+++ b/src/Response/Fields/GetFieldsResponse.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Fields;
+
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\AuthApiResponseDecorator;
+
+class GetFieldsResponse implements ApiResponse
+{
+    use AuthApiResponseDecorator;
+
+    /**
+     * @return array
+     */
+    public function getFields()
+    {
+        return $this->getResponseBody()['fieldsJson'];
+    }
+}
diff --git a/src/Response/Label/PostLabelResponse.php b/src/Response/Label/PostLabelResponse.php
new file mode 100644
index 0000000..83f5985
--- /dev/null
+++ b/src/Response/Label/PostLabelResponse.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Label;
+
+use WPDesk\SaasPlatformClient\Model\Label\Label;
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\AuthApiResponseDecorator;
+
+class PostLabelResponse implements ApiResponse
+{
+    use AuthApiResponseDecorator;
+
+    /**
+     * @return Label
+     */
+    public function getLabel()
+    {
+        return new Label($this->getResponseBody());
+    }
+}
diff --git a/src/Response/Maintenance/MaintenanceResponseContext.php b/src/Response/Maintenance/MaintenanceResponseContext.php
new file mode 100644
index 0000000..fa0a066
--- /dev/null
+++ b/src/Response/Maintenance/MaintenanceResponseContext.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Maintenance;
+
+use WPDesk\SaasPlatformClient\Response\Response;
+
+class MaintenanceResponseContext
+{
+
+    /**
+     * Response.
+     *
+     * @var Response
+     */
+    private $response;
+
+    public function __construct(Response $response)
+    {
+        $this->response = $response;
+    }
+
+    /**
+     * Get maintenance message.
+     *
+     * @return string|null
+     */
+    public function getMaintenanceMessage()
+    {
+        $responseBody = $this->response->getResponseBody();
+        if (isset($responseBody['message'])) {
+            return $responseBody['message'];
+        }
+        return null;
+    }
+
+    /**
+     * Get maintenance till.
+     *
+     * @return int|null
+     */
+    public function getMaintenanceTill()
+    {
+        $responseBody = $this->response->getResponseBody();
+        if (isset($responseBody['maintenance_till'])) {
+            return intval($responseBody['maintenance_till']);
+        }
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/src/Response/ProtectedResponse.php b/src/Response/ProtectedResponse.php
new file mode 100644
index 0000000..363fde0
--- /dev/null
+++ b/src/Response/ProtectedResponse.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response;
+
+use WPDesk\SaasPlatformClient\Response\Exception\TriedExtractDataFromErrorResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\ApiResponseDecorator;
+
+/**
+ * Response is protected in a way so when you try to get body of the response when an error occured you will get an exception
+ *
+ * Class ProtectedResponse
+ * @package WPDesk\SaasPlatformClient\Response
+ */
+class ProtectedResponse implements Response
+{
+    use ApiResponseDecorator;
+
+    public function getResponseBody()
+    {
+        if ($this->isError()) {
+            throw TriedExtractDataFromErrorResponse::createWithClassInfo(get_class($this->rawResponse), $this->getResponseCode());
+        }
+        return $this->rawResponse->getResponseBody();
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/Response/Rate/PostRateResponse.php b/src/Response/Rate/PostRateResponse.php
new file mode 100644
index 0000000..8f53e7e
--- /dev/null
+++ b/src/Response/Rate/PostRateResponse.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Rate;
+
+use WPDesk\SaasPlatformClient\Model\Rate\RateResponse;
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\AuthApiResponseDecorator;
+
+class PostRateResponse implements ApiResponse
+{
+    use AuthApiResponseDecorator;
+
+    const RESPONSE_CODE_RATE_FAILED = 274;
+
+    /**
+     * @return RateResponse
+     */
+    public function getRate()
+    {
+        return new RateResponse($this->getResponseBody());
+    }
+
+    /**
+     * Platform can't respond in meaningful way about rates
+     *
+     * @return bool
+     */
+    public function isRateFailedError() {
+        return $this->rawResponse->getResponseCode() === self::RESPONSE_CODE_RATE_FAILED;
+    }
+}
diff --git a/src/Response/RawResponse.php b/src/Response/RawResponse.php
new file mode 100644
index 0000000..6360d2e
--- /dev/null
+++ b/src/Response/RawResponse.php
@@ -0,0 +1,113 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response;
+
+class RawResponse implements Response
+{
+    const RESPONSE_CODE_SUCCESS = 200;
+    const RESPONSE_CODE_CREATED = 201;
+    const RESPONSE_CODE_ERROR_BAD_REQUEST = 400;
+    const RESPONSE_CODE_DOMAIN_NOT_ALLOWED = 462;
+    const RESPONSE_CODE_ERROR_FATAL = 500;
+    const RESPONSE_CODE_MAINTENANCE = 503;
+    const HEADER_X_PLATFORM_VERSION_HASH = 'X-Platform-Version-Hash';
+
+    /** @var array */
+    private $data;
+
+    /** @var int */
+    private $code;
+
+    /** @var array */
+    private $headers;
+
+    /**
+     * RawResponse constructor.
+     * @param array $body
+     * @param int $code
+     * @param array $headers
+     */
+    public function __construct(array $body, $code, array $headers)
+    {
+        $this->data = $body;
+        $this->code = (int)$code;
+        $this->headers = $headers;
+    }
+
+    /**
+     * Returns response http code
+     *
+     * @return int
+     */
+    public function getResponseCode()
+    {
+        return $this->code;
+    }
+
+    /**
+     * Returns response body as array
+     *
+     * @return array
+     */
+    public function getResponseBody()
+    {
+        return $this->data;
+    }
+
+    /**
+     * Returns response body as array
+     *
+     * @return array
+     */
+    public function getResponseErrorBody()
+    {
+        return $this->data;
+    }
+
+    /**
+     * Returns response body as array
+     *
+     * @return array
+     */
+    public function getResponseHeaders()
+    {
+        return $this->headers;
+    }
+
+    /**
+     * Is any error occured
+     *
+     * @return bool
+     */
+    public function isError()
+    {
+        $code = $this->getResponseCode();
+        return ( $code < 200 || $code >= 300 ) && !$this->isMaintenance();
+    }
+
+    /**
+     * Is maintenance.
+     *
+     * @return bool
+     */
+    public function isMaintenance()
+    {
+        $code = $this->getResponseCode();
+        return self::RESPONSE_CODE_MAINTENANCE === $code;
+    }
+
+    /**
+     * Get platform version hash string.
+     *
+     * @return bool|string
+     */
+    public function getPlatformVersionHash()
+    {
+        if (isset($this->headers[self::HEADER_X_PLATFORM_VERSION_HASH])) {
+            return $this->headers[self::HEADER_X_PLATFORM_VERSION_HASH];
+        }
+        return false;
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/Response/Response.php b/src/Response/Response.php
new file mode 100644
index 0000000..245bfc4
--- /dev/null
+++ b/src/Response/Response.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response;
+
+
+interface Response
+{
+    /**
+     * @return int
+     */
+    public function getResponseCode();
+
+    /** @return array */
+    public function getResponseBody();
+
+    /** @return array */
+    public function getResponseHeaders();
+
+    /** @return array */
+    public function getResponseErrorBody();
+
+    /**
+     * Is any error occured
+     *
+     * @return bool
+     */
+    public function isError();
+
+    /**
+     * Is maintenance
+     *
+     * @return bool
+     */
+    public function isMaintenance();
+
+    /**
+     * Get platform version hash string.
+     *
+     * @return bool|string
+     */
+    public function getPlatformVersionHash();
+
+}
\ No newline at end of file
diff --git a/src/Response/Shipment/GetListShipmentAdminContextResponse.php b/src/Response/Shipment/GetListShipmentAdminContextResponse.php
new file mode 100644
index 0000000..3aa1bd0
--- /dev/null
+++ b/src/Response/Shipment/GetListShipmentAdminContextResponse.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Shipment;
+
+use WPDesk\SaasPlatformClient\Model\Shipment\ShipmentAdminContext;
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\AuthApiResponseDecorator;
+use WPDesk\SaasPlatformClient\Response\Traits\PagedListImplementation;
+
+class GetListShipmentAdminContextResponse implements ApiResponse
+{
+    use AuthApiResponseDecorator;
+    use PagedListImplementation;
+
+    /*
+     * @return ShippingService[]
+     */
+    public function getPage()
+    {
+        $page = $this->getRawPage();
+        $itemList = [];
+        if (count($page) > 0) {
+            foreach ($page as $item) {
+                $itemList[] = new ShipmentAdminContext($item);
+            }
+        }
+        return $itemList;
+    }
+}
\ No newline at end of file
diff --git a/src/Response/Shipment/PostShipmentResponse.php b/src/Response/Shipment/PostShipmentResponse.php
new file mode 100644
index 0000000..1153fff
--- /dev/null
+++ b/src/Response/Shipment/PostShipmentResponse.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Shipment;
+
+use WPDesk\SaasPlatformClient\Model\Shipment\ShipmentResponse;
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\AuthApiResponseDecorator;
+
+class PostShipmentResponse implements ApiResponse
+{
+    use AuthApiResponseDecorator;
+
+    const RESPONSE_CODE_SHIPMENT_PLAN_EXCEEDED = 472;
+
+    /**
+     * @return bool
+     */
+    public function isShipmentPlanExceeded()
+    {
+        return $this->getResponseCode() === self::RESPONSE_CODE_SHIPMENT_PLAN_EXCEEDED;
+    }
+
+    /**
+     * @return ShipmentResponse
+     */
+    public function getShipment()
+    {
+        return new ShipmentResponse($this->getResponseBody());
+    }
+}
diff --git a/src/Response/ShippingServices/GetShippingServiceResponse.php b/src/Response/ShippingServices/GetShippingServiceResponse.php
new file mode 100644
index 0000000..3ea05c4
--- /dev/null
+++ b/src/Response/ShippingServices/GetShippingServiceResponse.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\ShippingServices;
+
+use WPDesk\SaasPlatformClient\Model\ShippingService;
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\AuthApiResponseDecorator;
+
+final class GetShippingServiceResponse implements ApiResponse
+{
+    use AuthApiResponseDecorator;
+
+    /**
+     * @return ShippingService
+     */
+    public function getShippingService()
+    {
+        return new ShippingService($this->getResponseBody());
+    }
+}
\ No newline at end of file
diff --git a/src/Response/ShippingServices/GetShippingServicesListResponse.php b/src/Response/ShippingServices/GetShippingServicesListResponse.php
new file mode 100644
index 0000000..fc89bbf
--- /dev/null
+++ b/src/Response/ShippingServices/GetShippingServicesListResponse.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\ShippingServices;
+
+use WPDesk\SaasPlatformClient\Model\ShippingService;
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\AuthApiResponseDecorator;
+use WPDesk\SaasPlatformClient\Response\Traits\PagedListImplementation;
+
+final class GetShippingServicesListResponse implements ApiResponse
+{
+    use AuthApiResponseDecorator;
+    use PagedListImplementation;
+
+    /*
+     * @return ShippingService[]
+     */
+    public function getPage()
+    {
+        $page = $this->getRawPage();
+        $itemList = [];
+        if (count($page) > 0) {
+            foreach ($page as $item) {
+                $itemList[] = new ShippingService($item);
+            }
+        }
+        return $itemList;
+    }
+
+}
\ No newline at end of file
diff --git a/src/Response/ShippingServicesSettings/GetSettingsResponse.php b/src/Response/ShippingServicesSettings/GetSettingsResponse.php
new file mode 100644
index 0000000..1ab9835
--- /dev/null
+++ b/src/Response/ShippingServicesSettings/GetSettingsResponse.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\ShippingServicesSettings;
+
+use WPDesk\SaasPlatformClient\Model\ShippingServiceSetting;
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\AuthApiResponseDecorator;
+
+class GetSettingsResponse implements ApiResponse
+{
+    use AuthApiResponseDecorator;
+
+    /**
+     * @param array $data
+     * @return array
+     */
+    private function convertHalReferencesToJson(array $data)
+    {
+        foreach ($data['_links'] as $key => $item) {
+            $data[$key] = $item['href'];
+        }
+        return $data;
+    }
+
+    /**
+     * @return ShippingServiceSetting
+     */
+    public function getSetting()
+    {
+        return new ShippingServiceSetting($this->convertHalReferencesToJson($this->getResponseBody()));
+    }
+}
diff --git a/src/Response/ShippingServicesSettings/Test/GetSettingsTestResponse.php b/src/Response/ShippingServicesSettings/Test/GetSettingsTestResponse.php
new file mode 100644
index 0000000..ec791b2
--- /dev/null
+++ b/src/Response/ShippingServicesSettings/Test/GetSettingsTestResponse.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\ShippingServicesSettings\Test;
+
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\Exception\EmptyStatusFromResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\AuthApiResponseDecorator;
+
+class GetSettingsTestResponse implements ApiResponse
+{
+    use AuthApiResponseDecorator;
+
+    const MESSAGE = 'message';
+    const STATUS  = 'status';
+
+    public function getMessage()
+    {
+        $responseBody = $this->getResponseBody();
+        if (isset($responseBody[self::MESSAGE])) {
+            return $responseBody[self::MESSAGE];
+        } else {
+            throw EmptyMessageFromResponse::createWithClassInfo(get_class($this->rawResponse), $this->getResponseCode());
+        }
+    }
+
+    public function getStatus()
+    {
+        $responseBody = $this->getResponseBody();
+        if (isset($responseBody[self::STATUS])) {
+            return $responseBody[self::STATUS];
+        } else {
+            throw EmptyStatusFromResponse::createWithClassInfo(get_class($this->rawResponse), $this->getResponseCode());
+        }
+    }
+
+}
diff --git a/src/Response/Status/GetStatusResponse.php b/src/Response/Status/GetStatusResponse.php
new file mode 100644
index 0000000..222902c
--- /dev/null
+++ b/src/Response/Status/GetStatusResponse.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Status;
+
+use WPDesk\SaasPlatformClient\Model\Status;
+use WPDesk\SaasPlatformClient\Response\ApiResponse;
+use WPDesk\SaasPlatformClient\Response\Traits\AuthApiResponseDecorator;
+
+class GetStatusResponse implements ApiResponse
+{
+    use AuthApiResponseDecorator;
+
+    /**
+     * @return Status
+     */
+    public function getStatus()
+    {
+        return new Status($this->getResponseBody());
+    }
+}
diff --git a/src/Response/Traits/ApiResponseDecorator.php b/src/Response/Traits/ApiResponseDecorator.php
new file mode 100644
index 0000000..e02c35f
--- /dev/null
+++ b/src/Response/Traits/ApiResponseDecorator.php
@@ -0,0 +1,145 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Traits;
+
+use WPDesk\SaasPlatformClient\Response\AuthApiResponse;
+use WPDesk\SaasPlatformClient\Response\RawResponse;
+use WPDesk\SaasPlatformClient\Response\Response;
+
+trait ApiResponseDecorator
+{
+
+    /** @var RawResponse */
+    private $rawResponse;
+
+    /**
+     * RawResponseDecorator constructor.
+     * @param Response $rawResponse
+     */
+    public function __construct(Response $rawResponse)
+    {
+        $this->rawResponse = $rawResponse;
+    }
+
+    /**
+     * Returns response http code
+     *
+     * @return int
+     */
+    public function getResponseCode()
+    {
+        return $this->rawResponse->getResponseCode();
+    }
+
+    /**
+     * Returns response body as array
+     *
+     * @return array
+     */
+    public function getResponseBody()
+    {
+        return $this->rawResponse->getResponseBody();
+    }
+
+    /**
+     * Returns response body as array
+     *
+     * @return array
+     */
+    public function getResponseErrorBody()
+    {
+        return $this->rawResponse->getResponseErrorBody();
+    }
+
+    /**
+     * Returns response body as array
+     *
+     * @return array
+     */
+    public function getResponseHeaders()
+    {
+        return $this->rawResponse->getResponseHeaders();
+    }
+
+    /**
+     * Get links structure to the other request
+     *
+     * @return array
+     */
+    public function getLinks()
+    {
+        $body = $this->getResponseBody();
+        return $body['_links'];
+    }
+
+    /**
+     * Is it a BAD REQUEST response
+     *
+     * @return bool
+     */
+    public function isBadRequest()
+    {
+        return $this->getResponseCode() === RawResponse::RESPONSE_CODE_ERROR_BAD_REQUEST;
+    }
+
+    /**
+     * Is it a DOMAIN NOT ALLOWED response
+     *
+     * @return bool
+     */
+    public function isDomainNotAllowed()
+    {
+        return $this->getResponseCode() === RawResponse::RESPONSE_CODE_DOMAIN_NOT_ALLOWED;
+    }
+
+    /**
+     * Is it a FATAL ERROR response
+     *
+     * @return bool
+     */
+    public function isServerFatalError()
+    {
+        return $this->getResponseCode() === RawResponse::RESPONSE_CODE_ERROR_FATAL;
+    }
+
+    /**
+     * Is any error occured
+     *
+     * @return bool
+     */
+    public function isError()
+    {
+        return $this->rawResponse->isError();
+    }
+
+    /**
+     * Is requested resource exists
+     *
+     * @return bool
+     */
+    public function isNotExists()
+    {
+        return $this->getResponseCode() === AuthApiResponse::RESPONSE_CODE_NOT_EXISTS;
+    }
+
+    /**
+     * Is maintenance.
+     *
+     * @return bool
+     */
+    public function isMaintenance()
+    {
+        return $this->rawResponse->isMaintenance();
+    }
+
+    /**
+     * Get platform version hash string.
+     *
+     * @return bool|string
+     */
+    public function getPlatformVersionHash()
+    {
+        return $this->rawResponse->getPlatformVersionHash();
+    }
+
+    }
\ No newline at end of file
diff --git a/src/Response/Traits/AuthApiResponseDecorator.php b/src/Response/Traits/AuthApiResponseDecorator.php
new file mode 100644
index 0000000..5e45ff8
--- /dev/null
+++ b/src/Response/Traits/AuthApiResponseDecorator.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Traits;
+
+use WPDesk\SaasPlatformClient\Response\AuthApiResponse;
+
+trait AuthApiResponseDecorator
+{
+    use ApiResponseDecorator;
+
+    /**
+     * @return bool
+     */
+    public function isBadCredentials()
+    {
+        return $this->getResponseCode() === AuthApiResponse::RESPONSE_CODE_BAD_CREDENTIALS;
+    }
+
+    /**
+     * Is bad credential because token expires
+     *
+     * @return bool
+     */
+    public function isTokenExpired()
+    {
+        return $this->isBadCredentials();
+    }
+
+    /**
+     * Is bad credential because token is invalid
+     *
+     * @return bool
+     */
+    public function isTokenInvalid()
+    {
+        return $this->isBadCredentials();
+    }
+}
\ No newline at end of file
diff --git a/src/Response/Traits/PagedListImplementation.php b/src/Response/Traits/PagedListImplementation.php
new file mode 100644
index 0000000..f364978
--- /dev/null
+++ b/src/Response/Traits/PagedListImplementation.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Traits;
+
+trait PagedListImplementation
+{
+    /*
+     * @return array
+     */
+    public function getRawPage()
+    {
+        $body = $this->getResponseBody();
+        if ($body['_embedded'] !== null && $body['_embedded']['item'] !== null) {
+            return $body['_embedded']['item'];
+        }
+        return [];
+    }
+
+    /**
+     * @return int
+     */
+    public function getPageCount()
+    {
+        return (int)floor($this->getItemCount() / $this->getItemsPerPage());
+    }
+
+    /**
+     * @return int
+     */
+    public function getItemsPerPage()
+    {
+        $body = $this->getResponseBody();
+        return (int)$body['itemsPerPage'];
+    }
+
+    /**
+     * @return int
+     */
+    public function getItemCount()
+    {
+        $body = $this->getResponseBody();
+        return (int)$body['totalItems'];
+    }
+}
\ No newline at end of file
diff --git a/src/Response/Users/GetUserResponse.php b/src/Response/Users/GetUserResponse.php
new file mode 100644
index 0000000..199a517
--- /dev/null
+++ b/src/Response/Users/GetUserResponse.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace WPDesk\SaasPlatformClient\Response\Users;
+
+use WPDesk\SaasPlatformClient\Model\User;
+use WPDesk\SaasPlatformClient\Response\Response;
+use WPDesk\SaasPlatformClient\Response\Traits\AuthApiResponseDecorator;
+
+final class GetUserResponse implements Response
+{
+    use AuthApiResponseDecorator;
+
+    /**
+     * @return User
+     */
+    public function getUser()
+    {
+        return new User($this->getResponseBody());
+    }
+}
\ No newline at end of file
diff --git a/tests/docker-compose.yaml b/tests/docker-compose.yaml
new file mode 100644
index 0000000..2a86b03
--- /dev/null
+++ b/tests/docker-compose.yaml
@@ -0,0 +1,172 @@
+version: '2.0'
+
+services:
+
+  wordpress:
+    image: wpdesknet/phpunit-woocommerce:0-0
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql0
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql0
+
+  wordpress-0-1:
+    image: wpdesknet/phpunit-woocommerce:0-1
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql1
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql1
+
+  wordpress-0-2:
+    image: wpdesknet/phpunit-woocommerce:0-2
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql2
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql2
+
+  wordpress-0-3:
+    image: wpdesknet/phpunit-woocommerce:0-3
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql3
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql3
+
+  wordpress-0-4:
+    image: wpdesknet/phpunit-woocommerce:0-4
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql4
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql4
+
+  wordpress-0-5:
+    image: wpdesknet/phpunit-woocommerce:0-5
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql5
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql5
+
+  wordpress-1-0:
+    image: wpdesknet/phpunit-woocommerce:1-0
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql0
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql0
+
+  wordpress-2-0:
+    image: wpdesknet/phpunit-woocommerce:2-0
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql0
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql0
+
+  wordpress-3-0:
+    image: wpdesknet/phpunit-woocommerce:3-0
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql0
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql0
+
+  wordpress-4-0:
+    image: wpdesknet/phpunit-woocommerce:4-0
+    volumes:
+    - .././:/opt/project
+    depends_on:
+    - mysql0
+    environment:
+      WORDPRESS_DB_NAME: wptest
+      WORDPRESS_DB_USER: mysql
+      WORDPRESS_DB_PASSWORD: mysql
+      WORDPRESS_DB_HOST: mysql0
+
+  mysql0:
+    image: mysql:5.7
+    environment:
+      MYSQL_ROOT_PASSWORD: mysql
+      MYSQL_DATABASE: wptest
+      MYSQL_USER: mysql
+      MYSQL_PASSWORD: mysql
+
+  mysql1:
+    image: mysql:5.7
+    environment:
+      MYSQL_ROOT_PASSWORD: mysql
+      MYSQL_DATABASE: wptest
+      MYSQL_USER: mysql
+      MYSQL_PASSWORD: mysql
+
+  mysql2:
+    image: mysql:5.7
+    environment:
+      MYSQL_ROOT_PASSWORD: mysql
+      MYSQL_DATABASE: wptest
+      MYSQL_USER: mysql
+      MYSQL_PASSWORD: mysql
+
+  mysql3:
+    image: mysql:5.7
+    environment:
+      MYSQL_ROOT_PASSWORD: mysql
+      MYSQL_DATABASE: wptest
+      MYSQL_USER: mysql
+      MYSQL_PASSWORD: mysql
+
+  mysql4:
+    image: mysql:5.7
+    environment:
+      MYSQL_ROOT_PASSWORD: mysql
+      MYSQL_DATABASE: wptest
+      MYSQL_USER: mysql
+      MYSQL_PASSWORD: mysql
+
+  mysql5:
+    image: mysql:5.7
+    environment:
+      MYSQL_ROOT_PASSWORD: mysql
+      MYSQL_DATABASE: wptest
+      MYSQL_USER: mysql
+      MYSQL_PASSWORD: mysql
+
diff --git a/tests/integration/bootstrap.php b/tests/integration/bootstrap.php
new file mode 100644
index 0000000..a422fd9
--- /dev/null
+++ b/tests/integration/bootstrap.php
@@ -0,0 +1,28 @@
+<?php
+
+ini_set('error_reporting', E_ALL); // or error_reporting(E_ALL);
+ini_set('display_errors', '1');
+ini_set('display_startup_errors', '1');
+
+require_once __DIR__ . '/../../vendor/autoload.php';
+
+// disable xdebug backtrace
+if ( function_exists( 'xdebug_disable' ) ) {
+	xdebug_disable();
+}
+
+if ( getenv( 'PLUGIN_PATH' ) !== false ) {
+	define( 'PLUGIN_PATH', getenv( 'PLUGIN_PATH' ) );
+} else {
+	define( 'PLUGIN_PATH', __DIR__ . DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR );
+}
+
+require_once( getenv( 'WP_DEVELOP_DIR' ) . '/tests/phpunit/includes/functions.php' );
+
+tests_add_filter( 'muplugins_loaded', function () {
+}, 100 );
+
+putenv('WP_TESTS_DIR=' . getenv( 'WP_DEVELOP_DIR' ) . '/tests/phpunit');
+require_once( getenv( 'WC_DEVELOP_DIR' ) . '/tests/bootstrap.php' );
+
+do_action('plugins_loaded');
\ No newline at end of file
diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php
new file mode 100644
index 0000000..76b8109
--- /dev/null
+++ b/tests/unit/bootstrap.php
@@ -0,0 +1,9 @@
+<?php
+/**
+ * PHPUnit bootstrap file
+ */
+
+require_once __DIR__ . '/../../vendor/autoload.php';
+
+WP_Mock::setUsePatchwork( true );
+WP_Mock::bootstrap();
-- 
GitLab