From 7fc9bdb0b9416b5cd2409da2a47bf17a2d26a566 Mon Sep 17 00:00:00 2001 From: Maximilian Beckers Date: Thu, 15 Feb 2018 14:38:21 +0100 Subject: [PATCH 1/4] fixed #20 --- CHANGELOG.md | 7 + README.md | 7 + composer.json | 3 +- src/Exception/DeviceApiCallException.php | 10 ++ src/Helper/DeviceAddressInformationHelper.php | 116 +++++++++++++ .../Device/DeviceAddressInformation.php | 70 ++++++++ src/Request/System.php | 14 +- .../DeviceAddressInformationHelperTest.php | 155 ++++++++++++++++++ test/Tests/Request/RequestData/launch.json | 1 + 9 files changed, 378 insertions(+), 5 deletions(-) create mode 100644 src/Exception/DeviceApiCallException.php create mode 100644 src/Helper/DeviceAddressInformationHelper.php create mode 100644 src/Request/Device/DeviceAddressInformation.php create mode 100644 test/Tests/Helper/DeviceAddressInformationHelperTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 402157d..cf1bbc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [1.1.0](https://github.com/maxbeckers/amazon-alexa-php/tree/1.1.0) (2018-02-15) +[Full Changelog](https://github.com/maxbeckers/amazon-alexa-php/compare/1.0.16...1.1.0) + +**Closed issues:** + +- Service for device address information [\#20](https://github.com/maxbeckers/amazon-alexa-php/issues/20) + ## [1.0.16](https://github.com/maxbeckers/amazon-alexa-php/tree/1.0.16) (2018-02-06) [Full Changelog](https://github.com/maxbeckers/amazon-alexa-php/compare/1.0.15...1.0.16) diff --git a/README.md b/README.md index 5fc64c1..d36fee7 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,13 @@ public function handleRequest(Request $request): Response return $this->responseHelper->respond('Success :)'); } ``` +## Check device address information +To get either "Full Address" or "Country & Postal Code" from the customer you need the permissions for user api call. More informations for the call see [device-address-api](https://developer.amazon.com/de/docs/custom-skills/device-address-api.html). +```php +$helper = new DeviceAddressInformationHelper(); +$fullAddress = $helper->getAddress($request); +$countryAndPostalCode = $helper->getCountryAndPostalCode($request); +``` ## Generate SSML For SSML output you can use the `SsmlGenerator`. With the helper will generate valid SSML for alexa. All types of alexa known SSML tags have a function in the `SsmlGeneator`. You can add all SSML you need to the generator and call `getSsml` to get the full string. diff --git a/composer.json b/composer.json index e5fdd46..16ebd31 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,8 @@ ], "minimum-stability": "dev", "require": { - "php": ">=7.0" + "php": ">=7.0", + "guzzlehttp/guzzle": ">=6.0" }, "require-dev": { "friendsofphp/php-cs-fixer": ">=2.8", diff --git a/src/Exception/DeviceApiCallException.php b/src/Exception/DeviceApiCallException.php new file mode 100644 index 0000000..a1d444c --- /dev/null +++ b/src/Exception/DeviceApiCallException.php @@ -0,0 +1,10 @@ + + */ +class DeviceApiCallException extends \Exception +{ +} diff --git a/src/Helper/DeviceAddressInformationHelper.php b/src/Helper/DeviceAddressInformationHelper.php new file mode 100644 index 0000000..46dd79f --- /dev/null +++ b/src/Helper/DeviceAddressInformationHelper.php @@ -0,0 +1,116 @@ +//developer.amazon.com/de/docs/custom-skills/device-address-api.html. + * + * @author Maximilian Beckers + */ +class DeviceAddressInformationHelper +{ + /** + * @var Client + */ + private $client; + + /** + * @param Client|null $client + */ + public function __construct(Client $client = null) + { + $this->client = $client ?: new Client(); + } + + /** + * @param Request $request + * + * @throws MissingRequestDataException + * + * @return DeviceAddressInformation + */ + public function getCountryAndPostalCode(Request $request): DeviceAddressInformation + { + if (!isset($request->context->system->device->deviceId, $request->context->system->apiAccessToken, $request->context->system->apiEndpoint)) { + throw new MissingRequestDataException(); + } + + $deviceId = $request->context->system->device->deviceId; + $token = $request->context->system->apiAccessToken; + $endpoint = $request->context->system->apiEndpoint; + + $url = sprintf('%s/v1/devices/%s/settings/address/countryAndPostalCode', $endpoint, $deviceId); + + return $this->apiCall($url, $token); + } + + /** + * @param Request $request + * + * @throws MissingRequestDataException + * + * @return DeviceAddressInformation + */ + public function getAddress(Request $request): DeviceAddressInformation + { + if (!isset($request->context->system->device->deviceId, $request->context->system->apiAccessToken, $request->context->system->apiEndpoint)) { + throw new MissingRequestDataException(); + } + + $deviceId = $request->context->system->device->deviceId; + $token = $request->context->system->apiAccessToken; + $endpoint = $request->context->system->apiEndpoint; + + $url = sprintf('%s/v1/devices/%s/settings/address', $endpoint, $deviceId); + + return $this->apiCall($url, $token); + } + + /** + * @param string $url + * @param string $token + * + * @throws DeviceApiCallException + * + * @return DeviceAddressInformation + */ + private function apiCall(string $url, string $token): DeviceAddressInformation + { + $response = $this->client->request('GET', $url, [ + 'headers' => [ + 'Authorization' => 'Basic '.$token, + 'Accept' => 'application/json', + ], + ]); + + /* + * Api Call response codes: + * 200 OK Successfully got the address associated with this deviceId. + * 204 No Content The query did not return any results. + * 403 Forbidden The authentication token is invalid or doesn’t have access to the resource. + * 405 Method Not Allowed The method is not supported. + * 429 Too Many Requests The skill has been throttled due to an excessive number of requests. + * 500 Internal Error An unexpected error occurred. + */ + switch ($response->getStatusCode()) { + case 200: + break; + case 204: + case 403: + case 405: + case 429: + case 500: + default: + throw new DeviceApiCallException(sprintf('Error in api call (status code:"%s")', $response->getStatusCode())); + } + + return DeviceAddressInformation::fromApiResponse(json_decode($response->getBody()->getContents(), true)); + } +} diff --git a/src/Request/Device/DeviceAddressInformation.php b/src/Request/Device/DeviceAddressInformation.php new file mode 100644 index 0000000..0670a51 --- /dev/null +++ b/src/Request/Device/DeviceAddressInformation.php @@ -0,0 +1,70 @@ + + */ +class DeviceAddressInformation +{ + /** + * @var string|null + */ + public $stateOrRegion; + + /** + * @var string|null + */ + public $city; + + /** + * @var string|null + */ + public $countryCode; + + /** + * @var string|null + */ + public $postalCode; + + /** + * @var string|null + */ + public $addressLine1; + + /** + * @var string|null + */ + public $addressLine2; + + /** + * @var string|null + */ + public $addressLine3; + + /** + * @var string|null + */ + public $districtOrCounty; + + /** + * @param array $amazonApiResponse + * + * @return DeviceAddressInformation + */ + public static function fromApiResponse(array $amazonApiResponse): self + { + $deviceAddressInformation = new self(); + + $deviceAddressInformation->stateOrRegion = isset($amazonApiResponse['stateOrRegion']) ? $amazonApiResponse['stateOrRegion'] : null; + $deviceAddressInformation->city = isset($amazonApiResponse['city']) ? $amazonApiResponse['city'] : null; + $deviceAddressInformation->countryCode = isset($amazonApiResponse['countryCode']) ? $amazonApiResponse['countryCode'] : null; + $deviceAddressInformation->postalCode = isset($amazonApiResponse['postalCode']) ? $amazonApiResponse['postalCode'] : null; + $deviceAddressInformation->addressLine1 = isset($amazonApiResponse['addressLine1']) ? $amazonApiResponse['addressLine1'] : null; + $deviceAddressInformation->addressLine2 = isset($amazonApiResponse['addressLine2']) ? $amazonApiResponse['addressLine2'] : null; + $deviceAddressInformation->addressLine3 = isset($amazonApiResponse['addressLine3']) ? $amazonApiResponse['addressLine3'] : null; + $deviceAddressInformation->districtOrCounty = isset($amazonApiResponse['districtOrCounty']) ? $amazonApiResponse['districtOrCounty'] : null; + + return $deviceAddressInformation; + } +} diff --git a/src/Request/System.php b/src/Request/System.php index e89dc55..0e11fba 100644 --- a/src/Request/System.php +++ b/src/Request/System.php @@ -22,6 +22,11 @@ class System */ public $device; + /** + * @var string|null + */ + public $apiAccessToken; + /** * @var string|null */ @@ -36,10 +41,11 @@ public static function fromAmazonRequest(array $amazonRequest): self { $system = new self(); - $system->application = isset($amazonRequest['application']) ? Application::fromAmazonRequest($amazonRequest['application']) : null; - $system->user = isset($amazonRequest['user']) ? User::fromAmazonRequest($amazonRequest['user']) : null; - $system->device = isset($amazonRequest['device']) ? Device::fromAmazonRequest($amazonRequest['device']) : null; - $system->apiEndpoint = isset($amazonRequest['apiEndpoint']) ? $amazonRequest['apiEndpoint'] : null; + $system->application = isset($amazonRequest['application']) ? Application::fromAmazonRequest($amazonRequest['application']) : null; + $system->user = isset($amazonRequest['user']) ? User::fromAmazonRequest($amazonRequest['user']) : null; + $system->device = isset($amazonRequest['device']) ? Device::fromAmazonRequest($amazonRequest['device']) : null; + $system->apiAccessToken = isset($amazonRequest['apiAccessToken']) ? $amazonRequest['apiAccessToken'] : null; + $system->apiEndpoint = isset($amazonRequest['apiEndpoint']) ? $amazonRequest['apiEndpoint'] : null; return $system; } diff --git a/test/Tests/Helper/DeviceAddressInformationHelperTest.php b/test/Tests/Helper/DeviceAddressInformationHelperTest.php new file mode 100644 index 0000000..97b6d7b --- /dev/null +++ b/test/Tests/Helper/DeviceAddressInformationHelperTest.php @@ -0,0 +1,155 @@ + + */ +class DeviceAddressInformationHelperTest extends TestCase +{ + public function testGetCountryAndPostalCodeSuccess() + { + $responseData = [ + 'countryCode' => 'US', + 'postalCode' => '98109', + ]; + + $client = $this->createMock(Client::class); + $apiResponse = $this->createMock(ResponseInterface::class); + $apiResponseBody = $this->createMock(StreamInterface::class); + + $client->method('request') + ->willReturn($apiResponse); + $apiResponse->method('getStatusCode') + ->willReturn(200); + $apiResponse->method('getBody') + ->willReturn($apiResponseBody); + $apiResponseBody->method('getContents') + ->willReturn(json_encode($responseData)); + + $deviceAddressInformationHelper = new DeviceAddressInformationHelper($client); + $this->assertSame( + Device\DeviceAddressInformation::fromApiResponse($responseData), + $deviceAddressInformationHelper->getCountryAndPostalCode($this->createDummyRequest('id', 'https://test.com', 'test')) + ); + } + + public function testGetCountryAndPostalCodeError() + { + $client = $this->createMock(Client::class); + $apiResponse = $this->createMock(ResponseInterface::class); + + $client->method('request') + ->willReturn($apiResponse); + $apiResponse->method('getStatusCode') + ->willReturn(403); + + $deviceAddressInformationHelper = new DeviceAddressInformationHelper($client); + $this->expectException(DeviceApiCallException::class); + $deviceAddressInformationHelper->getCountryAndPostalCode($this->createDummyRequest('id', 'https://test.com', 'test')); + } + + public function testGetCountryAndPostalCodeMissingData() + { + $client = $this->createMock(Client::class); + + $deviceAddressInformationHelper = new DeviceAddressInformationHelper($client); + $this->expectException(MissingRequestDataException::class); + $deviceAddressInformationHelper->getCountryAndPostalCode($this->createDummyRequest()); + } + + public function testGetAddressSuccess() + { + $responseData = [ + 'stateOrRegion' => 'WA', + 'city' => 'Seattle', + 'countryCode' => 'US', + 'postalCode' => '98109', + 'addressLine1' => '410 Terry Ave North', + 'addressLine2' => '', + 'addressLine3' => 'aeiou', + 'districtOrCounty' => '', + ]; + + $client = $this->createMock(Client::class); + $apiResponse = $this->createMock(ResponseInterface::class); + $apiResponseBody = $this->createMock(StreamInterface::class); + + $client->method('request') + ->willReturn($apiResponse); + $apiResponse->method('getStatusCode') + ->willReturn(200); + $apiResponse->method('getBody') + ->willReturn($apiResponseBody); + $apiResponseBody->method('getContents') + ->willReturn(json_encode($responseData)); + + $deviceAddressInformationHelper = new DeviceAddressInformationHelper($client); + $this->assertSame( + Device\DeviceAddressInformation::fromApiResponse($responseData), + $deviceAddressInformationHelper->getAddress($this->createDummyRequest('id', 'https://test.com', 'test')) + ); + } + + public function testGetAddressError() + { + $client = $this->createMock(Client::class); + $apiResponse = $this->createMock(ResponseInterface::class); + + $client->method('request') + ->willReturn($apiResponse); + $apiResponse->method('getStatusCode') + ->willReturn(500); + + $deviceAddressInformationHelper = new DeviceAddressInformationHelper($client); + $this->expectException(DeviceApiCallException::class); + $deviceAddressInformationHelper->getAddress($this->createDummyRequest('id', 'https://test.com', 'test')); + } + + public function testGetAddressMissingData() + { + $client = $this->createMock(Client::class); + + $deviceAddressInformationHelper = new DeviceAddressInformationHelper($client); + $this->expectException(MissingRequestDataException::class); + $deviceAddressInformationHelper->getCountryAndPostalCode(new Request()); + } + + /** + * @param string $deviceId + * @param string $apiEndpoint + * @param string $apiAccessToken + * + * @return Request + */ + private function createDummyRequest(string $deviceId = null, string $apiEndpoint = null, string $apiAccessToken = null): Request + { + $device = new Device(); + $device->deviceId = $deviceId; + + $system = new System(); + $system->device = $device; + $system->apiEndpoint = $apiEndpoint; + $system->apiAccessToken = $apiAccessToken; + + $context = new Context(); + $context->system = $system; + + $request = new Request(); + $request->context = $context; + + return $request; + } +} diff --git a/test/Tests/Request/RequestData/launch.json b/test/Tests/Request/RequestData/launch.json index 4cfff58..a7b854c 100644 --- a/test/Tests/Request/RequestData/launch.json +++ b/test/Tests/Request/RequestData/launch.json @@ -35,6 +35,7 @@ "AudioPlayer": {} } }, + "apiAccessToken": "apiAccessToken", "apiEndpoint": "apiEndpoint" }, "AudioPlayer": { From 786f59896815e916507fe07cea916f1760e60055 Mon Sep 17 00:00:00 2001 From: Maximilian Beckers Date: Thu, 15 Feb 2018 14:43:27 +0100 Subject: [PATCH 2/4] fix tests --- test/Tests/Helper/DeviceAddressInformationHelperTest.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/Tests/Helper/DeviceAddressInformationHelperTest.php b/test/Tests/Helper/DeviceAddressInformationHelperTest.php index 97b6d7b..9c9f99f 100644 --- a/test/Tests/Helper/DeviceAddressInformationHelperTest.php +++ b/test/Tests/Helper/DeviceAddressInformationHelperTest.php @@ -1,7 +1,5 @@ willReturn(json_encode($responseData)); $deviceAddressInformationHelper = new DeviceAddressInformationHelper($client); - $this->assertSame( + $this->assertEquals( Device\DeviceAddressInformation::fromApiResponse($responseData), $deviceAddressInformationHelper->getCountryAndPostalCode($this->createDummyRequest('id', 'https://test.com', 'test')) ); @@ -97,7 +95,7 @@ public function testGetAddressSuccess() ->willReturn(json_encode($responseData)); $deviceAddressInformationHelper = new DeviceAddressInformationHelper($client); - $this->assertSame( + $this->assertEquals( Device\DeviceAddressInformation::fromApiResponse($responseData), $deviceAddressInformationHelper->getAddress($this->createDummyRequest('id', 'https://test.com', 'test')) ); From 920fbfed1360d9f065c94d7114666d6fe04065a3 Mon Sep 17 00:00:00 2001 From: Maximilian Beckers Date: Thu, 15 Feb 2018 14:44:11 +0100 Subject: [PATCH 3/4] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf1bbc9..4167052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log ## [1.1.0](https://github.com/maxbeckers/amazon-alexa-php/tree/1.1.0) (2018-02-15) -[Full Changelog](https://github.com/maxbeckers/amazon-alexa-php/compare/1.0.16...1.1.0) +[Full Changelog](https://github.com/maxbeckers/amazon-alexa-php/compare/1.0.0...1.1.0) **Closed issues:** From 747b359c7865b146d9790c2f3d8acb216726e637 Mon Sep 17 00:00:00 2001 From: Maximilian Beckers Date: Thu, 15 Feb 2018 14:54:23 +0100 Subject: [PATCH 4/4] cs fix --- .php_cs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.php_cs.php b/.php_cs.php index 941245e..884e857 100644 --- a/.php_cs.php +++ b/.php_cs.php @@ -78,7 +78,7 @@ 'php_unit_construct' => true, 'php_unit_dedicate_assert' => true, 'php_unit_fqcn_annotation' => true, - 'php_unit_strict' => true, + 'php_unit_strict' => false, 'phpdoc_add_missing_param_annotation' => true, 'phpdoc_align' => true, 'phpdoc_annotation_without_dot' => true,