diff --git a/src/availabilities/Service.php b/src/availabilities/Service.php new file mode 100644 index 0000000..e88ea24 --- /dev/null +++ b/src/availabilities/Service.php @@ -0,0 +1,405 @@ + + * @version 2.2.0 + * @license Copyright (c) 2015-2023 Meplato GmbH. All rights reserved. + * @link https://developer.meplato.com/store2/#terms Terms of Service + * @link https://developer.meplato.com/store2/ External documentation + */ +class Service +{ + /** @@var string API title */ + const TITLE = "Meplato Store API"; + /** @@var string API version */ + const VERSION = "2.2.0"; + /** @@var string Base URL of the service, including the path */ + const BASE_URL = "https://store.meplato.com/api/v2"; + /** @@var string User Agent string that will be sent to the server */ + const USER_AGENT = "meplato-php-client/2.0"; + + /** @var HttpClientInterface Interface for making HTTP requests */ + private $client; + /** @var string Base URL for the API, including the path (default: self::BASE_URL) */ + private $baseURL; + /** @var string User name part of the credentials */ + private $user; + /** @var string Password */ + private $password; + + function __construct($client) + { + $this->client = $client; + $this->baseURL = self::BASE_URL; + } + + function getClient() + { + return $this->client; + } + + function getBaseURL() + { + return $this->baseURL; + } + + function setBaseURL($baseURL) + { + $this->baseURL = $baseURL; + } + + function getUser() + { + return $this->user; + } + + function setUser($user) + { + $this->user = $user; + } + + function getPassword() + { + return $this->password; + } + + function setPassword($password) + { + $this->password = $password; + } + + function delete() + { + return new DeleteService($this); + } + + function get() + { + return new GetService($this); + } + + function upsert() + { + return new UpsertService($this); + } +} + + +/** + * Delete availability information of a product. It is an asynchronous + * operation. + */ +class DeleteService +{ + private $service; + private $opt = []; + private $hdr = []; + private $spn; + + /** + * Creates a new instance of DeleteService. + */ + function __construct($service) + { + $this->service = $service; + } + + /** + * 2-letter ISO code of the country/region where the product is stored + * + * @param $region (string) + * @return $this so that the function is chainable + */ + function region($region) + { + $this->opt["region"] = $region; + return $this; + } + + /** + * SPN is the unique identifier of a product within a merchant. + * + * @param $spn (string) + * @return $this so that the function is chainable + */ + function spn($spn) + { + $this->spn = $spn; + return $this; + } + + /** + * Zip code where the product is stored + * + * @param $zipCode (string) + * @return $this so that the function is chainable + */ + function zipCode($zipCode) + { + $this->opt["zipCode"] = $zipCode; + return $this; + } + + /** + * Execute the service call. + * + * The return values has the following properties: + * - kind (string): Kind describes this entity, it will be store#availability/deleteResponse. + * + * @return array Deserialized JSON object + * @throws \Meplato\Store2\ServiceException if something goes wrong + */ + function execute() + { + // Parameters (in template and query string) + $params = []; + if (array_key_exists("region", $this->opt)) { + $params["region"] = $this->opt["region"]; + } + $params["spn"] = $this->spn; + if (array_key_exists("zipCode", $this->opt)) { + $params["zipCode"] = $this->opt["zipCode"]; + } + + // HTTP Headers + $headers = [ + "User-Agent" => Service::USER_AGENT, + "Accept" => "application/json", + "Content-Type" => "application/json" + ]; + + $user = $this->service->getUser(); + $pass = $this->service->getPassword(); + if (!empty($user) || !empty($pass)) { + $credentials = base64_encode("{$user}:{$pass}"); + $headers["Authorization"] = "Basic {$credentials}"; + } + + $urlTemplate = $this->service->getBaseURL() . "/products/{spn}/availabilities{?region,zipCode}"; + + $body = NULL; + + // Execute request + $response = $this->service->getClient()->execute("delete", $urlTemplate, $params, $headers, $body); + $status = $response->getStatusCode(); + if ($status >= 200 && $status <= 299) { + return $response->getBodyJSON(); + } + throw new \Meplato\Store2\ServiceException($response); + } +} + + + +/** + * Read availability information of a product + */ +class GetService +{ + private $service; + private $opt = []; + private $hdr = []; + private $spn; + + /** + * Creates a new instance of GetService. + */ + function __construct($service) + { + $this->service = $service; + } + + /** + * 2-letter ISO code of the country/region where the product is stored + * + * @param $region (string) + * @return $this so that the function is chainable + */ + function region($region) + { + $this->opt["region"] = $region; + return $this; + } + + /** + * SPN is the unique identifier of a product within a merchant. + * + * @param $spn (string) + * @return $this so that the function is chainable + */ + function spn($spn) + { + $this->spn = $spn; + return $this; + } + + /** + * Zip code where the product is stored + * + * @param $zipCode (string) + * @return $this so that the function is chainable + */ + function zipCode($zipCode) + { + $this->opt["zipCode"] = $zipCode; + return $this; + } + + /** + * Execute the service call. + * + * The return values has the following properties: + * - items (array): Collection of availability information associated with an SPN for a merchant. + * - kind (string): Kind is store#availability/getResponse for this kind of response. + * + * @return array Deserialized JSON object + * @throws \Meplato\Store2\ServiceException if something goes wrong + */ + function execute() + { + // Parameters (in template and query string) + $params = []; + if (array_key_exists("region", $this->opt)) { + $params["region"] = $this->opt["region"]; + } + $params["spn"] = $this->spn; + if (array_key_exists("zipCode", $this->opt)) { + $params["zipCode"] = $this->opt["zipCode"]; + } + + // HTTP Headers + $headers = [ + "User-Agent" => Service::USER_AGENT, + "Accept" => "application/json", + "Content-Type" => "application/json" + ]; + + $user = $this->service->getUser(); + $pass = $this->service->getPassword(); + if (!empty($user) || !empty($pass)) { + $credentials = base64_encode("{$user}:{$pass}"); + $headers["Authorization"] = "Basic {$credentials}"; + } + + $urlTemplate = $this->service->getBaseURL() . "/products/{spn}/availabilities{?region,zipCode}"; + + $body = NULL; + + // Execute request + $response = $this->service->getClient()->execute("get", $urlTemplate, $params, $headers, $body); + $status = $response->getStatusCode(); + if ($status >= 200 && $status <= 299) { + return $response->getBodyJSON(); + } + throw new \Meplato\Store2\ServiceException($response); + } +} + + + +/** + * Update or create availability information of a product. It is an asynchronous + * operation. + */ +class UpsertService +{ + private $service; + private $opt = []; + private $hdr = []; + private $spn; + private $availability; + + /** + * Creates a new instance of UpsertService. + */ + function __construct($service) + { + $this->service = $service; + } + + /** + * Availability properties of the product. + * + * @param $availability (array) + * @return $this so that the function is chainable + */ + function availability($availability) + { + $this->availability = $availability; + return $this; + } + + /** + * SPN is the unique identifier of a product within a merchant. + * + * @param $spn (string) + * @return $this so that the function is chainable + */ + function spn($spn) + { + $this->spn = $spn; + return $this; + } + + /** + * Execute the service call. + * + * The return values has the following properties: + * - kind (string): Kind describes this entity, it will be store#availability/upsertResponse. + * - link (string): Link includes the URL where this resource will be available + * + * @return array Deserialized JSON object + * @throws \Meplato\Store2\ServiceException if something goes wrong + */ + function execute() + { + // Parameters (in template and query string) + $params = []; + $params["spn"] = $this->spn; + + // HTTP Headers + $headers = [ + "User-Agent" => Service::USER_AGENT, + "Accept" => "application/json", + "Content-Type" => "application/json" + ]; + + $user = $this->service->getUser(); + $pass = $this->service->getPassword(); + if (!empty($user) || !empty($pass)) { + $credentials = base64_encode("{$user}:{$pass}"); + $headers["Authorization"] = "Basic {$credentials}"; + } + + $urlTemplate = $this->service->getBaseURL() . "/products/{spn}/availabilities"; + + $body = json_encode($this->availability); + + // Execute request + $response = $this->service->getClient()->execute("put", $urlTemplate, $params, $headers, $body); + $status = $response->getStatusCode(); + if ($status >= 200 && $status <= 299) { + return $response->getBodyJSON(); + } + throw new \Meplato\Store2\ServiceException($response); + } +} + + +?> diff --git a/tests/availabilities/BaseTest.php b/tests/availabilities/BaseTest.php new file mode 100644 index 0000000..a575a1e --- /dev/null +++ b/tests/availabilities/BaseTest.php @@ -0,0 +1,40 @@ +getHttpClient(); + $this->service = new \Meplato\Store2\Availabilities\Service($client); + $this->service->setBaseURL("http://store2.go/api/v2"); + return $this->service; + } + + protected function setUp(): void + { + } + + protected function tearDown(): void + { + } +} +?> diff --git a/tests/availabilities/DeleteTest.php b/tests/availabilities/DeleteTest.php new file mode 100644 index 0000000..fc53a4b --- /dev/null +++ b/tests/availabilities/DeleteTest.php @@ -0,0 +1,40 @@ +getService(); + $this->mockResponseFromFile('availabilities.delete.success'); + $response = $service->delete()->spn('1234')->execute(); + + $this->assertIsArray($response); + $this->assertArrayHasKey('kind', $response); + $this->assertEquals('store#availabilities/deleteResponse', $response['kind']); + } +} +?> diff --git a/tests/availabilities/GetTest.php b/tests/availabilities/GetTest.php new file mode 100644 index 0000000..cadf4b5 --- /dev/null +++ b/tests/availabilities/GetTest.php @@ -0,0 +1,64 @@ +getService(); + $this->mockResponseFromFile('availabilities.get.success'); + $response = $service->get()->spn("1234")->execute(); + $this->assertIsArray($response); + $this->assertArrayHasKey('kind', $response); + $this->assertEquals('store#availabilities/getResponse', $response['kind']); + + $this->assertArrayHasKey('items', $response); + $this->assertIsArray($response['items']); + $this->assertCount(3, $response['items']); + $this->assertEquals('1234', $response['items'][0]['spn']); + } + + /** + * Test what happens when availabilities are not found. + * + * @group availabilities + * @group availabilities.get + */ + public function testGetNotFound() + { + $service = $this->getService(); + $this->mockResponseFromFile('availabilities.get.not_found'); + + $response = $service->get()->spn('no-such-product')->execute(); + + $this->assertIsArray($response); + $this->assertArrayHasKey('kind', $response); + $this->assertEquals('store#availabilities/getResponse', $response['kind']); + + $this->assertArrayHasKey('items', $response); + $this->assertNull($response['items']); + } +} +?> diff --git a/tests/availabilities/UpsertTest.php b/tests/availabilities/UpsertTest.php new file mode 100644 index 0000000..d72ce95 --- /dev/null +++ b/tests/availabilities/UpsertTest.php @@ -0,0 +1,46 @@ + "not in stock", + 'Quantity'=> 0, + 'Region'=> "AQ", + 'Updated'=> "Q1/2024", + 'ZipCode'=> "1234", + ]; + + $service = $this->getService(); + + // Upsert availabilities, i.e. either create or update. + $this->mockResponseFromFile('availabilities.upsert.success'); + $response = $service->upsert()->spn(1234)->availability($availability)->execute(); + $this->assertIsArray($response); + $this->assertArrayHasKey('kind', $response); + $this->assertEquals('store#availabilities/upsertResponse', $response['kind']); + $this->assertArrayHasKey('link', $response); + } +} +?> diff --git a/tests/mock/responses/availabilities.delete.success b/tests/mock/responses/availabilities.delete.success new file mode 100644 index 0000000..e0ebfc2 --- /dev/null +++ b/tests/mock/responses/availabilities.delete.success @@ -0,0 +1,15 @@ +HTTP/1.1 202 OK +Cache-Control: private, no-cache +Content-Type: application/json; charset=utf-8 +Last-Modified: Tue, 31 Mar 2015 14:18:15 GMT +P3p: CP="This is not a P3P policy!" +Vary: Cookie +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +X-Ua-Compatible: IE=edge +X-Xss-Protection: 1; mode=block +Date: Tue, 25 Mar 2024 14:18:15 GMT + +{ + "kind": "store#availabilities/deleteResponse" +} \ No newline at end of file diff --git a/tests/mock/responses/availabilities.get.not_found b/tests/mock/responses/availabilities.get.not_found new file mode 100644 index 0000000..004094c --- /dev/null +++ b/tests/mock/responses/availabilities.get.not_found @@ -0,0 +1,17 @@ +HTTP/1.1 200 +Cache-Control: private, no-cache +Content-Type: application/json; charset=utf-8 +Last-Modified: Tue, 31 Mar 2015 14:15:25 GMT +P3p: CP="This is not a P3P policy!" +Vary: Cookie +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +X-Ua-Compatible: IE=edge +X-Xss-Protection: 1; mode=block +Date: Tue, 31 Mar 2015 14:15:25 GMT + +{ + "kind": "store#availabilities/getResponse", + "items": null, + "Error": null +} diff --git a/tests/mock/responses/availabilities.get.success b/tests/mock/responses/availabilities.get.success new file mode 100644 index 0000000..5a77f9e --- /dev/null +++ b/tests/mock/responses/availabilities.get.success @@ -0,0 +1,45 @@ +HTTP/1.1 200 OK +Cache-Control: private, no-cache +Content-Type: application/json; charset=utf-8 +Last-Modified: Tue, 31 Mar 2015 14:18:15 GMT +P3p: CP="This is not a P3P policy!" +Vary: Cookie +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +X-Ua-Compatible: IE=edge +X-Xss-Protection: 1; mode=block +Date: Tue, 25 Mar 2024 14:18:15 GMT + +{ + "kind": "store#availabilities/getResponse", + "Error": null, + "items": [ + { + "message": "in stock", + "mpcc": "meplato", + "quantity": 15.3, + "region": "UK", + "spn": "1234", + "updated": "Q4/2022", + "zipCode": "04109" + }, + { + "message": "in stock", + "mpcc": "meplato", + "quantity": 35.0, + "region": "DK", + "spn": "1234", + "updated": "Q4/2022", + "zipCode": "05109" + }, + { + "message": "in stock", + "mpcc": "meplato", + "quantity": 20.4, + "region": "DE", + "spn": "1234", + "updated": "Q4/2022", + "zipCode": "06109" + } + ] +} \ No newline at end of file diff --git a/tests/mock/responses/availabilities.upsert.success b/tests/mock/responses/availabilities.upsert.success new file mode 100644 index 0000000..9590369 --- /dev/null +++ b/tests/mock/responses/availabilities.upsert.success @@ -0,0 +1,16 @@ +HTTP/1.1 202 OK +Cache-Control: private, no-cache +Content-Type: application/json; charset=utf-8 +Last-Modified: Tue, 31 Mar 2015 14:18:15 GMT +P3p: CP="This is not a P3P policy!" +Vary: Cookie +X-Content-Type-Options: nosniff +X-Frame-Options: SAMEORIGIN +X-Ua-Compatible: IE=edge +X-Xss-Protection: 1; mode=block +Date: Tue, 25 Mar 2024 14:18:15 GMT + +{ + "kind": "store#availabilities/upsertResponse", + "link": "string" +} \ No newline at end of file