config = $config; $this->tokenStorage = $tokenStorage; $this->signer = $signer; } /** * Returns credentials * use $useMigrationToken = true for /authorization/v1/authorizationCode request * @param false $useMigrationToken * @return array * @throws \Exception */ public function getCredentials($useMigrationToken = false) { $lwaAccessToken = $useMigrationToken === true ? $this->getMigrationToken() : ($useMigrationToken === 'grantless' ? $this->getGrantlessAuthToken() : $this->getLWAToken()); $stsCredentials = $this->getStsTokens(); return [ 'access_token' => $lwaAccessToken, 'sts_credentials' => $stsCredentials ]; } /** * Prepares credentials for clients which require Restricted Data Tokens * see: https://github.com/amzn/selling-partner-api-docs/blob/main/guides/en-US/use-case-guides/tokens-api-use-case-guide/tokens-API-use-case-guide-2021-03-01.md * * * @param array https://github.com/amzn/selling-partner-api-docs/blob/main/references/tokens-api/tokens_2021-03-01.md#createrestricteddatatokenrequest$restrictedOperations Array with items representing CreateRestrictedDataTokenRequest * see: https://github.com/amzn/selling-partner-api-docs/blob/main/references/tokens-api/tokens_2021-03-01.md#createrestricteddatatokenrequest * @return array aceess token and STS credentials */ public function getRdtCredentials(array $restrictedOperations) { $rdAccessToken = $this->getRestrictedDataAccessToken($restrictedOperations); $stsCredentials = $this->getStsTokens(); return [ 'access_token' => $rdAccessToken, 'sts_credentials' => $stsCredentials ]; } private function getRestrictedDataAccessToken($restrictedOperations = []) { $restrictedOperationsHash = md5(\json_encode($restrictedOperations)); $tokenKey = 'restricted_data_token_' . $restrictedOperationsHash; $knownToken = $this->loadTokenFromStorage($tokenKey); if (!is_null($knownToken)) { return $knownToken; } $cred = $this->getCredentials(); $tokensClient = new \DoubleBreak\Spapi\Api\Tokens($cred, $this->config); $result = $tokensClient->createRestrictedDataToken($restrictedOperations); $rdt = $result['restrictedDataToken']; $expiresOn = time() + $result['expiresIn']; $this->tokenStorage->storeToken($tokenKey, [ 'token' => $rdt, 'expiresOn' => $expiresOn ]); return $rdt; } private function getLWAToken() { $knownToken = $this->loadTokenFromStorage('lwa_access_token'); if (!is_null($knownToken)) { return $knownToken; } $client = $this->createHttpClient([ 'base_uri' => 'https://api.amazon.com', 'timeout' => 3.0, ]); try { $requestOptions = [ 'form_params' => [ 'grant_type' => 'refresh_token', 'refresh_token' => $this->config['refresh_token'], 'client_id' => $this->config['client_id'], 'client_secret' => $this->config['client_secret'] ] ]; $response = $client->post('/auth/o2/token', $requestOptions); } catch (\Exception $e) { //log something throw $e; } $json = json_decode($response->getBody(), true); $this->tokenStorage->storeToken('lwa_access_token', [ 'token' => $json['access_token'], 'expiresOn' => time() + ($this->config['access_token_longevity'] ?? 3600) ]); return $json['access_token']; } /** * Request a Login with Amazon access token * @see https://github.com/amzn/selling-partner-api-docs/blob/main/guides/developer-guide/SellingPartnerApiDeveloperGuide.md#step-1-request-a-login-with-amazon-access-token * @return mixed * @throws \Exception */ public function getMigrationToken() { $knownToken = $this->loadTokenFromStorage('migration_token'); if (!is_null($knownToken)) { return $knownToken; } $client = $this->createHttpClient([ 'base_uri' => 'https://api.amazon.com', 'timeout' => 3.0, ]); try { $requestOptions = [ 'form_params' => [ 'grant_type' => 'client_credentials', 'scope' => 'sellingpartnerapi::migration', 'client_id' => $this->config['client_id'], 'client_secret' => $this->config['client_secret'] ] ]; $response = $client->post('/auth/o2/token', $requestOptions); $json = json_decode($response->getBody(), true); } catch (\Exception $e) { //log something throw $e; } if (!array_key_exists('access_token', $json)) { throw new IncorrectResponseException('Failed to load migration token.'); } $this->tokenStorage->storeToken('migration_token', [ 'token' => $json['access_token'], 'expiresOn' => time() + $json['expires_in'] ]); return $json['access_token']; } public function getGrantlessAuthToken() { $knownToken = $this->loadTokenFromStorage('grantless_auth_token'); if (!is_null($knownToken)) { return $knownToken; } $client = $this->createHttpClient([ 'base_uri' => 'https://api.amazon.com', 'timeout' => 3.0, ]); try { $requestOptions = [ 'form_params' => [ 'grant_type' => 'client_credentials', 'scope' => 'sellingpartnerapi::notifications', 'client_id' => $this->config['client_id'], 'client_secret' => $this->config['client_secret'] ] ]; $response = $client->post('/auth/o2/token', $requestOptions); $json = json_decode($response->getBody(), true); } catch (\Exception $e) { //log something throw $e; } if (!array_key_exists('access_token', $json)) { throw new IncorrectResponseException('Failed to load grantless auth token.'); } $this->tokenStorage->storeToken('grantless_auth_token', [ 'token' => $json['access_token'], 'expiresOn' => time() + $json['expires_in'] ]); return $json['access_token']; } private function getStsTokens() { $knownToken = $this->loadTokenFromStorage('sts_credentials'); if (!is_null($knownToken)) { return $knownToken; } $requestOptions = [ 'headers' => [ 'accept' => 'application/json' ], 'form_params' => [ 'Action' => 'AssumeRole', 'DurationSeconds' => $this->config['sts_session _longevity'] ?? 3600, 'RoleArn' => $this->config['role_arn'], 'RoleSessionName' => 'session1', 'Version' => '2011-06-15', ] ]; $host = 'sts.amazonaws.com'; $uri = '/'; $requestOptions = $this->signer->sign($requestOptions, [ 'service' => 'sts', 'access_key' => $this->config['access_key'], 'secret_key' => $this->config['secret_key'], 'region' => 'us-east-1', //This should be hardcoded 'host' => $host, 'uri' => $uri, 'payload' => \GuzzleHttp\Psr7\build_query($requestOptions['form_params']), 'method' => 'POST', ]); $client = $this->createHttpClient([ 'base_uri' => 'https://' . $host, 'timeout' => 3.0, ]); try { $response = $client->post($uri, $requestOptions); $json = json_decode($response->getBody(), true); $credentials = $json['AssumeRoleResponse']['AssumeRoleResult']['Credentials'] ?? null; $tokens = [ 'access_key' => $credentials['AccessKeyId'], 'secret_key' => $credentials['SecretAccessKey'], 'session_token' => $credentials['SessionToken'] ]; $this->tokenStorage->storeToken('sts_credentials', [ 'token' => $tokens, 'expiresOn' => $credentials['Expiration'] ]); return $tokens; } catch (\Exception $e) { //log something throw $e; } } /** * Exchanges the LWA authorization code for an LWA refresh token * @see https://github.com/amzn/selling-partner-api-docs/blob/main/guides/developer-guide/SellingPartnerApiDeveloperGuide.md#step-5-your-application-exchanges-the-lwa-authorization-code-for-an-lwa-refresh-token * @param $authorizationCode * @throws \Exception */ public function exchangesAuthorizationCodeForRefreshToken($authorizationCode) { $client = $this->createHttpClient([ 'base_uri' => 'https://api.amazon.com', 'timeout' => 3.0, ]); try { $requestOptions = [ 'form_params' => [ 'grant_type' => 'authorization_code', 'code' => $authorizationCode, 'client_id' => $this->config['client_id'], 'client_secret' => $this->config['client_secret'] ] ]; $response = $client->post('/auth/o2/token', $requestOptions); return json_decode($response->getBody(), true); } catch (\Exception $e) { throw $e; } } private function loadTokenFromStorage($key) { $knownToken = $this->tokenStorage->getToken($key); if (!empty($knownToken)) { $expiresOn = $knownToken['expiresOn']; if ($expiresOn > time()) { return $knownToken['token']; } } return null; } }