├── .gitignore ├── NOTICE ├── clients ├── sellingpartner-api-documents-helper-java │ ├── settings.gradle │ ├── src │ │ ├── test │ │ │ ├── resources │ │ │ │ └── mockito-extensions │ │ │ │ │ └── org.mockito.plugins.Mockmaker │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── amazon │ │ │ │ └── spapi │ │ │ │ └── documents │ │ │ │ ├── exception │ │ │ │ ├── CryptoExceptionTest.java │ │ │ │ ├── MissingCharsetExceptionTest.java │ │ │ │ └── HttpResponseExceptionTest.java │ │ │ │ ├── DownloadBundleTest.java │ │ │ │ ├── CompressionAlgorithmTest.java │ │ │ │ ├── DownloadSpecificationTest.java │ │ │ │ ├── impl │ │ │ │ └── AESCryptoStreamFactoryTest.java │ │ │ │ └── UploadSpecificationTest.java │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── amazon │ │ │ └── spapi │ │ │ └── documents │ │ │ ├── exception │ │ │ ├── CryptoException.java │ │ │ ├── MissingCharsetException.java │ │ │ └── HttpResponseException.java │ │ │ ├── CryptoStreamFactory.java │ │ │ ├── CompressionAlgorithm.java │ │ │ ├── HttpTransferClient.java │ │ │ ├── DownloadSpecification.java │ │ │ ├── UploadSpecification.java │ │ │ ├── impl │ │ │ ├── AESCryptoStreamFactory.java │ │ │ └── OkHttpTransferClient.java │ │ │ └── DownloadHelper.java │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── build.gradle │ └── pom.xml ├── sellingpartner-api-aa-java │ ├── resources │ │ ├── java │ │ │ └── config.json │ │ └── swagger-codegen │ │ │ └── templates │ │ │ ├── api_test.mustache │ │ │ ├── build.gradle.mustache │ │ │ └── StringUtil.mustache │ ├── .gitignore │ ├── src │ │ └── com │ │ │ └── amazon │ │ │ └── SellingPartnerAPIAA │ │ │ ├── RateLimitConfiguration.java │ │ │ ├── LWAAccessTokenCache.java │ │ │ ├── LWAAccessTokenCacheItem.java │ │ │ ├── LWAExceptionErrorCode.java │ │ │ ├── ScopeConstants.java │ │ │ ├── LWAClientScopes.java │ │ │ ├── LWAAccessTokenRequestMeta.java │ │ │ ├── RateLimitConfigurationOnRequests.java │ │ │ ├── LWAException.java │ │ │ ├── LWAClientScopesSerializerDeserializer.java │ │ │ ├── LWAAuthorizationCredentials.java │ │ │ ├── LWAAccessTokenCacheImpl.java │ │ │ ├── LWAAuthorizationSigner.java │ │ │ ├── RestrictedDataTokenSigner.java │ │ │ └── LWAClient.java │ ├── tst │ │ └── com │ │ │ └── amazon │ │ │ └── SellingPartnerAPIAA │ │ │ └── LWAClientScopesSerializerDeserializerTest.java │ └── README.md ├── sellingpartner-api-aa-javascript │ ├── src │ │ ├── js.config.json │ │ ├── resources │ │ │ └── swagger-codegen │ │ │ │ └── templates │ │ │ │ ├── model.mustache │ │ │ │ ├── package.mustache │ │ │ │ └── index.mustache │ │ ├── sample_node_app │ │ │ └── index.mjs │ │ ├── helper │ │ │ └── LwaAuthClient.mjs │ │ └── generate-js-sdk.sh │ └── package.json ├── sellingpartner-api-aa-csharp │ ├── .gitignore │ ├── src │ │ └── Amazon.SellingPartnerAPIAA │ │ │ ├── IDateHelper.cs │ │ │ ├── SigningDateHelper.cs │ │ │ ├── RateLimitConfiguration.cs │ │ │ ├── ScopeConstants.cs │ │ │ ├── LWAExceptionErrorCode.cs │ │ │ ├── RateLimitConfigurationOnRequests.cs │ │ │ ├── Amazon.SellingPartnerAPIAA.csproj │ │ │ ├── LWAAuthorizationCredentials.cs │ │ │ ├── LWAAuthorizationSigner.cs │ │ │ ├── LWAException.cs │ │ │ ├── LWAAccessTokenRequestMeta.cs │ │ │ ├── LWAAccessTokenRequestMetaBuilder.cs │ │ │ ├── resources │ │ │ └── swagger-codegen │ │ │ │ └── templates │ │ │ │ ├── api_test.mustache │ │ │ │ └── IReadableConfiguration.mustache │ │ │ ├── Utils.cs │ │ │ └── LWAClient.cs │ ├── test │ │ └── Amazon.SellingPartnerAPIAATests │ │ │ ├── Amazon.SellingPartnerAPIAATests.csproj │ │ │ ├── UtilsTest.cs │ │ │ ├── LWAAuthorizationSignerTest.cs │ │ │ └── LWAAccessTokenRequestMetaBuilderTest.cs │ ├── SellingPartnerAPIAuthAndAuthCSharp.sln │ └── README.md ├── sellingpartner-api-aa-php │ ├── src │ │ └── authandauth │ │ │ ├── ScopeConstants.php │ │ │ ├── RateLimitConfiguration.php │ │ │ ├── LWAAuthorizationSigner.php │ │ │ ├── LWAAccessTokenCache.php │ │ │ ├── RateLimitConfigurationOnRequests.php │ │ │ ├── LWAAuthorizationCredentials.php │ │ │ ├── LWAAccessTokenRequestMeta.php │ │ │ ├── RestrictedDataTokenSigner.php │ │ │ └── LWAClient.php │ ├── resources │ │ └── openapi-generator │ │ │ ├── config.json │ │ │ └── templates │ │ │ ├── model_enum.mustache │ │ │ ├── model.mustache │ │ │ ├── composer.mustache │ │ │ ├── ModelInterface.mustache │ │ │ └── ApiException.mustache │ ├── composer.json │ └── README.md ├── sellingpartner-api-aa-python │ ├── auth │ │ ├── credentials.py │ │ ├── LwaExceptionErrorCode.py │ │ ├── LwaException.py │ │ └── LwaRequest.py │ ├── test_lwaexception.py │ ├── requirements.txt │ ├── resources │ │ └── api_test.mustache │ ├── test.py │ ├── test_lwarequest.py │ └── spapi │ │ └── spapiclient.py ├── postman-collections │ └── tokens-api-sandbox-postman-collection.json └── sample-code │ └── DelegatedRestrictedDataTokenWorkflowForDelegatee.java ├── .github ├── pull_request_template.md └── workflows │ ├── trigger-sdk-repository.yml │ ├── closing_inactive_issues.yml │ └── rdme-openapi.yml ├── models ├── authorization-api-model │ └── authorization.md ├── feeds-api-model │ └── feeds_2020-09-04.md ├── reports-api-model │ └── reports_2020-09-04.md └── fba-small-and-light-api-model │ └── fbaSmallandLight.md ├── CODE_OF_CONDUCT.md ├── README.md ├── schemas ├── feeds │ └── listings-feed-schema-v2.example.json ├── reports │ ├── b2bProductOpportunitiesNotYetOnAmazonReport-2020-11-19.json │ └── vendorRealTimeTrafficReport.json └── notifications │ └── FBAOutboundShipmentStatusNotification.json └── CONTRIBUTING.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "sellingpartner-api-documents-helper-java" -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/resources/java/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "artifactVersion": "3.0", 3 | "library": "okhttp4-gson" 4 | } -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/test/resources/mockito-extensions/org.mockito.plugins.Mockmaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 2 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/ 3 | target/ 4 | out/ 5 | .settings/ 6 | .DS_Store 7 | .classpath 8 | .project 9 | 10 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-javascript/src/js.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "usePromises": true, 3 | "useES6": true, 4 | "modelPropertyNaming": "camelCase" 5 | } -------------------------------------------------------------------------------- /models/authorization-api-model/authorization.md: -------------------------------------------------------------------------------- 1 | This API has been removed. Refer to the [SP-API Deprecations](https://developer-docs.amazon.com/sp-api/docs/sp-api-deprecations) table for more information. -------------------------------------------------------------------------------- /models/feeds-api-model/feeds_2020-09-04.md: -------------------------------------------------------------------------------- 1 | This API has been removed. Refer to the [SP-API Deprecations](https://developer-docs.amazon.com/sp-api/docs/sp-api-deprecations) table for more information. 2 | -------------------------------------------------------------------------------- /models/reports-api-model/reports_2020-09-04.md: -------------------------------------------------------------------------------- 1 | This API has been removed. Refer to the [SP-API Deprecations](https://developer-docs.amazon.com/sp-api/docs/sp-api-deprecations) table for more information. 2 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/.gitignore: -------------------------------------------------------------------------------- 1 | #Ignore bin directories 2 | bin 3 | **/bin 4 | #Ignore obj directories 5 | obj 6 | **/obj 7 | #ignore .vs directories 8 | .vs 9 | #misc 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amzn/selling-partner-api-models/HEAD/clients/sellingpartner-api-documents-helper-java/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/IDateHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace Amazon.SellingPartnerAPIAA 3 | { 4 | public interface IDateHelper 5 | { 6 | DateTime GetUtcNow(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/RateLimitConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.amazon.SellingPartnerAPIAA; 2 | 3 | public interface RateLimitConfiguration { 4 | 5 | Double getRateLimitPermit(); 6 | 7 | Long getTimeOut(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAccessTokenCache.java: -------------------------------------------------------------------------------- 1 | package com.amazon.SellingPartnerAPIAA; 2 | 3 | public interface LWAAccessTokenCache { 4 | String get(Object key); 5 | void put(Object key, String accessToken, long tokenTTLInSeconds); 6 | } 7 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-php/src/authandauth/ScopeConstants.php: -------------------------------------------------------------------------------- 1 | licenseInfo}} 2 | {{=< >=}} 3 | import {ApiClient} from '../ApiClient.js'; 4 | <#imports> 5 | import {} from './.js'; 6 | 7 | <={{ }}=> 8 | 9 | {{#models}} 10 | {{#model}} 11 | {{#isEnum}} 12 | {{>partial_model_enum_class}} 13 | {{/isEnum}} 14 | {{^isEnum}} 15 | {{>partial_model_generic}} 16 | {{/isEnum}} 17 | {{/model}} 18 | {{/models}} 19 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-python/test_lwaexception.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from auth.LwaException import LwaException 3 | 4 | class TestLwaException(unittest.TestCase): 5 | 6 | def test_exception_creation(self): 7 | error = LwaException("invalid_client", "Invalid client ID") 8 | self.assertEqual(str(error), "LWA Error - Code: invalid_client, Message: Invalid client ID") 9 | 10 | # Add more test cases as needed 11 | 12 | if __name__ == '__main__': 13 | unittest.main() 14 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-python/auth/LwaExceptionErrorCode.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | class LwaExceptionErrorCode(Enum): 4 | ACCESS_DENIED = "access_denied" 5 | INVALID_GRANT = "invalid_grant" 6 | INVALID_REQUEST = "invalid_request" 7 | INVALID_SCOPE = "invalid_scope" 8 | SERVER_ERROR = "server_error" 9 | TEMPORARILY_UNAVAILABLE = "temporarily_unavailable" 10 | UNAUTHORIZED_CLIENT = "unauthorized_client" 11 | INVALID_CLIENT = "invalid_client" 12 | OTHER = "other" 13 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/test/java/com/amazon/spapi/documents/exception/CryptoExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents.exception; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class CryptoExceptionTest { 8 | @Test 9 | public void testConstructor() { 10 | Throwable throwable = new RuntimeException(); 11 | CryptoException exception = new CryptoException(throwable); 12 | 13 | assertSame(throwable, exception.getCause()); 14 | } 15 | } -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/test/java/com/amazon/spapi/documents/exception/MissingCharsetExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents.exception; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class MissingCharsetExceptionTest { 8 | @Test 9 | public void testConstructor() { 10 | String message = "This is the message"; 11 | MissingCharsetException exception = new MissingCharsetException(message); 12 | 13 | assertEquals(message, exception.getMessage()); 14 | } 15 | } -------------------------------------------------------------------------------- /.github/workflows/trigger-sdk-repository.yml: -------------------------------------------------------------------------------- 1 | name: Send dispatch event to selling-partner-api-sdk 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - 'main' 8 | paths: 9 | - 'clients/**' 10 | - 'models/**' 11 | 12 | jobs: 13 | trigger-event: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Trigger workflow 17 | run: | 18 | curl -X POST https://api.github.com/repos/amzn/selling-partner-api-sdk/dispatches \ 19 | -H 'Accept: application/vnd.github.everest-preview+json' \ 20 | -u ${{ secrets.MAFGE_PAT }} \ 21 | --data '{"event_type": "trigger-build"}' 22 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAClientScopes.java: -------------------------------------------------------------------------------- 1 | package com.amazon.SellingPartnerAPIAA; 2 | 3 | 4 | import com.google.gson.annotations.JsonAdapter; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | 8 | import java.util.Set; 9 | 10 | @AllArgsConstructor 11 | @Getter 12 | @JsonAdapter(LWAClientScopesSerializerDeserializer.class) 13 | public class LWAClientScopes { 14 | 15 | private final Set scopes; 16 | 17 | protected void addScope(String scope) { 18 | scopes.add(scope); 19 | 20 | } 21 | 22 | protected boolean isEmpty() { 23 | return scopes.isEmpty(); 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAccessTokenRequestMeta.java: -------------------------------------------------------------------------------- 1 | package com.amazon.SellingPartnerAPIAA; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | @Data 8 | @Builder 9 | class LWAAccessTokenRequestMeta { 10 | @SerializedName("grant_type") 11 | private String grantType; 12 | 13 | @SerializedName("refresh_token") 14 | private String refreshToken; 15 | 16 | @SerializedName("client_id") 17 | private String clientId; 18 | 19 | @SerializedName("client_secret") 20 | private String clientSecret; 21 | 22 | @SerializedName("scope") 23 | private LWAClientScopes scopes; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-python/auth/LwaException.py: -------------------------------------------------------------------------------- 1 | 2 | class LwaException(Exception): 3 | def __init__(self, error_code, error_message, cause=None): 4 | super().__init__(f"LWA Error - Code {error_code}, Message: {error_message}") 5 | self.error_code = error_code 6 | self.error_message = error_message 7 | self.cause = cause 8 | 9 | def __str__(self): 10 | cause_str = f", Cause: {self.cause}" if self.cause else "" 11 | return f"LWA Error - Code: {self.error_code}, Message: {self.error_message}{cause_str}" 12 | 13 | def get_error_code(self): 14 | return self.error_code 15 | 16 | def get_error_message(self): 17 | return self.error_message 18 | 19 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/RateLimitConfigurationOnRequests.java: -------------------------------------------------------------------------------- 1 | package com.amazon.SellingPartnerAPIAA; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class RateLimitConfigurationOnRequests implements RateLimitConfiguration { 9 | 10 | /** 11 | * RateLimiter Permit 12 | */ 13 | private Double rateLimitPermit; 14 | 15 | /** 16 | * Timeout for RateLimiter 17 | */ 18 | private Long waitTimeOutInMilliSeconds; 19 | 20 | @Override 21 | public Long getTimeOut() { 22 | return waitTimeOutInMilliSeconds; 23 | } 24 | 25 | @Override 26 | public Double getRateLimitPermit() { 27 | return rateLimitPermit; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/RateLimitConfigurationOnRequests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Amazon.SellingPartnerAPIAA 6 | { 7 | public class RateLimitConfigurationOnRequests : RateLimitConfiguration 8 | { 9 | /** 10 | * RateLimiter Permit 11 | */ 12 | public int RateLimitPermit; 13 | 14 | /** 15 | * Timeout for RateLimiter 16 | */ 17 | public int WaitTimeOutInMilliSeconds; 18 | 19 | public int getRateLimitPermit() 20 | { 21 | return RateLimitPermit; 22 | } 23 | 24 | public int getTimeOut() 25 | { 26 | return WaitTimeOutInMilliSeconds; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/Amazon.SellingPartnerAPIAA.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net461 5 | 2.0 6 | 2.0 7 | 2.0 8 | 2.0 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-php/resources/openapi-generator/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "additionalProperties": { 3 | "customTemplateDir": "./resources/openapi-generator/templates", 4 | "common_header" : "/**\n * Selling Partner API\n *\n * The Selling Partner API enables developers to programmatically retrieve information from various domains.\n * These APIs provide tools for building fast, flexible, and custom applications,\n * as well as demand-based decision support systems.\n *\n * The version of the OpenAPI document: v0\n * Generated by: https://openapi-generator.tech\n * Generator version: 7.9.0\n */" 5 | }, 6 | "files": { 7 | "ModelInterface.mustache": { 8 | "templateFile": "ModelInterface.mustache", 9 | "destinationFilename": "lib/Model/ModelInterface.php" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-javascript/src/sample_node_app/index.mjs: -------------------------------------------------------------------------------- 1 | import { AppConfig } from '../app.config.mjs'; 2 | import { LwaAuthClient } from '../helper/LwaAuthClient.mjs'; 3 | import { SellersApi, ApiClient as SellersApiClient } from '../../sdk/src/sellers/index.js'; 4 | 5 | (async () => { 6 | const lwaClient = new LwaAuthClient(AppConfig.lwaClientId, AppConfig.lwaClientSecret, AppConfig.lwaRefreshToken); 7 | const sellerApiClient = new SellersApiClient(AppConfig.endpoint); 8 | 9 | const sellerApi = new SellersApi(sellerApiClient); 10 | sellerApiClient.applyXAmzAccessTokenToRequest(await lwaClient.getAccessToken()); 11 | const participations = await sellerApi.getMarketplaceParticipations(); 12 | console.log(JSON.stringify(participations) + "\n**********************************"); 13 | })(); 14 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-javascript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@amzn/testsellingpartnerjavascriptapilwalib", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "description": "The NPM package name should always start with `@amzn/` to cleanly separate from public packages, avoid accidental publish to public repository, and allow publishing to CodeArtifact.", 6 | "main": "/src/index.mjs", 7 | "scripts": { 8 | "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config test_for_generated_sdk/jest.config.json" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "ssh://git.amazon.com/pkg/TestSellingPartnerJavaScriptApiLwaLib" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "superagent": "^8.0.9" 18 | }, 19 | "devDependencies": { 20 | "jest": "^29.6.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-php/resources/openapi-generator/templates/model_enum.mustache: -------------------------------------------------------------------------------- 1 | class {{classname}} 2 | { 3 | /** 4 | * Possible values of this enum 5 | */ 6 | {{#allowableValues}} 7 | {{#enumVars}} 8 | {{#enumDescription}} 9 | /** 10 | * {{enumDescription}} 11 | */ 12 | {{/enumDescription}} 13 | public const {{{name}}} = {{{value}}}; 14 | 15 | {{/enumVars}} 16 | {{/allowableValues}} 17 | /** 18 | * Gets allowable values of the enum 19 | * @return string[] 20 | */ 21 | public static function getAllowableEnumValues(): array 22 | { 23 | return [ 24 | {{#allowableValues}} 25 | {{#enumVars}} 26 | self::{{{name}}}{{^-last}}, 27 | {{/-last}} 28 | {{/enumVars}} 29 | {{/allowableValues}} 30 | 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-python/requirements.txt: -------------------------------------------------------------------------------- 1 | arrow==1.2.3 2 | attrs==23.1.0 3 | boto3==1.26.151 4 | botocore==1.29.151 5 | bravado==11.0.3 6 | bravado-core==5.17.1 7 | cachetools==5.3.1 8 | certifi==2023.5.7 9 | charset-normalizer==3.1.0 10 | chevron==0.14.0 11 | confuse==2.0.1 12 | fqdn==1.5.1 13 | idna==3.4 14 | isoduration==20.11.0 15 | jmespath==1.0.1 16 | jsonpointer==2.4 17 | jsonref==1.1.0 18 | jsonschema==4.18.2 19 | jsonschema-specifications==2023.6.1 20 | monotonic==1.6 21 | msgpack==1.0.5 22 | python-amazon-sp-api==1.0.2 23 | python-dateutil==2.8.2 24 | pytz==2023.3 25 | PyYAML==6.0 26 | referencing==0.29.1 27 | requests==2.31.0 28 | rfc3339-validator==0.1.4 29 | rfc3987==1.3.8 30 | rpds-py==0.8.10 31 | s3transfer==0.6.1 32 | simplejson==3.19.1 33 | six==1.16.0 34 | swagger-spec-validator==3.0.3 35 | typing_extensions==4.7.1 36 | uri-template==1.3.0 37 | urllib3==1.26.16 38 | webcolors==1.13 39 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/test/Amazon.SellingPartnerAPIAATests/Amazon.SellingPartnerAPIAATests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 2.0 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-python/resources/api_test.mustache: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | {{>partial_header}} 4 | 5 | from __future__ import absolute_import 6 | 7 | import unittest 8 | 9 | import {{packageName}} 10 | from {{apiPackage}}.{{classVarName}} import {{classname}} # noqa: E501 11 | from {{packageName}}.rest import ApiException 12 | 13 | 14 | class {{#operations}}Test{{classname}}(unittest.TestCase): 15 | """{{classname}} unit test stubs""" 16 | 17 | def setUp(self): 18 | self.api = {{apiPackage}}.{{classVarName}}.{{classname}}() # noqa: E501 19 | 20 | def tearDown(self): 21 | pass 22 | 23 | {{#operation}} 24 | def test_{{operationId}}(self): 25 | """Test case for {{{operationId}}} 26 | 27 | {{#summary}} 28 | {{{summary}}} # noqa: E501 29 | {{/summary}} 30 | """ 31 | pass 32 | 33 | {{/operation}} 34 | {{/operations}} 35 | 36 | if __name__ == '__main__': 37 | unittest.main() -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-javascript/src/resources/swagger-codegen/templates/package.mustache: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{{projectName}}}", 3 | "type": "module", 4 | "version": "{{{projectVersion}}}", 5 | "description": "{{{projectDescription}}}", 6 | "license": "{{licenseName}}", 7 | "main": "{{sourceFolder}}{{#invokerPackage}}/{{invokerPackage}}{{/invokerPackage}}/index.js", 8 | "scripts": { 9 | "test": "mocha --compilers js:babel-core/register --recursive" 10 | }, 11 | "browser": { 12 | "fs": false 13 | }, 14 | "dependencies": { 15 | "babel": "^6.23.0", 16 | "babel-cli": "^6.26.0", 17 | "babel-plugin-transform-builtin-extend": "^1.1.2", 18 | "superagent": "3.7.0", 19 | "querystring": "0.2.0" 20 | }, 21 | "devDependencies": { 22 | "babel-core": "6.26.0", 23 | "babel-preset-env": "^1.6.1", 24 | "babel-preset-stage-0": "^6.24.1", 25 | "mocha": "~2.3.4", 26 | "sinon": "1.17.3", 27 | "expect.js": "~0.3.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/LWAAuthorizationCredentials.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Amazon.SellingPartnerAPIAA 5 | { 6 | public class LWAAuthorizationCredentials 7 | { 8 | public LWAAuthorizationCredentials() 9 | { 10 | this.Scopes = new List(); 11 | } 12 | 13 | /** 14 | * LWA Client Id 15 | */ 16 | public string ClientId { get; set; } 17 | 18 | /** 19 | * LWA Client Secret 20 | */ 21 | public string ClientSecret { get; set; } 22 | 23 | /** 24 | * LWA Refresh Token 25 | */ 26 | public string RefreshToken { get; set; } 27 | 28 | /** 29 | * LWA Authorization Server Endpoint 30 | */ 31 | public Uri Endpoint { get; set; } 32 | 33 | /** 34 | * LWA Authorization Scopes 35 | */ 36 | public List Scopes { get; set; } 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /.github/workflows/closing_inactive_issues.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. 2 | # 3 | # You can adjust the behavior by modifying this file. 4 | # For more information, see: 5 | # https://github.com/actions/stale 6 | name: Mark stale issues and pull requests 7 | 8 | on: 9 | schedule: 10 | - cron: '5 * * * *' 11 | 12 | jobs: 13 | stale: 14 | 15 | runs-on: ubuntu-latest 16 | permissions: 17 | issues: write 18 | contents: write 19 | 20 | steps: 21 | - uses: actions/stale@v9 22 | with: 23 | days-before-issue-stale: 30 24 | days-before-issue-close: 8 25 | close-issue-message: 'closed for inactivity' 26 | repo-token: ${{ secrets.GITHUB_TOKEN }} 27 | stale-issue-message: 'This issue is scheduled to close soon. If you are still encountering problems, please feel free to open a new issue and make a reference to this one.' 28 | stale-issue-label: 'closing soon' 29 | operations-per-run: 1000 30 | only-labels: 'answered' 31 | 32 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-python/test.py: -------------------------------------------------------------------------------- 1 | if __name__ == "__main__": 2 | print("Starting the Script...") 3 | 4 | from auth.credentials import SPAPIConfig 5 | # User inputs their credentials in the config 6 | config = SPAPIConfig( 7 | client_id="Your Client-id", 8 | client_secret="Your Client-secret", 9 | refresh_token="Your Refresh-token", 10 | region="SANDBOX", # Possible values NA, EU, FE, and SANDBOX 11 | scope = None # Required for grant_type='client_credentials' ; Possible values "sellingpartnerapi::notifications" and "sellingpartnerapi::migration" 12 | ) 13 | 14 | from spapi.spapiclient import SPAPIClient 15 | 16 | # Create the API Client 17 | print("Config and client initialized...") 18 | api_client = SPAPIClient(config) 19 | 20 | marketplace_ids = ["ATVPDKIKX0DER"] 21 | created_after = "2024-01-19T12:34:56.789012" 22 | 23 | orders_api = api_client.get_api_client('OrdersV0Api') 24 | orders_response = orders_api.get_orders(marketplace_ids=marketplace_ids, created_after=created_after) 25 | print("Orders API Response:") 26 | print(orders_response) 27 | 28 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/main/java/com/amazon/spapi/documents/CryptoStreamFactory.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents; 2 | 3 | import com.amazon.spapi.documents.exception.CryptoException; 4 | 5 | import java.io.InputStream; 6 | 7 | /** 8 | * Crypto stream factory interface. 9 | */ 10 | public interface CryptoStreamFactory { 11 | /** 12 | * Create a new {@link InputStream} that decrypts a stream of encrypted data. 13 | * 14 | * @param source The source for encrypted data to decrypt 15 | * @return A new {@link InputStream} from which decrypted data can be read 16 | * @throws CryptoException Crypto exception 17 | */ 18 | InputStream newDecryptStream(InputStream source) throws CryptoException; 19 | 20 | /** 21 | * Create a new {@link InputStream} that encrypts a stream of unencrypted data. 22 | * 23 | * @param source The source for unencrypted data to encrypt 24 | * @return A new {@link InputStream} from which encrypted data can be read 25 | * @throws CryptoException Crypto exception 26 | */ 27 | InputStream newEncryptStream(InputStream source) throws CryptoException; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/main/java/com/amazon/spapi/documents/CompressionAlgorithm.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents; 2 | 3 | /** 4 | * The compression algorithm. 5 | */ 6 | public enum CompressionAlgorithm { 7 | GZIP; 8 | 9 | /** 10 | * Convert from any equivalent enum value. If the specified enum value is null, return null. 11 | * 12 | * @param val The equivalent enum value to convert 13 | * @return This enum's equivalent to the specified enum value 14 | */ 15 | public static CompressionAlgorithm fromEquivalent(T val) { 16 | if (val != null) { 17 | return CompressionAlgorithm.valueOf(val.toString()); 18 | } 19 | 20 | return null; 21 | } 22 | 23 | /** 24 | * Convert from a string. If the specified string is null, return null. 25 | * 26 | * @param val The value to convert. 27 | * @return This enum's equivalent to the specified string 28 | */ 29 | public static CompressionAlgorithm fromEquivalent(String val) { 30 | if (val != null) { 31 | return CompressionAlgorithm.valueOf(val); 32 | } 33 | 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-php/resources/openapi-generator/templates/model.mustache: -------------------------------------------------------------------------------- 1 | partial_header}} 16 | /** 17 | * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). 18 | * https://openapi-generator.tech 19 | * Do not edit the class manually. 20 | */ 21 | 22 | namespace {{modelPackage}}; 23 | {{^isEnum}} 24 | {{^parentSchema}} 25 | 26 | use 27 | ArrayAccess; 28 | {{/parentSchema}} 29 | {{/isEnum}} 30 | use {{invokerPackage}}\ObjectSerializer; 31 | use {{invokerPackage}}\Model\ModelInterface; 32 | 33 | /** 34 | * {{classname}} Class Doc Comment 35 | * 36 | * @category Class 37 | {{#description}} 38 | * @description {{.}} 39 | {{/description}} 40 | * @package {{invokerPackage}} 41 | * @author OpenAPI Generator team 42 | * @link https://openapi-generator.tech 43 | {{^isEnum}} 44 | * @implements \ArrayAccess 45 | {{/isEnum}} 46 | */ 47 | {{#isEnum}}{{>model_enum}}{{/isEnum}}{{^isEnum}}{{>model_generic}}{{/isEnum}} 48 | {{/model}}{{/models}} 49 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/resources/swagger-codegen/templates/api_test.mustache: -------------------------------------------------------------------------------- 1 | {{>licenseInfo}} 2 | 3 | package {{package}}; 4 | 5 | import {{invokerPackage}}.ApiException; 6 | {{#imports}}import {{import}}; 7 | {{/imports}} 8 | import org.junit.Test; 9 | import org.junit.Ignore; 10 | 11 | {{^fullJavaUtil}} 12 | import java.util.ArrayList; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | {{/fullJavaUtil}} 17 | import com.amazon.SellingPartnerAPIAA.LWAException; 18 | 19 | /** 20 | * API tests for {{classname}} 21 | */ 22 | @Ignore 23 | public class {{classname}}Test { 24 | 25 | private final {{classname}} api = new {{classname}}(); 26 | 27 | {{#operations}}{{#operation}} 28 | /** 29 | * {{summary}} 30 | * 31 | * {{notes}} 32 | * 33 | * @throws ApiException if the Api call fails 34 | * @throws LWAException If calls to fetch LWA access token fails 35 | */ 36 | @Test 37 | public void {{operationId}}Test() throws ApiException, LWAException { 38 | {{#allParams}} 39 | {{{dataType}}} {{paramName}} = null; 40 | {{/allParams}} 41 | {{#returnType}}{{{returnType}}} response = {{/returnType}}api.{{operationId}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}); 42 | 43 | // TODO: test validations 44 | } 45 | {{/operation}}{{/operations}} 46 | } -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/LWAAuthorizationSigner.cs: -------------------------------------------------------------------------------- 1 | using RestSharp; 2 | 3 | namespace Amazon.SellingPartnerAPIAA 4 | { 5 | public class LWAAuthorizationSigner 6 | { 7 | public const string AccessTokenHeaderName = "x-amz-access-token"; 8 | 9 | public LWAClient LWAClient { get; set; } 10 | 11 | /// 12 | /// Constructor for LWAAuthorizationSigner 13 | /// 14 | /// LWA Authorization Credentials for token exchange 15 | public LWAAuthorizationSigner(LWAAuthorizationCredentials lwaAuthorizationCredentials) 16 | { 17 | LWAClient = new LWAClient(lwaAuthorizationCredentials); 18 | } 19 | 20 | /// 21 | /// Signs a request with LWA Access Token 22 | /// 23 | /// Request to sign 24 | /// restRequest with LWA signature 25 | public IRestRequest Sign(IRestRequest restRequest) 26 | { 27 | string accessToken = LWAClient.GetAccessToken(); 28 | 29 | restRequest.AddHeader(AccessTokenHeaderName, accessToken); 30 | 31 | return restRequest; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/LWAException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace Amazon.SellingPartnerAPIAA 3 | { 4 | public class LWAException:Exception 5 | { 6 | private String errorMessage; 7 | 8 | private String errorCode; 9 | 10 | public LWAException():base() 11 | { 12 | 13 | } 14 | 15 | public LWAException(String errorCode, String errorMessage) 16 | { 17 | this.errorCode = errorCode; 18 | this.errorMessage = errorMessage; 19 | } 20 | 21 | public LWAException(String errorCode, String errorMessage, String message): base(message) 22 | { 23 | this.errorCode = errorCode; 24 | this.errorMessage = errorMessage; 25 | } 26 | 27 | public LWAException(String errorCode, String errorMessage, String message, Exception innerException) : base(message,innerException) 28 | { 29 | this.errorCode = errorCode; 30 | this.errorMessage = errorMessage; 31 | } 32 | 33 | 34 | public String getErrorCode() 35 | { 36 | return this.errorCode; 37 | } 38 | 39 | public String getErrorMessage() 40 | { 41 | return this.errorMessage; 42 | } 43 | 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/LWAAccessTokenRequestMeta.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Newtonsoft.Json; 3 | 4 | namespace Amazon.SellingPartnerAPIAA 5 | { 6 | public class LWAAccessTokenRequestMeta 7 | { 8 | [JsonProperty(PropertyName = "grant_type")] 9 | public string GrantType { get; set; } 10 | 11 | [JsonProperty(PropertyName = "refresh_token")] 12 | public string RefreshToken { get; set; } 13 | 14 | [JsonProperty(PropertyName = "client_id")] 15 | public string ClientId { get; set; } 16 | 17 | [JsonProperty(PropertyName = "client_secret")] 18 | public string ClientSecret { get; set; } 19 | 20 | [JsonProperty(PropertyName = "scope")] 21 | public string Scope { get; set; } 22 | 23 | public override bool Equals(object obj) 24 | { 25 | LWAAccessTokenRequestMeta other = obj as LWAAccessTokenRequestMeta; 26 | 27 | return other != null && 28 | this.GrantType == other.GrantType && 29 | this.RefreshToken == other.RefreshToken && 30 | this.ClientId == other.ClientId && 31 | this.ClientSecret == other.ClientSecret && 32 | this.Scope == other.Scope; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-python/test_lwarequest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import patch 3 | from auth.LwaRequest import AccessTokenCache 4 | from auth.LwaException import LwaException 5 | 6 | class TestAccessTokenCache(unittest.TestCase): 7 | 8 | @patch('auth.LwaRequest.requests.post') 9 | def test_token_retrieval_success(self, mock_post): 10 | # Mock a successful token response 11 | mock_response = mock_post.return_value 12 | mock_response.raise_for_status.side_effect = None 13 | mock_response.json.return_value = {"access_token": "test_token", "expires_in": 3600} 14 | 15 | token_cache = AccessTokenCache() 16 | token = token_cache.get_lwa_access_token("client_id", "client_secret", "refresh_token") 17 | 18 | self.assertEqual(token, "test_token") 19 | 20 | @patch('auth.LwaRequest.requests.post') 21 | def test_token_retrieval_failure(self, mock_post): 22 | # Mock a failing token response 23 | mock_post.side_effect = Exception("Network error") 24 | 25 | token_cache = AccessTokenCache() 26 | with self.assertRaises(LwaException): 27 | token_cache.get_lwa_access_token("client_id", "client_secret", "refresh_token") 28 | 29 | # Add more test cases as needed 30 | 31 | if __name__ == '__main__': 32 | unittest.main() 33 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAException.java: -------------------------------------------------------------------------------- 1 | package com.amazon.SellingPartnerAPIAA; 2 | 3 | public class LWAException extends Exception { 4 | 5 | private String errorMessage; 6 | 7 | private String errorCode; 8 | 9 | LWAException() { 10 | super(); 11 | } 12 | 13 | LWAException(String errorCode, String errorMessage) { 14 | super(); 15 | this.errorCode = errorCode; 16 | this.errorMessage = errorMessage; 17 | } 18 | 19 | LWAException(String errorCode, String errorMessage, String message) { 20 | super(message); 21 | this.errorCode = errorCode; 22 | this.errorMessage = errorMessage; 23 | } 24 | 25 | LWAException(String errorCode, String errorMessage, Throwable cause) { 26 | super(cause); 27 | this.errorCode = errorCode; 28 | this.errorMessage = errorMessage; 29 | } 30 | 31 | LWAException(String errorCode, String errorMessage, String message, Throwable cause) { 32 | super(message, cause); 33 | this.errorCode = errorCode; 34 | this.errorMessage = errorMessage; 35 | } 36 | 37 | public String getErrorCode() { 38 | return this.errorCode; 39 | } 40 | 41 | public String getErrorMessage() { 42 | return this.errorMessage; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-php/src/authandauth/LWAAuthorizationSigner.php: -------------------------------------------------------------------------------- 1 | lwaClient = new LWAClient($lwaAuthorizationCredentials->getEndpoint()); 20 | $this->lwaAccessTokenRequestMeta = new LWAAccessTokenRequestMeta($lwaAuthorizationCredentials); 21 | $this->lwaClient->setLWAAccessTokenCache($lwaAccessTokenCache); 22 | } 23 | 24 | public function sign(Request $request): Request 25 | { 26 | $accessToken = $this->lwaClient->getAccessToken($this->lwaAccessTokenRequestMeta); 27 | $request = $request->withHeader(static::SIGNED_ACCESS_TOKEN_HEADER_NAME, $accessToken); 28 | return $request; 29 | } 30 | 31 | public function getLwaClient(): LWAClient 32 | { 33 | return $this->lwaClient; 34 | } 35 | 36 | public function setLwaClient(LWAClient $lwaClient): void 37 | { 38 | $this->lwaClient = $lwaClient; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAClientScopesSerializerDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.amazon.SellingPartnerAPIAA; 2 | 3 | import com.google.gson.JsonDeserializationContext; 4 | import com.google.gson.JsonDeserializer; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonObject; 7 | import com.google.gson.JsonParseException; 8 | import com.google.gson.JsonPrimitive; 9 | import com.google.gson.JsonSerializationContext; 10 | import com.google.gson.JsonSerializer; 11 | 12 | import java.lang.reflect.Type; 13 | import java.util.Arrays; 14 | import java.util.HashSet; 15 | import java.util.Set; 16 | 17 | public class LWAClientScopesSerializerDeserializer implements JsonDeserializer, 18 | JsonSerializer { 19 | 20 | @Override 21 | public JsonElement serialize(LWAClientScopes src, Type typeOfSrc, JsonSerializationContext context) { 22 | return new JsonPrimitive(String.join(" ", src.getScopes())); 23 | } 24 | 25 | @Override 26 | public LWAClientScopes deserialize(JsonElement jsonElement, Type type, 27 | JsonDeserializationContext jsonDeserializationContext) 28 | throws JsonParseException { 29 | JsonObject jsonObj = jsonElement.getAsJsonObject(); 30 | Set scopeSet = new HashSet<>(Arrays.asList(jsonObj.get("scope").getAsString().split(" "))); 31 | return new LWAClientScopes(scopeSet); 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/test/java/com/amazon/spapi/documents/DownloadBundleTest.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents; 2 | 3 | import com.amazon.spapi.documents.impl.AESCryptoStreamFactory; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.File; 7 | import java.io.FileNotFoundException; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | class DownloadBundleTest { 12 | private static String KEY = "sxx/wImF6BFndqSAz56O6vfiAh8iD9P297DHfFgujec="; 13 | private static String VECTOR = "7S2tn363v0wfCfo1IX2Q1A=="; 14 | 15 | private DownloadBundle createBadFileBundle() throws Exception { 16 | String contentType = "text/xml; charset=UTF-8"; 17 | CryptoStreamFactory cryptoStreamFactory = new AESCryptoStreamFactory.Builder(KEY, VECTOR).build(); 18 | 19 | File file = File.createTempFile("foo", null, null); 20 | file.delete(); 21 | 22 | return new DownloadBundle(null, contentType, cryptoStreamFactory, file); 23 | } 24 | 25 | @Test 26 | public void testNewInputStreamBadFile() throws Exception { 27 | DownloadBundle downloadBundle = createBadFileBundle(); 28 | assertThrows(FileNotFoundException.class, () -> downloadBundle.newInputStream()); 29 | } 30 | 31 | @Test 32 | public void testNewReaderBadFile() throws Exception { 33 | DownloadBundle downloadBundle = createBadFileBundle(); 34 | assertThrows(FileNotFoundException.class, () -> downloadBundle.newBufferedReader()); 35 | } 36 | } -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/resources/swagger-codegen/templates/build.gradle.mustache: -------------------------------------------------------------------------------- 1 | /* 2 | * okhttp 3 | */ 4 | plugins { 5 | id 'java' 6 | id 'maven-publish' 7 | } 8 | 9 | repositories { 10 | mavenLocal() 11 | maven { 12 | url = uri('https://repo.maven.apache.org/maven2/') 13 | } 14 | } 15 | 16 | dependencies { 17 | implementation 'io.swagger:swagger-annotations:1.6.9' 18 | implementation 'com.squareup.okhttp3:okhttp:4.12.0' 19 | implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' 20 | implementation 'com.google.code.gson:gson:2.10.1' 21 | implementation 'io.gsonfire:gson-fire:1.8.5' 22 | implementation 'javax.annotation:javax.annotation-api:1.3.2' 23 | implementation 'io.swagger.core.v3:swagger-annotations:2.2.16' 24 | {{#joda}} 25 | implementation 'joda-time:joda-time:2.12.1' 26 | {{/joda}} 27 | {{#threetenbp}} 28 | implementation 'org.threeten:threetenbp:1.6.5' 29 | {{/threetenbp}} 30 | testImplementation 'junit:junit:4.13.2' 31 | } 32 | 33 | group = '{{groupId}}' 34 | version = '{{artifactVersion}}' 35 | description = '{{artifactDescription}}' 36 | 37 | java.sourceCompatibility = 11 38 | java.targetCompatibility = 11 39 | 40 | tasks.register('testsJar', Jar) { 41 | archiveClassifier = 'tests' 42 | from(sourceSets.test.output) 43 | } 44 | 45 | java { 46 | withSourcesJar() 47 | withJavadocJar() 48 | } 49 | 50 | publishing { 51 | publications { 52 | maven(MavenPublication) { 53 | from(components.java) 54 | artifact(testsJar) 55 | } 56 | } 57 | } 58 | 59 | tasks.withType(JavaCompile) { 60 | options.encoding = 'UTF-8' 61 | } -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/test/java/com/amazon/spapi/documents/CompressionAlgorithmTest.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | class CompressionAlgorithmTest { 9 | public enum MyEnum { 10 | GZIP, NOT_GZIP 11 | } 12 | 13 | @Test 14 | public void testFromEquivalent() { 15 | assertEquals(CompressionAlgorithm.GZIP, CompressionAlgorithm.fromEquivalent(MyEnum.GZIP)); 16 | } 17 | 18 | @Test 19 | public void testFromEquivalentNull() { 20 | MyEnum myEnum = null; 21 | assertNull(CompressionAlgorithm.fromEquivalent(myEnum)); 22 | } 23 | 24 | @Test 25 | public void testNotEquivalent() { 26 | Assertions.assertThrows(IllegalArgumentException.class, 27 | () -> CompressionAlgorithm.fromEquivalent(MyEnum.NOT_GZIP)); 28 | } 29 | 30 | @Test 31 | public void testFromString() { 32 | assertEquals(CompressionAlgorithm.GZIP, CompressionAlgorithm.fromEquivalent("GZIP")); 33 | } 34 | 35 | @Test 36 | public void testFromStringNull() { 37 | String val = null; 38 | assertNull(CompressionAlgorithm.fromEquivalent(val)); 39 | } 40 | 41 | @Test 42 | public void testFromStringUnsupportedValue() { 43 | Assertions.assertThrows(IllegalArgumentException.class, 44 | () -> CompressionAlgorithm.fromEquivalent("NOT_GZIP")); 45 | } 46 | } -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-php/resources/openapi-generator/templates/composer.mustache: -------------------------------------------------------------------------------- 1 | { 2 | {{#composerPackageName}} 3 | "name": "{{.}}", 4 | {{/composerPackageName}} 5 | {{#artifactVersion}} 6 | "version": "{{.}}", 7 | {{/artifactVersion}} 8 | "description": "{{{appDescription}}}", 9 | "keywords": [ 10 | "openapitools", 11 | "openapi-generator", 12 | "openapi", 13 | "php", 14 | "sdk", 15 | "rest", 16 | "api" 17 | ], 18 | "homepage": "https://openapi-generator.tech", 19 | "license": "unlicense", 20 | "authors": [ 21 | { 22 | "name": "OpenAPI-Generator contributors", 23 | "homepage": "https://openapi-generator.tech" 24 | } 25 | ], 26 | "require": { 27 | "php": "^7.4 || ^8.0", 28 | "ext-curl": "*", 29 | "ext-json": "*", 30 | "ext-mbstring": "*", 31 | "guzzlehttp/guzzle": "^7.3", 32 | "guzzlehttp/psr7": "^1.7 || ^2.0", 33 | "symfony/rate-limiter": "^6.1", 34 | "aws/aws-sdk-php": "^3.228", 35 | "spapi/auth-and-auth": "1.0", 36 | "vlucas/phpdotenv": "^5.6", 37 | "ext-openssl": "*" 38 | }, 39 | "require-dev": { 40 | "phpunit/phpunit": "^8.0 || ^9.0", 41 | "friendsofphp/php-cs-fixer": "^3.5" 42 | }, 43 | "autoload": { 44 | "psr-4": { "{{escapedInvokerPackage}}\\" : "{{srcBasePath}}/" } 45 | }, 46 | "autoload-dev": { 47 | "psr-4": { "{{escapedInvokerPackage}}\\Test\\" : "{{testBasePath}}/" } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAuthorizationCredentials.java: -------------------------------------------------------------------------------- 1 | package com.amazon.SellingPartnerAPIAA; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import lombok.NonNull; 6 | 7 | import java.util.Arrays; 8 | import java.util.HashSet; 9 | 10 | /** 11 | * LWAAuthorizationCredentials 12 | */ 13 | @Data 14 | @Builder 15 | public class LWAAuthorizationCredentials { 16 | /** 17 | * LWA Client Id 18 | */ 19 | @NonNull 20 | private String clientId; 21 | 22 | /** 23 | * LWA Client Secret 24 | */ 25 | @NonNull 26 | private String clientSecret; 27 | 28 | /** 29 | * LWA Refresh Token 30 | */ 31 | private String refreshToken; 32 | 33 | /** 34 | * LWA Authorization Server Endpoint 35 | */ 36 | @NonNull 37 | private String endpoint; 38 | 39 | /** 40 | * LWA Client Scopes 41 | */ 42 | private LWAClientScopes scopes; 43 | 44 | public static class LWAAuthorizationCredentialsBuilder { 45 | 46 | { 47 | scopes = new LWAClientScopes(new HashSet<>()); 48 | } 49 | 50 | public LWAAuthorizationCredentialsBuilder withScope(String scope) { 51 | return withScopes(scope); 52 | } 53 | 54 | public LWAAuthorizationCredentialsBuilder withScopes(String... scopes) { 55 | if (scopes != null) { 56 | Arrays.stream(scopes) 57 | .forEach(this.scopes::addScope); 58 | } 59 | return this; 60 | } 61 | 62 | 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/SellingPartnerAPIAuthAndAuthCSharp.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.SellingPartnerAPIAA", "src\Amazon.SellingPartnerAPIAA\Amazon.SellingPartnerAPIAA.csproj", "{64339397-3AAB-49D3-8B50-7A467B16D545}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.SellingPartnerAPIAATests", "test\Amazon.SellingPartnerAPIAATests\Amazon.SellingPartnerAPIAATests.csproj", "{12B130EB-1087-4F88-BDFA-3088868C0A46}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {64339397-3AAB-49D3-8B50-7A467B16D545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {64339397-3AAB-49D3-8B50-7A467B16D545}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {64339397-3AAB-49D3-8B50-7A467B16D545}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {64339397-3AAB-49D3-8B50-7A467B16D545}.Release|Any CPU.Build.0 = Release|Any CPU 18 | {12B130EB-1087-4F88-BDFA-3088868C0A46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {12B130EB-1087-4F88-BDFA-3088868C0A46}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {12B130EB-1087-4F88-BDFA-3088868C0A46}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {12B130EB-1087-4F88-BDFA-3088868C0A46}.Release|Any CPU.Build.0 = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(MonoDevelopProperties) = preSolution 24 | version = 2.0 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/main/java/com/amazon/spapi/documents/HttpTransferClient.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents; 2 | 3 | import com.amazon.spapi.documents.exception.HttpResponseException; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | 8 | /** 9 | * HTTP transfer client. Implementations of this interface must be thread-safe and reusable for multiple requests. 10 | */ 11 | public interface HttpTransferClient { 12 | /** 13 | * Perform an HTTP GET on the specified url, storing the response body to destination. 14 | * 15 | * @param url The url to perform an HTTP GET on 16 | * @param destination The file to write the HTTP GET response body to 17 | * @return The Content-Type header value extracted from the response to the HTTP GET 18 | * @throws HttpResponseException On failure HTTP response 19 | * @throws IOException IO exception 20 | */ 21 | String download(String url, File destination) throws HttpResponseException, IOException; 22 | 23 | /** 24 | * Perform an HTTP PUT on the specified url, uploading the contents of source to the body 25 | * of the request. 26 | * 27 | * @param url The url to perform an HTTP PUT on 28 | * @param contentType The Content-Type header to be used for the HTTP PUT request 29 | * @param source The file to read the HTTP PUT body from 30 | * @throws HttpResponseException On failure HTTP response 31 | * @throws IOException IO exception 32 | */ 33 | void upload(String url, String contentType, File source) throws HttpResponseException, IOException; 34 | } 35 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-php/src/authandauth/LWAAccessTokenCache.php: -------------------------------------------------------------------------------- 1 | $value, 21 | static::EXPIRED_TIME => $accessTokenExpiredTimeMillis 22 | ]; 23 | $this->tokenStorage[$key] = $accessTokenCacheItem; 24 | } 25 | 26 | public function get($key): ?string 27 | { 28 | if (array_key_exists($key, $this->tokenStorage)) { 29 | $accessTokenValue = $this->tokenStorage[$key]; 30 | $accessToken = $accessTokenValue[static::ACCESS_TOKEN]; 31 | $accessTokenExpiredTimeMillis = $accessTokenValue[static::EXPIRED_TIME]; 32 | $currTimeMillis = floor(microtime(true) * 1000); 33 | $accessTokenExpiredWindow = $accessTokenExpiredTimeMillis - static::EXPIRATION_BUFFER; 34 | if ($currTimeMillis < $accessTokenExpiredWindow) { 35 | return $accessToken; 36 | } 37 | } 38 | return null; 39 | } 40 | 41 | public function remove($key) 42 | { 43 | unset($this->tokenStorage[$key]); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-php/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spapi/auth-and-auth", 3 | "description": "Amazon Selling Partner APIs official client library.", 4 | "type": "library", 5 | "keywords": [ 6 | "sp-api", 7 | "amazon", 8 | "sdk", 9 | "openapi-generator", 10 | "php", 11 | "rest" 12 | ], 13 | "homepage": "https://developer-docs.amazon.com/sp-api", 14 | "license": "Apache-2.0", 15 | "authors": [ 16 | { 17 | "name": "Amazon API Services", 18 | "homepage": "https://developer-docs.amazon.com/sp-api" 19 | } 20 | ], 21 | "require": { 22 | "php": "^8.3", 23 | "ext-json": "*", 24 | "guzzlehttp/guzzle": "^7.9", 25 | "guzzlehttp/psr7": "^2.0", 26 | "aws/aws-sdk-php": "^3.228", 27 | "symfony/http-kernel": "^7.2", 28 | "symfony/rate-limiter": "^7.2", 29 | "vlucas/phpdotenv": "^5.6", 30 | "ext-openssl": "*" 31 | }, 32 | "conflict": { 33 | "guzzlehttp/guzzle": ">=8.0" 34 | }, 35 | "require-dev": { 36 | "phpunit/phpunit": "^9.6", 37 | "squizlabs/php_codesniffer": "^3.7" 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "SpApi\\AuthAndAuth\\": "sdk/src/authandauth", 42 | "OpenAPI\\Client\\": "sdk/lib/", 43 | "OpenAPI\\Client\\Test\\" : "sdk/test/" 44 | } 45 | }, 46 | "autoload-dev": { 47 | "psr-4": { 48 | "SpApi\\Test\\AuthAndAuth\\" : "sdk/src/tests/authandauth/" 49 | } 50 | }, 51 | "scripts": { 52 | "fix": "phpcs --standard=PSR12 sdk/src/authandauth sdk/src/tests/", 53 | "test": "phpunit sdk/tests/authandauth" 54 | } 55 | } -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/resources/swagger-codegen/templates/StringUtil.mustache: -------------------------------------------------------------------------------- 1 | {{>licenseInfo}} 2 | 3 | package {{invokerPackage}}; 4 | 5 | {{>generatedAnnotation}} 6 | public class StringUtil { 7 | /** 8 | * Check if the given array contains the given value (with case-insensitive comparison). 9 | * 10 | * @param array The array 11 | * @param value The value to search 12 | * @return true if the array contains the value 13 | */ 14 | public static boolean containsIgnoreCase(String[] array, String value) { 15 | for (String str : array) { 16 | if (value == null && str == null) return true; 17 | if (value != null && value.equalsIgnoreCase(str)) return true; 18 | } 19 | return false; 20 | } 21 | 22 | /** 23 | * Join an array of strings with the given separator. 24 | *

25 | * Note: This might be replaced by utility method from commons-lang or guava someday 26 | * if one of those libraries is added as dependency. 27 | *

28 | * 29 | * @param array The array of strings 30 | * @param separator The separator 31 | * @return the resulting string 32 | */ 33 | public static String join(String[] array, String separator) { 34 | int len = array.length; 35 | if (len == 0) return ""; 36 | 37 | StringBuilder out = new StringBuilder(); 38 | out.append(array[0]); 39 | for (int i = 1; i < len; i++) { 40 | out.append(separator).append(array[i]); 41 | } 42 | return out.toString(); 43 | } 44 | 45 | /** 46 | * Check if the given value is null or an empty string 47 | * 48 | * @param value The value to check 49 | * @return true if the value is null or empty 50 | */ 51 | public static boolean isEmpty(String value) { 52 | return value == null || value.isEmpty(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/test/Amazon.SellingPartnerAPIAATests/UtilsTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using Xunit; 5 | using Amazon.SellingPartnerAPIAA; 6 | 7 | namespace Amazon.SellingPartnerAPIAATests 8 | { 9 | public class UtilsTest 10 | { 11 | private const string TestString = "test"; 12 | 13 | [Fact] 14 | public void TestUrlEncode_WithoutEncoding() 15 | { 16 | string result = Utils.UrlEncode("Test-_.~"); 17 | Assert.Equal("Test-_.~", result); 18 | } 19 | 20 | [Fact] 21 | public void TestUrlEncode_WithEncoding() 22 | { 23 | string result = Utils.UrlEncode("Test$%*^"); 24 | Assert.Equal("Test%24%25%2A%5E", result); 25 | } 26 | 27 | [Fact] 28 | public void TestUrlEncode_Empty() 29 | { 30 | Assert.Empty(Utils.UrlEncode(string.Empty)); 31 | } 32 | 33 | [Fact] 34 | public void TestHash() 35 | { 36 | Assert.NotEmpty(Utils.Hash(TestString)); 37 | } 38 | 39 | [Fact] 40 | public void TestToHex() 41 | { 42 | string result = Utils.ToHex(Encoding.UTF8.GetBytes(TestString)); 43 | Assert.Equal("74657374", result); 44 | } 45 | 46 | [Fact] 47 | public void TestGetKeyedHash() 48 | { 49 | byte[] expectedHash = new byte[] { 106, 120, 238, 51, 86, 30, 87, 173, 232, 197, 95, 132,155, 50 | 183, 80, 81, 25, 213, 212, 241, 218, 201, 168, 17, 253, 143, 54, 226, 42, 118, 61, 54 }; 51 | byte[] keyedHash = Utils.GetKeyedHash(Encoding.UTF8.GetBytes("testKey"), TestString); 52 | Assert.True(expectedHash.SequenceEqual(keyedHash)); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/LWAAccessTokenRequestMetaBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace Amazon.SellingPartnerAPIAA 4 | { 5 | public class LWAAccessTokenRequestMetaBuilder 6 | { 7 | public const string SellerAPIGrantType = "refresh_token"; 8 | public const string SellerlessAPIGrantType = "client_credentials"; 9 | 10 | private const string Delimiter = " "; 11 | 12 | /// 13 | /// Builds an instance of LWAAccessTokenRequestMeta modeling appropriate LWA token 14 | /// request params based on configured LWAAuthorizationCredentials 15 | /// 16 | /// LWA Authorization Credentials 17 | /// 18 | public virtual LWAAccessTokenRequestMeta Build(LWAAuthorizationCredentials lwaAuthorizationCredentials) 19 | { 20 | LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta = new LWAAccessTokenRequestMeta() 21 | { 22 | ClientId = lwaAuthorizationCredentials.ClientId, 23 | ClientSecret = lwaAuthorizationCredentials.ClientSecret, 24 | RefreshToken = lwaAuthorizationCredentials.RefreshToken 25 | }; 26 | 27 | if (lwaAuthorizationCredentials.Scopes == null || lwaAuthorizationCredentials.Scopes.Count == 0) 28 | { 29 | lwaAccessTokenRequestMeta.GrantType = SellerAPIGrantType; 30 | } 31 | else 32 | { 33 | lwaAccessTokenRequestMeta.Scope = string.Join(Delimiter, lwaAuthorizationCredentials.Scopes); 34 | lwaAccessTokenRequestMeta.GrantType = SellerlessAPIGrantType; 35 | } 36 | 37 | return lwaAccessTokenRequestMeta; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/main/java/com/amazon/spapi/documents/exception/HttpResponseException.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents.exception; 2 | 3 | /** 4 | * The details of an HTTP response that indicates failure. 5 | */ 6 | public class HttpResponseException extends Exception { 7 | private final String body; 8 | private final int code; 9 | 10 | /** 11 | * {@inheritDoc} 12 | * 13 | * @param message The {@link Exception} message 14 | * @param body The body 15 | * @param code The HTTP status code 16 | */ 17 | public HttpResponseException(String message, String body, int code) { 18 | super(message); 19 | this.body = body; 20 | this.code = code; 21 | } 22 | 23 | /** 24 | * {@inheritDoc} 25 | * 26 | * @param message The {@link Exception} message 27 | * @param cause The {@link Exception} cause 28 | * @param body The body 29 | * @param code The HTTP status code 30 | */ 31 | public HttpResponseException(String message, Throwable cause, String body, int code) { 32 | super(message, cause); 33 | this.body = body; 34 | this.code = code; 35 | } 36 | 37 | /** 38 | * The body. To ensure that a remote server cannot overwhelm heap memory, the body may have been truncated. 39 | * 40 | * @return The body 41 | */ 42 | public String getBody() { 43 | return body; 44 | } 45 | 46 | /** 47 | * The HTTP status code 48 | * 49 | * @return The HTTP status code 50 | */ 51 | public int getCode() { 52 | return code; 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return super.toString() + " {code=" 58 | + getCode() 59 | + ", body=" 60 | + getBody() 61 | + '}'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAccessTokenCacheImpl.java: -------------------------------------------------------------------------------- 1 | package com.amazon.SellingPartnerAPIAA; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | 5 | public class LWAAccessTokenCacheImpl implements LWAAccessTokenCache { 6 | //in milliseconds; to avoid returning a token that would expire before or while a request is made 7 | private long expiryAdjustment = 60 * 1000; 8 | private static final long SECOND_TO_MILLIS = 1000; 9 | private ConcurrentHashMap accessTokenHashMap = 10 | new ConcurrentHashMap(); 11 | @Override 12 | public void put(Object oLWAAccessTokenRequestMeta, String accessToken, long tokenTTLInSeconds) { 13 | LWAAccessTokenCacheItem accessTokenCacheItem = new LWAAccessTokenCacheItem(); 14 | long insertTime = System.currentTimeMillis(); 15 | long accessTokenExpiresValueMillis = (tokenTTLInSeconds * SECOND_TO_MILLIS) + insertTime; 16 | accessTokenCacheItem.setAccessToken(accessToken); 17 | accessTokenCacheItem.setAccessTokenExpiredTime(accessTokenExpiresValueMillis); 18 | accessTokenHashMap.put(oLWAAccessTokenRequestMeta, accessTokenCacheItem); 19 | } 20 | 21 | @Override 22 | public String get(Object oLWAAccessTokenRequestMeta) { 23 | Object accessTokenValue = accessTokenHashMap.get(oLWAAccessTokenRequestMeta); 24 | if (accessTokenValue != null) { 25 | LWAAccessTokenCacheItem accessTokenData = 26 | (LWAAccessTokenCacheItem) accessTokenValue; 27 | long currentTime = System.currentTimeMillis(); 28 | long accessTokenExpiredTime = accessTokenData.getAccessTokenExpiredTime() - expiryAdjustment; 29 | if (currentTime < accessTokenExpiredTime) { 30 | return accessTokenData.getAccessToken(); 31 | } 32 | } 33 | return null; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Selling Partner API Models 2 | This repository contains Swagger models for developers to use to create software that calls Selling Partner APIs. Developers can use [swagger codegen](https://github.com/swagger-api/swagger-codegen) to generate client libraries from these models. Please refer to [selling-partner-api-docs](https://github.com/amzn/selling-partner-api-docs) for additional documentation and read the [Selling Partner API Developer Guide](https://github.com/amzn/selling-partner-api-docs/blob/main/guides/en-US/developer-guide/SellingPartnerApiDeveloperGuide.md) for instructions to get started. 3 | 4 | The [models directory](https://github.com/amzn/selling-partner-api-models/tree/main/models) contains all of the currently available Selling Partner API models. 5 | 6 | The [clients directory](https://github.com/amzn/selling-partner-api-models/tree/main/clients) contains a [Java library](https://github.com/amzn/selling-partner-api-models/tree/main/clients/sellingpartner-api-aa-java) and a [C# library](https://github.com/amzn/selling-partner-api-models/tree/main/clients/sellingpartner-api-aa-csharp) with mustache templates for use with [swagger-codegen](https://swagger.io/tools/swagger-codegen/) to generate client libraries with authentication and authorization functionality included. The templates are located in *resources/swagger-codegen*. 7 | 8 | The [schemas directory](https://github.com/amzn/selling-partner-api-models/tree/main/schemas) contains all of the currently available Selling Partner Api schemas. 9 | 10 | **Note:** For any issues related to SP API like bugs or troubleshooting, please reach out to [Developer Support](https://developer.amazonservices.com/support) so that the right team can help / channel your inputs (in case of bugs / feature requests) to the development team. 11 | 12 | ## Security 13 | 14 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 15 | 16 | ## License 17 | 18 | This project is licensed under the Apache-2.0 License. 19 | 20 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/test/Amazon.SellingPartnerAPIAATests/LWAAuthorizationSignerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | using Moq; 4 | using RestSharp; 5 | using Amazon.SellingPartnerAPIAA; 6 | 7 | namespace Amazon.SellingPartnerAPIAATests 8 | { 9 | public class LWAAuthorizationSignerTest 10 | { 11 | private static readonly LWAAuthorizationCredentials LWAAuthorizationCredentials = new LWAAuthorizationCredentials() 12 | { 13 | ClientId = "cid", 14 | ClientSecret = "csecret", 15 | Endpoint = new Uri("https://www.amazon.com") 16 | }; 17 | 18 | private LWAAuthorizationSigner lwaAuthorizationSignerUnderTest; 19 | 20 | public LWAAuthorizationSignerTest() 21 | { 22 | lwaAuthorizationSignerUnderTest = new LWAAuthorizationSigner(LWAAuthorizationCredentials); 23 | } 24 | 25 | [Fact] 26 | public void ConstructorInitializesLWAClientWithCredentials() 27 | { 28 | Assert.Equal(LWAAuthorizationCredentials, lwaAuthorizationSignerUnderTest.LWAClient.LWAAuthorizationCredentials); 29 | } 30 | 31 | [Fact] 32 | public void RequestIsSignedFromLWAClientProvidedToken() 33 | { 34 | string expectedAccessToken = "foo"; 35 | 36 | var mockLWAClient = new Mock(LWAAuthorizationCredentials); 37 | mockLWAClient.Setup(lwaClient => lwaClient.GetAccessToken()).Returns(expectedAccessToken); 38 | lwaAuthorizationSignerUnderTest.LWAClient = mockLWAClient.Object; 39 | 40 | IRestRequest restRequest = new RestRequest(); 41 | restRequest = lwaAuthorizationSignerUnderTest.Sign(restRequest); 42 | 43 | Parameter actualAccessTokenHeader = restRequest.Parameters.Find(parameter => 44 | ParameterType.HttpHeader.Equals(parameter.Type) && parameter.Name == LWAAuthorizationSigner.AccessTokenHeaderName); 45 | 46 | Assert.Equal(expectedAccessToken, actualAccessTokenHeader.Value); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'idea' 3 | id 'eclipse' 4 | id 'java' 5 | } 6 | 7 | group = 'com.amazon.sellingpartnerapi' 8 | version = '1.0.0' 9 | 10 | 11 | repositories { 12 | jcenter() 13 | mavenCentral() 14 | } 15 | 16 | sourceCompatibility = JavaVersion.VERSION_1_8 17 | targetCompatibility = JavaVersion.VERSION_1_8 18 | 19 | 20 | /*sourceSets { 21 | main { 22 | java { 23 | srcDirs = ['src/main/java'] 24 | } 25 | resources { 26 | srcDirs = ['src/main/resources'] 27 | } 28 | } 29 | 30 | test { 31 | java { 32 | srcDirs = ['src/test/java'] 33 | } 34 | resources { 35 | srcDirs = ['src/test/resources'] 36 | } 37 | } 38 | }*/ 39 | 40 | task execute(type: JavaExec) { 41 | main = System.getProperty('mainClass') 42 | classpath = sourceSets.main.runtimeClasspath 43 | } 44 | 45 | test { 46 | useJUnitPlatform() 47 | testLogging { 48 | events "passed", "skipped", "failed" 49 | } 50 | } 51 | 52 | dependencies { 53 | testImplementation(platform('org.junit:junit-bom:5.7.0')) 54 | testImplementation('org.junit.jupiter:junit-jupiter') 55 | testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.7.0") 56 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.0") 57 | testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.7.0") 58 | 59 | implementation 'com.squareup.okhttp:okhttp:2.7.5' 60 | implementation 'com.google.guava:guava:28.2-jre' 61 | 62 | // https://mvnrepository.com/artifact/org.threeten/threetenbp 63 | implementation group: 'org.threeten', name: 'threetenbp', version: '1.3.5' 64 | 65 | // https://mvnrepository.com/artifact/junit/junit 66 | implementation 'org.junit.jupiter:junit-jupiter-migrationsupport:5.5.1' 67 | 68 | implementation 'org.mockito:mockito-core:3.0.0' 69 | implementation 'org.mockito:mockito-inline:3.0.0' 70 | 71 | implementation 'org.apache.directory.studio:org.apache.commons.io:2.4' 72 | 73 | } 74 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/test/java/com/amazon/spapi/documents/exception/HttpResponseExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents.exception; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class HttpResponseExceptionTest { 8 | @Test 9 | public void testConstructor() { 10 | String message = "This is the message"; 11 | String body = "This is the body"; 12 | int code = 403; 13 | String expectToString = "com.amazon.spapi.documents.exception.HttpResponseException: " + 14 | "This is the message {code=403, body=This is the body}"; 15 | 16 | HttpResponseException exception = new HttpResponseException(message, body, code); 17 | 18 | assertEquals(message, exception.getMessage()); 19 | assertEquals(body, exception.getBody()); 20 | assertEquals(code, exception.getCode()); 21 | assertEquals(expectToString, exception.toString()); 22 | } 23 | 24 | @Test 25 | public void testConstructorNullBody() { 26 | String message = "This is the message"; 27 | String body = null; 28 | int code = 403; 29 | String expectToString = "com.amazon.spapi.documents.exception.HttpResponseException: " + 30 | "This is the message {code=403, body=null}"; 31 | 32 | HttpResponseException exception = new HttpResponseException(message, body, code); 33 | 34 | assertEquals(message, exception.getMessage()); 35 | assertEquals(body, exception.getBody()); 36 | assertEquals(code, exception.getCode()); 37 | assertEquals(expectToString, exception.toString()); 38 | } 39 | 40 | @Test 41 | public void testConstructorCause() { 42 | String message = "This is the message"; 43 | Throwable cause = new RuntimeException(); 44 | String body = "This is the body"; 45 | int code = 403; 46 | 47 | HttpResponseException exception = new HttpResponseException(message, cause, body, code); 48 | 49 | assertEquals(message, exception.getMessage()); 50 | assertSame(cause, exception.getCause()); 51 | assertEquals(body, exception.getBody()); 52 | assertEquals(code, exception.getCode()); 53 | } 54 | } -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/test/java/com/amazon/spapi/documents/DownloadSpecificationTest.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.mockito.Mockito; 5 | 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | class DownloadSpecificationTest { 9 | @Test 10 | public void testBuilderConstructorMissingCryptoStreamFactory() throws Exception { 11 | CryptoStreamFactory cryptoStreamFactory = null; 12 | String url = "https://www.amazon.com"; 13 | 14 | assertThrows(IllegalArgumentException.class, () -> 15 | new DownloadSpecification.Builder(cryptoStreamFactory, url)); 16 | } 17 | 18 | @Test 19 | public void testBuilderConstructorMissingUrl() throws Exception { 20 | CryptoStreamFactory cryptoStreamFactory = Mockito.mock(CryptoStreamFactory.class); 21 | String url = null; 22 | 23 | assertThrows(IllegalArgumentException.class, () -> 24 | new DownloadSpecification.Builder(cryptoStreamFactory, url)); 25 | } 26 | 27 | @Test 28 | public void testSuccess() throws Exception { 29 | CryptoStreamFactory cryptoStreamFactory = Mockito.mock(CryptoStreamFactory.class); 30 | String url = "http://abc.com/123"; 31 | 32 | DownloadSpecification spec = new DownloadSpecification.Builder(cryptoStreamFactory, url).build(); 33 | 34 | assertNull(spec.getCompressionAlgorithm()); 35 | assertSame(cryptoStreamFactory, spec.getCryptoStreamFactory()); 36 | assertEquals(url, spec.getUrl()); 37 | } 38 | 39 | @Test 40 | public void testCompressionAlgorithm() throws Exception { 41 | CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.GZIP; 42 | CryptoStreamFactory cryptoStreamFactory = Mockito.mock(CryptoStreamFactory.class); 43 | String url = "http://abc.com/123"; 44 | 45 | DownloadSpecification spec = new DownloadSpecification.Builder(cryptoStreamFactory, url) 46 | .withCompressionAlgorithm(compressionAlgorithm) 47 | .build(); 48 | 49 | assertEquals(compressionAlgorithm, spec.getCompressionAlgorithm()); 50 | assertSame(cryptoStreamFactory, spec.getCryptoStreamFactory()); 51 | assertEquals(url, spec.getUrl()); 52 | } 53 | } -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-python/spapi/spapiclient.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import logging 4 | import backoff 5 | from requests.exceptions import HTTPError 6 | 7 | #Update Path 8 | sys.path.append('path_to_folder/SellingPartnerAPIAuthAndAuthPython/client/orders') 9 | from swagger_client.configuration import Configuration 10 | from swagger_client.api_client import ApiClient 11 | 12 | #Update Path 13 | sys.path.append('path_to_folder/SellingPartnerAPIAuthAndAuthPython') 14 | from auth.LwaRequest import AccessTokenCache 15 | 16 | logging.basicConfig(level=logging.INFO) 17 | 18 | def is_rate_limit_error(e): 19 | """Check if the exception is a rate limit error (HTTP 429).""" 20 | return isinstance(e, HTTPError) and e.response.status_code == 429 21 | 22 | class SPAPIClient: 23 | region_to_endpoint = { 24 | "NA": "https://sellingpartnerapi-na.amazon.com", 25 | "EU": "https://sellingpartnerapi-eu.amazon.com", 26 | "FE": "https://sellingpartnerapi-fe.amazon.com", 27 | "SANDBOX": "https://sandbox.sellingpartnerapi-na.amazon.com" 28 | } 29 | 30 | def __init__(self, config): 31 | self.config = config 32 | self.api_base_url = self.region_to_endpoint.get(config.region) 33 | self.access_token_cache = AccessTokenCache() 34 | self.api_client = None 35 | self._initialize_client() 36 | 37 | def _initialize_client(self): 38 | logging.debug("Initializing API Client...") 39 | 40 | access_token = self.access_token_cache.get_lwa_access_token( 41 | client_id=self.config.client_id, 42 | client_secret=self.config.client_secret, 43 | refresh_token=self.config.refresh_token 44 | ) 45 | configuration = Configuration() 46 | configuration.host = self.api_base_url 47 | configuration.access_token = access_token 48 | 49 | self.api_client = ApiClient(configuration=configuration) 50 | 51 | @backoff.on_exception(backoff.expo, 52 | HTTPError, 53 | giveup=is_rate_limit_error, 54 | max_tries=5, 55 | on_giveup=lambda e: logging.error(f"Too Many Retries: {e}")) 56 | 57 | def get_api_client(self, api_name): 58 | try: 59 | module = __import__('swagger_client.api', fromlist=[api_name]) 60 | api_class = getattr(module, api_name) 61 | return api_class(self.api_client) 62 | except AttributeError: 63 | raise Exception(f"API client for {api_name} not found.") -------------------------------------------------------------------------------- /schemas/feeds/listings-feed-schema-v2.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": { 3 | "sellerId": "AXXXXXXXXXXXX", 4 | "version": "2.0", 5 | "issueLocale": "en_US", 6 | "report": { 7 | "includedData":[ 8 | "summaries", 9 | "attributes", 10 | "issues", 11 | "offers", 12 | "fulfillmentAvailability", 13 | "procurement", 14 | "relationships", 15 | "productTypes" 16 | ], 17 | "apiVersion" : "2021-08-01" 18 | } 19 | }, 20 | "messages": [ 21 | { 22 | "messageId": 1, 23 | "sku": "My-SKU-A", 24 | "operationType": "DELETE" 25 | }, 26 | { 27 | "messageId": 2, 28 | "sku": "My-SKU-B", 29 | "operationType": "PARTIAL_UPDATE", 30 | "productType": "LUGGAGE", 31 | "attributes": { 32 | "fulfillment_availability": [ 33 | { 34 | "fulfillment_channel_code": "DEFAULT", 35 | "quantity": 10 36 | } 37 | ] 38 | } 39 | }, 40 | { 41 | "messageId": 3, 42 | "sku": "My-SKU-C", 43 | "operationType": "UPDATE", 44 | "productType": "LUGGAGE", 45 | "requirements": "LISTING", 46 | "attributes": { 47 | "item_name": [ 48 | { 49 | "value": "My Title", 50 | "language_tag": "en_US", 51 | "marketplace_id": "ATVPDKIKX0DER" 52 | } 53 | ], 54 | "fulfillment_availability": [ 55 | { 56 | "fulfillment_channel_code": "AMAZON_NA", 57 | "quantity": 10 58 | } 59 | ] 60 | } 61 | }, 62 | { 63 | "messageId": 4, 64 | "sku": "My-SKU-D", 65 | "operationType": "PATCH", 66 | "productType": "LUGGAGE", 67 | "patches": [ 68 | { 69 | "op": "replace", 70 | "path": "/attributes/fulfillment_availability", 71 | "value": [ 72 | { 73 | "fulfillment_channel_code": "DEFAULT", 74 | "quantity": 10 75 | } 76 | ] 77 | } 78 | ] 79 | }, 80 | { 81 | "messageId": 5, 82 | "sku": "My-SKU-E", 83 | "operationType": "PATCH", 84 | "productType": "LUGGAGE", 85 | "patches": [ 86 | { 87 | "op": "merge", 88 | "path": "/attributes/fulfillment_availability", 89 | "value": [ 90 | { 91 | "fulfillment_channel_code": "DEFAULT", 92 | "quantity": 10 93 | } 94 | ] 95 | } 96 | ] 97 | } 98 | ] 99 | } -------------------------------------------------------------------------------- /.github/workflows/rdme-openapi.yml: -------------------------------------------------------------------------------- 1 | # This GitHub Actions workflow was auto-generated by the `rdme` cli on 2024-11-06T18:56:19.723Z 2 | # You can view our full documentation here: https://docs.readme.com/docs/rdme 3 | name: ReadMe GitHub Action 🦉 4 | 5 | on: 6 | push: 7 | branches: 8 | # This workflow will run every time you push code to the following branch: `main` 9 | # Check out GitHub's docs for more info on configuring this: 10 | # https://docs.github.com/actions/using-workflows/events-that-trigger-workflows 11 | - main 12 | 13 | jobs: 14 | rdme-openapi: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Check out repo 📚 18 | uses: actions/checkout@v3 19 | 20 | - name: Run `openapi` command 🚀 21 | uses: readmeio/rdme@v8 22 | with: 23 | rdme: openapi catalog-items-api-model/catalogItems_2022-04-01.json --key=${{ secrets.README_API_KEY }} --id=672bbbc7847aca00335d8976 24 | - name: Run `openapi` command 🚀 25 | uses: readmeio/rdme@v8 26 | with: 27 | rdme: openapi fulfillment-inbound-api-model/fulfillmentInbound_2024-03-20.json --key=${{ secrets.README_API_KEY }} --id=672bbde35d2c6300178a79aa 28 | - name: Run `openapi` command 🚀 29 | uses: readmeio/rdme@v8 30 | with: 31 | rdme: openapi feeds-api-model/feeds_2021-06-30.json --key=${{ secrets.README_API_KEY }} --id=672bbc58bccd1cf23f81fdbf 32 | - name: Run `openapi` command 🚀 33 | uses: readmeio/rdme@v8 34 | with: 35 | rdme: openapi vendor-shipments-api-model/vendorShipments.json --key=${{ secrets.README_API_KEY }} --id=672bbe959736d800458d34a1 36 | - name: Run `openapi` command 🚀 37 | uses: readmeio/rdme@v8 38 | with: 39 | rdme: openapi vendor-invoices-api-model/vendorInvoices.json --key=${{ secrets.README_API_KEY }} --id=672bbe78195b89000fdd5a12 40 | - name: Run `openapi` command 🚀 41 | uses: readmeio/rdme@v8 42 | with: 43 | rdme: openapi reports-api-model/reports_2021-06-30.json --key=${{ secrets.README_API_KEY }} --id=672bbe4a70bc520012f0807f 44 | - name: Run `openapi` command 🚀 45 | uses: readmeio/rdme@v8 46 | with: 47 | rdme: openapi orders-api-model/ordersV0.json --key=${{ secrets.README_API_KEY }} --id=672bbe2b026480e27a6cd778 48 | - name: Run `openapi` command 🚀 49 | uses: readmeio/rdme@v8 50 | with: 51 | rdme: openapi listings-items-api-model/listingsItems_2021-08-01.json --key=${{ secrets.README_API_KEY }} --id=672bbe0cd03b2700626433b0 52 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/resources/swagger-codegen/templates/api_test.mustache: -------------------------------------------------------------------------------- 1 | {{>partial_header}} 2 | using System; 3 | using System.IO; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.Linq; 7 | using System.Reflection; 8 | using RestSharp; 9 | using NUnit.Framework; 10 | 11 | using {{packageName}}.Client; 12 | using {{packageName}}.{{apiPackage}}; 13 | {{#hasImport}}using {{packageName}}.{{modelPackage}}; 14 | {{/hasImport}} 15 | 16 | namespace {{packageName}}.Test 17 | { 18 | /// 19 | /// Class for testing {{classname}} 20 | /// 21 | /// 22 | /// This file is automatically generated by Swagger Codegen. 23 | /// Please update the test case below to test the API endpoint. 24 | /// 25 | [TestFixture] 26 | public class {{classname}}Tests 27 | { 28 | private {{classname}} instance; 29 | 30 | /// 31 | /// Setup before each unit test 32 | /// 33 | [SetUp] 34 | public void Init() 35 | { 36 | // TODO uncomment below to initialize instance for testing 37 | //instance = new {{classname}}(); 38 | } 39 | 40 | /// 41 | /// Clean up after each unit test 42 | /// 43 | [TearDown] 44 | public void Cleanup() 45 | { 46 | 47 | } 48 | 49 | /// 50 | /// Test an instance of {{classname}} 51 | /// 52 | [Test] 53 | public void {{operationId}}InstanceTest() 54 | { 55 | // TODO uncomment below to test 'IsInstanceOfType' {{classname}} 56 | //Assert.IsInstanceOfType(typeof({{classname}}), instance, "instance is a {{classname}}"); 57 | } 58 | 59 | {{#operations}}{{#operation}} 60 | /// 61 | /// Test {{operationId}} 62 | /// 63 | [Test] 64 | public void {{operationId}}Test() 65 | { 66 | // TODO uncomment below to test the method and replace null with proper value 67 | {{#allParams}} 68 | //{{{dataType}}} {{paramName}} = null; 69 | {{/allParams}} 70 | //{{#returnType}}var response = {{/returnType}}instance.{{operationId}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}); 71 | {{#returnType}}//Assert.IsInstanceOf<{{{returnType}}}> (response, "response is {{{returnType}}}");{{/returnType}} 72 | } 73 | {{/operation}}{{/operations}} 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-javascript/src/helper/LwaAuthClient.mjs: -------------------------------------------------------------------------------- 1 | import superagent from 'superagent'; 2 | 3 | export class LwaAuthClient { 4 | #clientId = null; 5 | #clientSecret = null; 6 | #refreshToken = null; 7 | #accessToken = null; 8 | #accessTokenExpiry = null; 9 | /** 10 | * Constructs a new LwaAuthClient. 11 | * @class 12 | * @param {String} clientId LWA client ID. Get this value from SP-API Developer Portal. 13 | * @param {String} clientSecret LWA client secret. Get this value from SP-API Developer Portal. 14 | * @param {String} refreshToken LWA refresh token. Get this value from SP-API Developer Portal. 15 | */ 16 | constructor(clientId, clientSecret, refreshToken) { 17 | if (!clientId || typeof clientId !== 'string') { 18 | throw new Error(`invalid clientId.`); 19 | } 20 | if (!clientSecret || typeof clientSecret !== 'string') { 21 | throw new Error(`invalid clientSecret`); 22 | } 23 | if (!refreshToken || typeof refreshToken !== 'string') { 24 | throw new Error(`invalid refreshToken`); 25 | } 26 | this.#clientId = clientId; 27 | this.#clientSecret = clientSecret; 28 | this.#refreshToken = refreshToken; 29 | } 30 | 31 | /** 32 | * Either retrieve LWA access token or return access token if it already has valid token. 33 | * @returns {Promise} LWA access token. 34 | */ 35 | async getAccessToken() { 36 | if (this.#accessToken && this.#accessToken && this.#accessTokenExpiry > Date.now()) { 37 | console.log(`LWA access token already exists and is valid. ${this.#accessTokenExpiry}`); 38 | return Promise.resolve(this.#accessToken); 39 | } 40 | const token = await this.#doRefresh(); 41 | if (!token || !token.access_token) { 42 | throw new Error(`Failed to refresh LWA token.`); 43 | } 44 | this.#accessToken = token.access_token; 45 | this.#accessTokenExpiry = new Date().getTime() + (token.expires_in * 1000); 46 | return this.#accessToken; 47 | } 48 | 49 | /** 50 | * Private method to execute LWA token refresh flow. 51 | * @returns {Promise} LWA token response. 52 | */ 53 | #doRefresh = async () => { 54 | const res = await superagent.post('https://api.amazon.com/auth/o2/token') 55 | .send(`grant_type=refresh_token&refresh_token=${this.#refreshToken}&client_id=${this.#clientId}&client_secret=${this.#clientSecret}`) 56 | .set("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); 57 | return res.body; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /clients/postman-collections/tokens-api-sandbox-postman-collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "73a2e4eb-e98d-4f32-8443-866953365e6c", 4 | "name": "Tokens API Sandbox Collection", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "New Request", 10 | "request": { 11 | "auth": { 12 | "type": "noauth" 13 | }, 14 | "method": "POST", 15 | "header": [ 16 | { 17 | "key": "x-amz-access-token", 18 | "value": "", 19 | "type": "text", 20 | "description": "access_token value" 21 | } 22 | ], 23 | "body": { 24 | "mode": "raw", 25 | "raw": "{\n \"restrictedResources\": [\n {\n \"method\": \"GET\",\n \"path\": \"/orders/v0/orders/{orderId}/address\"\n }\n ]\n}", 26 | "options": { 27 | "raw": { 28 | "language": "json" 29 | } 30 | } 31 | }, 32 | "url": { 33 | "raw": "https://sandbox.sellingpartnerapi-na.amazon.com/tokens/2021-03-01/restrictedDataToken", 34 | "protocol": "https", 35 | "host": [ 36 | "sandbox", 37 | "sellingpartnerapi-na", 38 | "amazon", 39 | "com" 40 | ], 41 | "path": [ 42 | "tokens", 43 | "2021-03-01", 44 | "restrictedDataToken" 45 | ] 46 | } 47 | }, 48 | "response": [] 49 | }, 50 | { 51 | "name": "New Request", 52 | "request": { 53 | "auth": { 54 | "type": "noauth" 55 | }, 56 | "method": "GET", 57 | "header": [ 58 | { 59 | "key": "x-amz-access-token", 60 | "value": "", 61 | "type": "text", 62 | "description": "restrictedDataToken value" 63 | } 64 | ], 65 | "url": { 66 | "raw": "https://sandbox.sellingpartnerapi-na.amazon.com/orders/v0/orders/TEST_CASE_200/address", 67 | "protocol": "https", 68 | "host": [ 69 | "sandbox", 70 | "sellingpartnerapi-na", 71 | "amazon", 72 | "com" 73 | ], 74 | "path": [ 75 | "orders", 76 | "v0", 77 | "orders", 78 | "TEST_CASE_200", 79 | "address" 80 | ] 81 | } 82 | }, 83 | "response": [] 84 | } 85 | ], 86 | "event": [ 87 | { 88 | "listen": "prerequest", 89 | "script": { 90 | "type": "text/javascript", 91 | "exec": [ 92 | "" 93 | ] 94 | } 95 | }, 96 | { 97 | "listen": "test", 98 | "script": { 99 | "type": "text/javascript", 100 | "exec": [ 101 | "" 102 | ] 103 | } 104 | } 105 | ], 106 | "variable": [ 107 | { 108 | "key": "lwa_token", 109 | "value": "", 110 | "disabled": true 111 | } 112 | ] 113 | } -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/main/java/com/amazon/spapi/documents/DownloadSpecification.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents; 2 | 3 | import com.google.common.base.Preconditions; 4 | 5 | /** 6 | * Specification for {@link DownloadHelper#download(DownloadSpecification)}. 7 | */ 8 | public class DownloadSpecification { 9 | private final CompressionAlgorithm compressionAlgorithm; 10 | private final CryptoStreamFactory cryptoStreamFactory; 11 | private final String url; 12 | 13 | private DownloadSpecification(CompressionAlgorithm compressionAlgorithm, CryptoStreamFactory cryptoStreamFactory, 14 | String url) { 15 | this.compressionAlgorithm = compressionAlgorithm; 16 | this.cryptoStreamFactory = cryptoStreamFactory; 17 | this.url = url; 18 | } 19 | 20 | CompressionAlgorithm getCompressionAlgorithm() { 21 | return compressionAlgorithm; 22 | } 23 | 24 | CryptoStreamFactory getCryptoStreamFactory() { 25 | return cryptoStreamFactory; 26 | } 27 | 28 | String getUrl() { 29 | return url; 30 | } 31 | 32 | /** 33 | * Use this to create an instance of a {@link DownloadSpecification}. 34 | */ 35 | public static class Builder { 36 | private final CryptoStreamFactory cryptoStreamFactory; 37 | private final String url; 38 | 39 | private CompressionAlgorithm compressionAlgorithm = null; 40 | 41 | /** 42 | * Create the builder. 43 | * 44 | * @param cryptoStreamFactory The crypto stream factory 45 | * @param url The url to download the encrypted document from 46 | */ 47 | public Builder(CryptoStreamFactory cryptoStreamFactory, String url) { 48 | Preconditions.checkArgument(cryptoStreamFactory != null, "cryptoStreamFactory is required"); 49 | Preconditions.checkArgument(url != null, "url is required"); 50 | 51 | this.cryptoStreamFactory = cryptoStreamFactory; 52 | this.url = url; 53 | } 54 | 55 | /** 56 | * The compression algorithm. 57 | * 58 | * @param compressionAlgorithm The compression algorithm 59 | * @return this 60 | */ 61 | public Builder withCompressionAlgorithm(CompressionAlgorithm compressionAlgorithm) { 62 | this.compressionAlgorithm = compressionAlgorithm; 63 | return this; 64 | } 65 | 66 | /** 67 | * Create the specification. 68 | * 69 | * @return The specification 70 | */ 71 | public DownloadSpecification build() { 72 | return new DownloadSpecification(compressionAlgorithm, cryptoStreamFactory, url); 73 | } 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-javascript/src/resources/swagger-codegen/templates/index.mustache: -------------------------------------------------------------------------------- 1 | {{>licenseInfo}} 2 | {{=< >=}} 3 | import {ApiClient} from './ApiClient.js'; 4 | <#models>import {<#model>} from './<#modelPackage>/.js'; 5 | <#apiInfo><#apis>import {} from './<#apiPackage>/.js'; 6 | 7 | <={{ }}=> 8 | 9 | {{#emitJSDoc}}/**{{#projectDescription}} 10 | * {{projectDescription}}.
{{/projectDescription}} 11 | * The index module provides access to constructors for all the classes which comprise the public API. 12 | *

13 | * An AMD (recommended!) or CommonJS application will generally do something equivalent to the following: 14 | *

15 | * var {{moduleName}} = require('{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index'); // See note below*.
16 | * var xxxSvc = new {{moduleName}}.XxxApi(); // Allocate the API class we're going to use.
17 | * var yyyModel = new {{moduleName}}.Yyy(); // Construct a model instance.
18 | * yyyModel.someProperty = 'someValue';
19 | * ...
20 | * var zzz = xxxSvc.doSomething(yyyModel); // Invoke the service.
21 | * ...
22 | * 
23 | * *NOTE: For a top-level AMD script, use require(['{{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index'], function(){...}) 24 | * and put the application logic within the callback function. 25 | *

26 | *

27 | * A non-AMD browser application (discouraged) might do something like this: 28 | *

29 | * var xxxSvc = new {{moduleName}}.XxxApi(); // Allocate the API class we're going to use.
30 | * var yyy = new {{moduleName}}.Yyy(); // Construct a model instance.
31 | * yyyModel.someProperty = 'someValue';
32 | * ...
33 | * var zzz = xxxSvc.doSomething(yyyModel); // Invoke the service.
34 | * ...
35 | * 
36 | *

37 | * @module {{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}index 38 | * @version {{projectVersion}} 39 | */{{/emitJSDoc}} 40 | export { 41 | {{=< >=}} 42 | <#emitJSDoc>/** 43 | * The ApiClient constructor. 44 | * @property {module:<#invokerPackage>/ApiClient} 45 | */ 46 | ApiClient<#models>, 47 | 48 | <#emitJSDoc>/** 49 | * The model constructor. 50 | * @property {module:<#invokerPackage>/<#modelPackage>/} 51 | */ 52 | <#apiInfo><#apis>, 53 | 54 | <#emitJSDoc>/** 55 | * The service constructor. 56 | * @property {module:<#invokerPackage>/<#apiPackage>/} 57 | */ 58 | 59 | };<={{ }}=> 60 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-php/src/authandauth/RateLimitConfigurationOnRequests.php: -------------------------------------------------------------------------------- 1 | rateLimitType = $config["rateLimitType"] ?? "token_bucket"; 35 | $this->rateLimitToken = $config["rateLimitToken"]; 36 | $this->rateLimitTokenLimit = $config["rateLimitTokenLimit"]; 37 | 38 | $this->waitTimeOutInMilliSeconds = $config["waitTimeOutInMilliSeconds"] ?? 0; 39 | if ($this->rateLimitType === "sliding_window" && $this->waitTimeOutInMilliSeconds != 0) { 40 | throw new InvalidArgumentException("Sliding Window RateLimiter cannot reserve tokens"); 41 | } 42 | } 43 | 44 | public function getRateLimitType(): string 45 | { 46 | return $this->rateLimitType; 47 | } 48 | 49 | public function setRateLimitType(string $rateLimitType): RateLimitConfigurationOnRequests 50 | { 51 | $this->rateLimitType = $rateLimitType; 52 | return $this; 53 | } 54 | 55 | public function getRateLimitToken(): int 56 | { 57 | return $this->rateLimitToken; 58 | } 59 | 60 | public function setRateLimitToken(int $rateLimitToken): RateLimitConfigurationOnRequests 61 | { 62 | $this->rateLimitToken = $rateLimitToken; 63 | return $this; 64 | } 65 | 66 | public function getRateLimitTokenLimit(): int 67 | { 68 | return $this->rateLimitTokenLimit; 69 | } 70 | 71 | public function setRateLimitTokenLimit(int $rateLimitTokenLimit): RateLimitConfigurationOnRequests 72 | { 73 | $this->rateLimitTokenLimit = $rateLimitTokenLimit; 74 | return $this; 75 | } 76 | 77 | public function getTimeOut(): float 78 | { 79 | return $this->waitTimeOutInMilliSeconds; 80 | } 81 | 82 | public function setTimeOut(float $waitTimeOutInMilliSeconds): RateLimitConfigurationOnRequests 83 | { 84 | $this->waitTimeOutInMilliSeconds = $waitTimeOutInMilliSeconds; 85 | return $this; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-php/resources/openapi-generator/templates/ModelInterface.mustache: -------------------------------------------------------------------------------- 1 | clientId = $config["clientId"]; 45 | $this->clientSecret = $config["clientSecret"]; 46 | $this->refreshToken = $config["refreshToken"] ?? null; 47 | $this->endpoint = $config["endpoint"]; 48 | $this->scopes = $config["scopes"] ?? null; 49 | } 50 | 51 | public function getClientId(): string 52 | { 53 | return $this->clientId; 54 | } 55 | 56 | public function setClientId(string $clientId): LWAAuthorizationCredentials 57 | { 58 | $this->clientId = $clientId; 59 | return $this; 60 | } 61 | 62 | public function getClientSecret(): string 63 | { 64 | return $this->clientSecret; 65 | } 66 | 67 | public function setClientSecret(string $clientSecret): LWAAuthorizationCredentials 68 | { 69 | $this->clientSecret = $clientSecret; 70 | return $this; 71 | } 72 | 73 | public function getRefreshToken(): ?string 74 | { 75 | return $this->refreshToken; 76 | } 77 | 78 | public function setRefreshToken(?string $refreshToken): LWAAuthorizationCredentials 79 | { 80 | $this->refreshToken = $refreshToken; 81 | return $this; 82 | } 83 | 84 | public function getEndpoint(): string 85 | { 86 | return $this->endpoint; 87 | } 88 | 89 | public function setEndpoint(string $endpoint): LWAAuthorizationCredentials 90 | { 91 | $this->endpoint = $endpoint; 92 | return $this; 93 | } 94 | 95 | public function getScopes(): ?array 96 | { 97 | return $this->scopes; 98 | } 99 | 100 | public function setScopes(?array $scopes): LWAAuthorizationCredentials 101 | { 102 | $this->scopes = $scopes; 103 | return $this; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/Utils.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Security.Cryptography; 3 | using System.Globalization; 4 | 5 | namespace Amazon.SellingPartnerAPIAA 6 | { 7 | public static class Utils 8 | { 9 | public const string ValidUrlCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; 10 | 11 | /// 12 | /// Returns URL encoded version of input data according to RFC-3986 13 | /// 14 | /// String to be URL-encoded 15 | /// URL encoded version of input data 16 | public static string UrlEncode(string data) 17 | { 18 | StringBuilder encoded = new StringBuilder(); 19 | foreach (char symbol in Encoding.UTF8.GetBytes(data)) 20 | { 21 | if (ValidUrlCharacters.IndexOf(symbol) != -1) 22 | { 23 | encoded.Append(symbol); 24 | } 25 | else 26 | { 27 | encoded.Append("%").Append(string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)symbol)); 28 | } 29 | } 30 | return encoded.ToString(); 31 | } 32 | 33 | /// 34 | /// Returns hashed value of input data using SHA256 35 | /// 36 | /// String to be hashed 37 | /// Hashed value of input data 38 | public static byte[] Hash(string data) 39 | { 40 | return new SHA256CryptoServiceProvider().ComputeHash(Encoding.UTF8.GetBytes(data)); 41 | } 42 | 43 | /// 44 | /// Returns lowercase hexadecimal string of input byte array 45 | /// 46 | /// Data to be converted 47 | /// Lowercase hexadecimal string 48 | public static string ToHex(byte[] data) 49 | { 50 | StringBuilder sb = new StringBuilder(); 51 | 52 | for (int i = 0; i < data.Length; i++) 53 | { 54 | sb.Append(data[i].ToString("x2", CultureInfo.InvariantCulture)); 55 | } 56 | 57 | return sb.ToString(); 58 | } 59 | 60 | /// 61 | /// Computes the hash of given string using the specified key with HMACSHA256 62 | /// 63 | /// Key 64 | /// String to be hashed 65 | /// Hashed value of input data 66 | public static byte[] GetKeyedHash(byte[] key, string value) 67 | { 68 | KeyedHashAlgorithm hashAlgorithm = new HMACSHA256(key); 69 | hashAlgorithm.Initialize(); 70 | return hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(value)); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/tst/com/amazon/SellingPartnerAPIAA/LWAClientScopesSerializerDeserializerTest.java: -------------------------------------------------------------------------------- 1 | package com.amazon.SellingPartnerAPIAA; 2 | 3 | import com.google.gson.Gson; 4 | import org.junit.Assert; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.Arguments; 8 | import org.junit.jupiter.params.provider.MethodSource; 9 | 10 | import java.util.Arrays; 11 | import java.util.HashSet; 12 | import java.util.Set; 13 | import java.util.stream.Stream; 14 | 15 | import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_NOTIFICATIONS_API; 16 | import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_MIGRATION_API; 17 | 18 | public class LWAClientScopesSerializerDeserializerTest { 19 | private static final String TEST_SCOPE_1 = SCOPE_NOTIFICATIONS_API; 20 | private static final String TEST_SCOPE_2 = SCOPE_MIGRATION_API; 21 | 22 | private static final Set scopesTestSellerless = new HashSet(Arrays.asList(TEST_SCOPE_1, 23 | TEST_SCOPE_2)); 24 | 25 | private static final String SELLER_TYPE_SELLER = "seller"; 26 | private static final String SELLER_TYPE_SELLERLESS = "sellerless"; 27 | 28 | private Gson gson; 29 | 30 | @BeforeEach 31 | public void setup() { 32 | gson = new Gson(); 33 | } 34 | 35 | public static Stream scopeSerialization(){ 36 | 37 | return Stream.of( 38 | Arguments.of(SELLER_TYPE_SELLER, null), 39 | Arguments.of(SELLER_TYPE_SELLERLESS, new LWAClientScopes(scopesTestSellerless)) 40 | ); 41 | } 42 | 43 | public static Stream scopeDeserialization(){ 44 | 45 | return Stream.of( 46 | Arguments.of(SELLER_TYPE_SELLER, null), 47 | Arguments.of(SELLER_TYPE_SELLERLESS, "{\"scope\":\"sellingpartnerapi::migration sellingpartnerapi::notifications\"}") 48 | ); 49 | } 50 | 51 | @ParameterizedTest 52 | @MethodSource("scopeSerialization") 53 | public void testSerializeScope(String sellerType, LWAClientScopes testScope){ 54 | 55 | String scopeJSON = gson.toJson(testScope); 56 | 57 | if (sellerType.equals(SELLER_TYPE_SELLER)) { 58 | Assert.assertEquals("null", scopeJSON); 59 | } 60 | else if (sellerType.equals(SELLER_TYPE_SELLERLESS)){ 61 | Assert.assertTrue(!scopeJSON.isEmpty()); 62 | } 63 | } 64 | 65 | @ParameterizedTest 66 | @MethodSource("scopeDeserialization") 67 | public void testDeserializeScope(String sellerType, String serializedValue){ 68 | 69 | LWAClientScopes deserializedValue = gson.fromJson(serializedValue, LWAClientScopes.class); 70 | if (sellerType.equals(SELLER_TYPE_SELLER)) { 71 | Assert.assertNull(deserializedValue); 72 | } 73 | else if (sellerType.equals(SELLER_TYPE_SELLERLESS)){ 74 | Assert.assertEquals(deserializedValue.getScopes(),scopesTestSellerless); 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAAuthorizationSigner.java: -------------------------------------------------------------------------------- 1 | package com.amazon.SellingPartnerAPIAA; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import okhttp3.Request; 7 | 8 | /** 9 | * LWA Authorization Signer 10 | */ 11 | public class LWAAuthorizationSigner { 12 | public static final String SIGNED_ACCESS_TOKEN_HEADER_NAME = "x-amz-access-token"; 13 | 14 | @Getter(AccessLevel.PACKAGE) 15 | @Setter(AccessLevel.PACKAGE) 16 | private LWAClient lwaClient; 17 | 18 | private LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta; 19 | 20 | private void buildLWAAccessTokenRequestMeta(LWAAuthorizationCredentials lwaAuthorizationCredentials) { 21 | String tokenRequestGrantType; 22 | if (!lwaAuthorizationCredentials.getScopes().isEmpty()) { 23 | tokenRequestGrantType = "client_credentials"; 24 | } else { 25 | tokenRequestGrantType = "refresh_token"; 26 | } 27 | 28 | lwaAccessTokenRequestMeta = LWAAccessTokenRequestMeta.builder() 29 | .clientId(lwaAuthorizationCredentials.getClientId()) 30 | .clientSecret(lwaAuthorizationCredentials.getClientSecret()) 31 | .refreshToken(lwaAuthorizationCredentials.getRefreshToken()) 32 | .grantType(tokenRequestGrantType).scopes(lwaAuthorizationCredentials.getScopes()) 33 | .build(); 34 | } 35 | 36 | /** 37 | * 38 | * @param lwaAuthorizationCredentials LWA Authorization Credentials for token exchange 39 | */ 40 | public LWAAuthorizationSigner(LWAAuthorizationCredentials lwaAuthorizationCredentials) { 41 | 42 | lwaClient = new LWAClient(lwaAuthorizationCredentials.getEndpoint()); 43 | 44 | buildLWAAccessTokenRequestMeta(lwaAuthorizationCredentials); 45 | 46 | } 47 | 48 | /** 49 | * 50 | * Overloaded Constructor @param lwaAuthorizationCredentials LWA Authorization Credentials for token exchange 51 | * and LWAAccessTokenCache 52 | */ 53 | public LWAAuthorizationSigner(LWAAuthorizationCredentials lwaAuthorizationCredentials, 54 | LWAAccessTokenCache lwaAccessTokenCache) { 55 | 56 | lwaClient = new LWAClient(lwaAuthorizationCredentials.getEndpoint()); 57 | lwaClient.setLWAAccessTokenCache(lwaAccessTokenCache); 58 | 59 | buildLWAAccessTokenRequestMeta(lwaAuthorizationCredentials); 60 | 61 | } 62 | 63 | /** 64 | * Signs a Request with an LWA Access Token 65 | * @param originalRequest Request to sign (treated as immutable) 66 | * @return Copy of originalRequest with LWA signature 67 | * @throws LWAException If calls to fetch LWA access token fails 68 | */ 69 | public Request sign(Request originalRequest) throws LWAException { 70 | String accessToken = lwaClient.getAccessToken(lwaAccessTokenRequestMeta); 71 | 72 | return originalRequest.newBuilder() 73 | .addHeader(SIGNED_ACCESS_TOKEN_HEADER_NAME, accessToken) 74 | .build(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/test/java/com/amazon/spapi/documents/impl/AESCryptoStreamFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents.impl; 2 | 3 | import com.amazon.spapi.documents.exception.CryptoException; 4 | import com.google.common.io.ByteStreams; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.io.ByteArrayInputStream; 8 | import java.io.InputStream; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.Arrays; 11 | import java.util.Base64; 12 | 13 | import static org.junit.jupiter.api.Assertions.*; 14 | 15 | class AESCryptoStreamFactoryTest { 16 | private static String KEY = "sxx/wImF6BFndqSAz56O6vfiAh8iD9P297DHfFgujec="; 17 | private static String VECTOR = "7S2tn363v0wfCfo1IX2Q1A=="; 18 | 19 | @Test 20 | public void testBuilderConstructorMissingInitializationVector() { 21 | String key = "DEF"; 22 | String initializationVector = null; 23 | 24 | assertThrows(IllegalArgumentException.class, () -> 25 | new AESCryptoStreamFactory.Builder(key, initializationVector)); 26 | } 27 | 28 | @Test 29 | public void testBuilderConstructorMissingKey() { 30 | String key = null; 31 | String initializationVector = "ABC"; 32 | 33 | assertThrows(IllegalArgumentException.class, () -> 34 | new AESCryptoStreamFactory.Builder(key, initializationVector)); 35 | } 36 | 37 | @Test 38 | public void testBadKey() throws Exception { 39 | byte[] decodedKey = Base64.getDecoder().decode(KEY); 40 | String encodedKey = Base64.getEncoder().encodeToString( 41 | Arrays.copyOfRange(decodedKey, 2, decodedKey.length-1)); 42 | 43 | AESCryptoStreamFactory aesCryptoStreamFactory = new AESCryptoStreamFactory.Builder(encodedKey, VECTOR).build(); 44 | try (InputStream inputStream = new ByteArrayInputStream("Hello World!".getBytes(StandardCharsets.UTF_8))) { 45 | assertThrows(CryptoException.class, () -> aesCryptoStreamFactory.newDecryptStream(inputStream)); 46 | } 47 | } 48 | 49 | @Test 50 | public void testEncryptDecrypt() throws Exception { 51 | String stringContent = "Hello World!"; 52 | byte[] byteContent = stringContent.getBytes(StandardCharsets.UTF_8); 53 | 54 | AESCryptoStreamFactory aesCryptoStreamFactory = new AESCryptoStreamFactory.Builder(KEY, VECTOR).build(); 55 | 56 | try (InputStream encryptStream = 57 | aesCryptoStreamFactory.newEncryptStream(new ByteArrayInputStream(byteContent))) { 58 | byte[] encryptedContent = ByteStreams.toByteArray(encryptStream); 59 | 60 | try (InputStream decryptStream = 61 | aesCryptoStreamFactory.newDecryptStream(new ByteArrayInputStream(encryptedContent))) { 62 | byte[] decryptedContent = ByteStreams.toByteArray(decryptStream); 63 | 64 | assertArrayEquals(decryptedContent, byteContent); 65 | assertFalse(Arrays.equals(encryptedContent, decryptedContent)); 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-javascript/src/generate-js-sdk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | GITHUB_REPO="https://github.com/amzn/selling-partner-api-models.git" 3 | BASE_DIR=$(cd "$(dirname "$0")" && cd .. && pwd) 4 | CLONE_DIR="$BASE_DIR/models" 5 | 6 | # Get swagger jar file path from the input arguments 7 | swagger_jar_path="" 8 | while getopts 'j:' flag; do 9 | case "${flag}" in 10 | j) swagger_jar_path="${OPTARG}" 11 | ;; 12 | *) echo "available options are '-j'" 13 | exit 1 14 | ;; 15 | esac 16 | done 17 | 18 | if [ ! -f "$swagger_jar_path" ]; then 19 | echo "option '-j ' did not specify executable file. Aborted." 20 | exit 1 21 | fi 22 | 23 | ########################################################################## 24 | if [ -d "$CLONE_DIR" ]; then 25 | # prompt user, and if user gives "y", delete the directory. 26 | read -p "Found $CLONE_DIR already exists. Would you like to delete all the files under '$CLONE_DIR' and clone again? [y/n]: " confirm 27 | if [ "$confirm" == "y" ]; then 28 | # delete all the files in the directory 29 | rm -rf "${CLONE_DIR:?}/*" 30 | echo "Deleted files in $CLONE_DIR directory. Recreating $CLONE_DIR again." 31 | git clone "$GITHUB_REPO" "$CLONE_DIR" 32 | else 33 | echo "OK. Proceeding with the existing ${CLONE_DIR} without cloning." 34 | fi 35 | else 36 | midir "$CLONE_DIR" 37 | git clone "$GITHUB_REPO" "$CLONE_DIR" 38 | fi 39 | 40 | ########################################################################## 41 | TEMPLATE_DIR="$BASE_DIR/src/resources/swagger-codegen/templates" 42 | MODEL_DIR="$CLONE_DIR/models" 43 | SDK_DIR="$BASE_DIR/sdk" 44 | CODEGEN_CONFIG_PATH="$BASE_DIR/src/js.config.json" 45 | 46 | MODELS=("${MODEL_DIR}"/*/*) 47 | basePackage="js-client" 48 | 49 | if [ -d "$SDK_DIR" ]; then 50 | rm -rf "${SDK_DIR:?}/*" 51 | fi 52 | 53 | if [ ! -d "$SDK_DIR" ]; then 54 | mkdir "$SDK_DIR" 55 | fi 56 | 57 | function get_model_name () { 58 | swaggerFile="$1" 59 | modelNameWithExtension="${swaggerFile##*/}" 60 | echo "${modelNameWithExtension%.*}" 61 | } 62 | 63 | for model in "${MODELS[@]}" 64 | do 65 | modelName=$(get_model_name "$model") 66 | echo "model = $model" 67 | echo "model name = $modelName" 68 | java -jar "${swagger_jar_path}" generate \ 69 | --config "${CODEGEN_CONFIG_PATH}" \ 70 | --input-spec "${model}" \ 71 | --lang javascript \ 72 | --template-dir "${TEMPLATE_DIR}" \ 73 | --output "${SDK_DIR}" \ 74 | --invoker-package "${modelName}" \ 75 | --api-package "${basePackage}.${modelName}.api" \ 76 | --model-package "${basePackage}.${modelName}.model" \ 77 | --group-id "com.amazon" \ 78 | --artifact-id "sp-api-javascript-client" 79 | done 80 | 81 | echo "***********************************************************" 82 | echo "SP-API SDK is created under ${SDK_DIR} and SDK source code " 83 | echo "should be found ${SDK_DIR}/src/." 84 | echo "Please copy the SDK source DIRECTORIES (such as \"catalogItems_2022-04-01\")" 85 | echo "you want to use to the directory \"code/javascript/src/jsdsdk.\"." -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-php/src/authandauth/LWAAccessTokenRequestMeta.php: -------------------------------------------------------------------------------- 1 | refreshToken = $lwaAuthorizationCredentials->getRefreshToken(); 28 | $this->clientId = $lwaAuthorizationCredentials->getClientId(); 29 | $this->clientSecret = $lwaAuthorizationCredentials->getClientSecret(); 30 | $this->scopes = $lwaAuthorizationCredentials->getScopes(); 31 | 32 | if (!empty($lwaAuthorizationCredentials->getScopes())) { 33 | $this->grantType = "client_credentials"; 34 | } else { 35 | $this->grantType = "refresh_token"; 36 | } 37 | } 38 | 39 | public function jsonSerialize(): array 40 | { 41 | return [ 42 | static::GRANT_TYPE_SERIALIZED => $this->grantType, 43 | static::CLIENT_ID_SERIALIZED => $this->clientId, 44 | static::CLIENT_SECRET_SERIALIZED => $this->clientSecret, 45 | static::REFRESH_TOKEN_SERIALIZED => $this->refreshToken, 46 | static::SCOPE_SERIALIZED => $this->scopes ? implode(" ", $this->scopes) : null 47 | ]; 48 | } 49 | 50 | public function getGrantType(): string 51 | { 52 | return $this->grantType; 53 | } 54 | 55 | public function setGrantType(string $grantType) 56 | { 57 | $this->grantType = $grantType; 58 | } 59 | 60 | public function getRefreshToken(): ?string 61 | { 62 | return $this->refreshToken; 63 | } 64 | 65 | public function setRefreshToken(string $refreshToken) 66 | { 67 | $this->refreshToken = $refreshToken; 68 | } 69 | 70 | public function getClientId(): string 71 | { 72 | return $this->clientId; 73 | } 74 | 75 | public function setClientId(string $clientId) 76 | { 77 | $this->clientId = $clientId; 78 | } 79 | 80 | public function getClientSecret(): string 81 | { 82 | return $this->clientSecret; 83 | } 84 | 85 | public function setClientSecret(string $clientSecret) 86 | { 87 | $this->clientSecret = $clientSecret; 88 | } 89 | 90 | public function getScopes(): ?array 91 | { 92 | return $this->scopes; 93 | } 94 | 95 | public function setScopes(?array $scopes) 96 | { 97 | $this->scopes = $scopes; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/test/Amazon.SellingPartnerAPIAATests/LWAAccessTokenRequestMetaBuilderTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Amazon.SellingPartnerAPIAA; 4 | using Xunit; 5 | 6 | namespace Amazon.SellingPartnerAPIAATests 7 | { 8 | public class LWAAccessTokenRequestMetaBuilderTest 9 | { 10 | private const string TestClientId = "cid"; 11 | private const string TestClientSecret = "csecret"; 12 | private const string TestRefreshToken = "rtoken"; 13 | private static readonly Uri TestUri = new Uri("https://www.amazon.com"); 14 | private LWAAccessTokenRequestMetaBuilder lwaAccessTokenRequestMetaBuilderUnderTest; 15 | 16 | public LWAAccessTokenRequestMetaBuilderTest() 17 | { 18 | lwaAccessTokenRequestMetaBuilderUnderTest = new LWAAccessTokenRequestMetaBuilder(); 19 | } 20 | 21 | [Fact] 22 | public void LWAAuthorizationCredentialsWithoutScopesBuildsSellerTokenRequestMeta() 23 | { 24 | LWAAuthorizationCredentials lwaAuthorizationCredentials = new LWAAuthorizationCredentials() 25 | { 26 | ClientId = TestClientId, 27 | ClientSecret = TestClientSecret, 28 | Endpoint = TestUri, 29 | RefreshToken = TestRefreshToken 30 | }; 31 | 32 | LWAAccessTokenRequestMeta expected = new LWAAccessTokenRequestMeta() 33 | { 34 | ClientId = TestClientId, 35 | ClientSecret = TestClientSecret, 36 | GrantType = LWAAccessTokenRequestMetaBuilder.SellerAPIGrantType, 37 | RefreshToken = TestRefreshToken, 38 | Scope = null 39 | }; 40 | 41 | LWAAccessTokenRequestMeta actual = lwaAccessTokenRequestMetaBuilderUnderTest.Build(lwaAuthorizationCredentials); 42 | 43 | Assert.Equal(expected, actual); 44 | } 45 | 46 | [Fact] 47 | public void LWAAuthorizationCredentialsWithScopesBuildsSellerlessTokenRequestMeta() 48 | { 49 | LWAAuthorizationCredentials lwaAuthorizationCredentials = new LWAAuthorizationCredentials() 50 | { 51 | ClientId = TestClientId, 52 | ClientSecret = TestClientSecret, 53 | Endpoint = TestUri, 54 | Scopes = new List() { ScopeConstants.ScopeMigrationAPI, ScopeConstants.ScopeNotificationsAPI } 55 | }; 56 | 57 | LWAAccessTokenRequestMeta expected = new LWAAccessTokenRequestMeta() 58 | { 59 | ClientId = TestClientId, 60 | ClientSecret = TestClientSecret, 61 | GrantType = LWAAccessTokenRequestMetaBuilder.SellerlessAPIGrantType, 62 | Scope = string.Format("{0} {1}", ScopeConstants.ScopeMigrationAPI, ScopeConstants.ScopeNotificationsAPI), 63 | RefreshToken = null 64 | }; 65 | 66 | LWAAccessTokenRequestMeta actual = lwaAccessTokenRequestMetaBuilderUnderTest.Build(lwaAuthorizationCredentials); 67 | 68 | Assert.Equal(expected, actual); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-php/src/authandauth/RestrictedDataTokenSigner.php: -------------------------------------------------------------------------------- 1 | withHeader(LWAAuthorizationSigner::SIGNED_ACCESS_TOKEN_HEADER_NAME, $restrictedDataToken); 28 | } 29 | 30 | /** 31 | * Check if an operation requires RDT access 32 | * 33 | * @param string $operationName The operation name in format 'ClassName-operationId' 34 | * @return bool True if the operation requires RDT, false otherwise 35 | */ 36 | public static function isRestrictedOperation(string $operationName): bool 37 | { 38 | // List of operations that require RDT access 39 | $restrictedOperations = [ 40 | // Direct Fulfillment Orders API 41 | 'VendorOrdersApi-getOrders', 42 | 'VendorOrdersApi-getOrder', 43 | 44 | // Direct Fulfillment Shipping API 45 | 'VendorShippingLabelsApi-getShippingLabel', 46 | 'VendorShippingLabelsApi-getShippingLabels', 47 | 'VendorShippingApi-getPackingSlips', 48 | 'VendorShippingApi-getPackingSlip', 49 | 'VendorShippingLabelsApi-getCustomerInvoice', 50 | 'VendorShippingLabelsApi-getCustomerInvoices', 51 | 'VendorShippingLabelsApi-createShippingLabels', 52 | 53 | // Easy Ship API v2022-03-23 54 | 'EasyShipApi-createScheduledPackageBulk', 55 | 56 | // Orders API 57 | 'OrdersV0Api-getOrders', 58 | 'OrdersV0Api-getOrder', 59 | 'OrdersV0Api-getOrderBuyerInfo', 60 | 'OrdersV0Api-getOrderAddress', 61 | 'OrdersV0Api-getOrderItems', 62 | 'OrdersV0Api-getOrderItemsBuyerInfo', 63 | 'OrdersV0Api-getOrderRegulatedInfo', 64 | 65 | // Merchant Fulfillment API 66 | 'MerchantFulfillmentApi-getShipment', 67 | 'MerchantFulfillmentApi-cancelShipment', 68 | 'MerchantFulfillmentApi-createShipment', 69 | 70 | // Shipment Invoicing 71 | 'ShipmentInvoiceApi-getShipmentDetails', 72 | 73 | // Reports API 74 | 'ReportsApi-getReportDocument' 75 | ]; 76 | 77 | return in_array($operationName, $restrictedOperations); 78 | } 79 | } 80 | 81 | 82 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/test/java/com/amazon/spapi/documents/UploadSpecificationTest.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.mockito.Mockito; 5 | 6 | import java.io.ByteArrayInputStream; 7 | import java.io.InputStream; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | class UploadSpecificationTest { 12 | @Test 13 | public void testBuilderConstructorMissingContentType() throws Exception { 14 | String contentType = null; 15 | CryptoStreamFactory cryptoStreamFactory = Mockito.mock(CryptoStreamFactory.class); 16 | InputStream source = new ByteArrayInputStream(new byte[0]); 17 | String url = "https://www.amazon.com"; 18 | 19 | assertThrows(IllegalArgumentException.class, () -> new UploadSpecification.Builder( 20 | contentType, cryptoStreamFactory, source, url)); 21 | } 22 | 23 | @Test 24 | public void testBuilderConstructorMissingCryptoStreamFactory() throws Exception { 25 | String contentType = "text/xml; charset=UTF-8"; 26 | CryptoStreamFactory cryptoStreamFactory = null; 27 | InputStream source = new ByteArrayInputStream(new byte[0]); 28 | String url = "https://www.amazon.com"; 29 | 30 | assertThrows(IllegalArgumentException.class, () -> new UploadSpecification.Builder( 31 | contentType, cryptoStreamFactory, source, url)); 32 | } 33 | 34 | @Test 35 | public void testBuilderConstructorMissingSource() throws Exception { 36 | String contentType = "text/xml; charset=UTF-8"; 37 | CryptoStreamFactory cryptoStreamFactory = Mockito.mock(CryptoStreamFactory.class); 38 | InputStream source = null; 39 | String url = "https://www.amazon.com"; 40 | 41 | assertThrows(IllegalArgumentException.class, () -> new UploadSpecification.Builder( 42 | contentType, cryptoStreamFactory, source, url)); 43 | } 44 | 45 | @Test 46 | public void testBuilderConstructorMissingUrl() throws Exception { 47 | String contentType = "text/xml; charset=UTF-8"; 48 | CryptoStreamFactory cryptoStreamFactory = Mockito.mock(CryptoStreamFactory.class); 49 | InputStream source = new ByteArrayInputStream(new byte[0]); 50 | String url = null; 51 | 52 | assertThrows(IllegalArgumentException.class, () -> new UploadSpecification.Builder( 53 | contentType, cryptoStreamFactory, source, url)); 54 | } 55 | 56 | @Test 57 | public void testSuccess() throws Exception { 58 | String contentType = "text/xml; charset=UTF-8"; 59 | CryptoStreamFactory cryptoStreamFactory = Mockito.mock(CryptoStreamFactory.class); 60 | InputStream source = new ByteArrayInputStream(new byte[0]); 61 | String url = "http://abc.com/123"; 62 | 63 | UploadSpecification spec = new UploadSpecification.Builder(contentType, cryptoStreamFactory, source, url) 64 | .build(); 65 | 66 | assertEquals(contentType, spec.getContentType()); 67 | assertSame(cryptoStreamFactory, spec.getCryptoStreamFactory()); 68 | assertSame(source, spec.getSource()); 69 | assertEquals(url, spec.getUrl()); 70 | } 71 | } -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-php/README.md: -------------------------------------------------------------------------------- 1 | # Selling Partner API Authentication/Authorization Library 2 | This library provides helper classes for use when signing HTTP requests for Amazon Selling Partner APIs. It is intended for use 3 | with the Selling Partner API Client Libraries generated by [openapi generator](https://openapi-generator.tech/) 4 | using the Guzzlehttp library. It can also be integrated into custom projects. 5 | 6 | ## LWAAuthorizationSigner 7 | Obtains and signs a request with an access token from LWA (Login with Amazon) for the specified endpoint using the provided LWA credentials. 8 | 9 | *Example* 10 | ``` 11 | $lwaAuthorizationCredentials = new LWAAuthorizationCredentials([ 12 | "clientId" => '.....', 13 | "clientSecret" => '.....', 14 | "refreshToken" => '.....', 15 | "endpoint" => 'https://api.amazon.com/auth/o2/token' 16 | ]); 17 | 18 | // Initialize LWAAuthorizationSigner instance 19 | $lwaAuthorizationSigner = new LWAAuthorizationSigner($lwaAuthorizationCredentials); 20 | $config = new Configuration([], $lwaAuthorizationCredentials); 21 | 22 | // Setting SP-API endpoint region. Change it according to the desired region 23 | $config->setHost('https://sellingpartnerapi-na.amazon.com'); 24 | 25 | // Create a new HTTP client 26 | $client = new GuzzleHttp\Client(); 27 | 28 | // Create an instance of the Orders Api client 29 | $api = new OrdersApi($config, null, $client); 30 | ``` 31 | 32 | ## LWAAccessTokenCache 33 | Implements cache for access token that is returned in LWAClient and reuses the access token until time to live. 34 | 35 | ## RateLimitConfiguration 36 | Interface to set and get rateLimit configurations that are used with RateLimiter. RateLimiter is used on client side to restrict the rate at which requests are made. RateLimitConfiguration takes a Permit, the rate at which requests are made, and TimeOut. 37 | 38 | *Example* 39 | ``` 40 | $rateLimitOption = new RateLimitConfigurationOnRequests([ 41 | "rateLimitToken" => "...", 42 | "rateLimitTokenLimit" => "...", 43 | "waitTimeOutInMilliSeconds" => "..." 44 | ]); 45 | ``` 46 | 47 | 48 | ## Resources 49 | This package features Mustache templates designed for use with [openapi generator](https://openapi-generator.tech/). 50 | When you build Selling Partner API OpenAPI models with these templates, they help generate a rich SDK with functionality to invoke Selling Partner APIs built in. The templates are located in *resources/openapi-generator*. 51 | 52 | Dependencies are declared in the composer.json file. 53 | 54 | ## License 55 | OpenAPI Generator templates are subject to the [OpenAPI Generator License](https://github.com/OpenAPITools/openapi-generator/blob/v5.2.1/LICENSE). 56 | 57 | All other work licensed as follows: 58 | 59 | Copyright 2019 Amazon.com, Inc 60 | 61 | Licensed under the Apache License, Version 2.0 (the "License"); 62 | you may not use this library except in compliance with the License. 63 | You may obtain a copy of the License at 64 | 65 | http://www.apache.org/licenses/LICENSE-2.0 66 | 67 | Unless required by applicable law or agreed to in writing, software 68 | distributed under the License is distributed on an "AS IS" BASIS, 69 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 70 | See the License for the specific language governing permissions and 71 | limitations under the License. 72 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/resources/swagger-codegen/templates/IReadableConfiguration.mustache: -------------------------------------------------------------------------------- 1 | {{>partial_header}} 2 | 3 | using System.Collections.Generic; 4 | using Amazon.SellingPartnerAPIAA; 5 | 6 | namespace {{packageName}}.Client 7 | { 8 | /// 9 | /// Represents a readable-only configuration contract. 10 | /// 11 | public interface IReadableConfiguration 12 | { 13 | /// 14 | /// Gets the access token. 15 | /// 16 | /// Access token. 17 | string AccessToken { get; } 18 | 19 | /// 20 | /// Gets the API key. 21 | /// 22 | /// API key. 23 | IDictionary ApiKey { get; } 24 | 25 | /// 26 | /// Gets the API key prefix. 27 | /// 28 | /// API key prefix. 29 | IDictionary ApiKeyPrefix { get; } 30 | 31 | /// 32 | /// Gets the base path. 33 | /// 34 | /// Base path. 35 | string BasePath { get; } 36 | 37 | /// 38 | /// Gets the date time format. 39 | /// 40 | /// Date time foramt. 41 | string DateTimeFormat { get; } 42 | 43 | /// 44 | /// Gets the default header. 45 | /// 46 | /// Default header. 47 | IDictionary DefaultHeader { get; } 48 | 49 | /// 50 | /// Gets the temp folder path. 51 | /// 52 | /// Temp folder path. 53 | string TempFolderPath { get; } 54 | 55 | /// 56 | /// Gets the HTTP connection timeout (in milliseconds) 57 | /// 58 | /// HTTP connection timeout. 59 | int Timeout { get; } 60 | 61 | /// 62 | /// Gets the user agent. 63 | /// 64 | /// User agent. 65 | string UserAgent { get; } 66 | 67 | /// 68 | /// Gets the username. 69 | /// 70 | /// Username. 71 | string Username { get; } 72 | 73 | /// 74 | /// Gets the password. 75 | /// 76 | /// Password. 77 | string Password { get; } 78 | 79 | /// 80 | /// Gets the API key with prefix. 81 | /// 82 | /// API key identifier (authentication scheme). 83 | /// API key with prefix. 84 | string GetApiKeyWithPrefix(string apiKeyIdentifier); 85 | 86 | /// 87 | /// Gets the LWAAuthorizationCredentials for Amazon Selling Partner API Authorization 88 | /// 89 | /// AuthorizationCredentials 90 | LWAAuthorizationCredentials AuthorizationCredentials { get; } 91 | 92 | /// 93 | /// Gets the RateLimitConfigurationOnRequests for Amazon Selling Partner API RateLimit 94 | /// 95 | /// RateLimitConfiguration 96 | RateLimitConfiguration RateLimitConfig { get; } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/RestrictedDataTokenSigner.java: -------------------------------------------------------------------------------- 1 | package com.amazon.SellingPartnerAPIAA; 2 | 3 | import okhttp3.Request; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | public class RestrictedDataTokenSigner { 8 | 9 | /** 10 | * Sign the request with a Restricted Data Token 11 | * 12 | * @param request Request to sign 13 | * @param restrictedDataToken The Restricted Data Token 14 | * @param operationName The operation name in format 'ClassName-operationId' 15 | * @return Signed request 16 | * @throws IllegalArgumentException If RDT is used for a non-restricted operation 17 | */ 18 | public static Request sign(Request request, String restrictedDataToken, String operationName) { 19 | boolean isRestricted = isRestrictedOperation(operationName); 20 | if (!isRestricted) { 21 | throw new IllegalArgumentException( 22 | "Operation '" + operationName + "' does not require a Restricted Data Token (RDT). " + 23 | "Remove the RDT parameter for non-restricted operations." 24 | ); 25 | } 26 | 27 | return request.newBuilder() 28 | .header(LWAAuthorizationSigner.SIGNED_ACCESS_TOKEN_HEADER_NAME, restrictedDataToken) 29 | .build(); 30 | } 31 | 32 | /** 33 | * Check if an operation requires RDT access 34 | * 35 | * @param operationName The operation name in format 'ClassName-operationId' 36 | * @return True if the operation requires RDT, false otherwise 37 | */ 38 | private static boolean isRestrictedOperation(String operationName) { 39 | List restrictedOperations = Arrays.asList( 40 | // Direct Fulfillment Orders API 41 | "VendorOrdersApi-getOrders", 42 | "VendorOrdersApi-getOrder", 43 | 44 | // Direct Fulfillment Shipping API 45 | "VendorShippingLabelsApi-getShippingLabel", 46 | "VendorShippingLabelsApi-getShippingLabels", 47 | "VendorShippingApi-getPackingSlips", 48 | "VendorShippingApi-getPackingSlip", 49 | "VendorShippingLabelsApi-getCustomerInvoice", 50 | "VendorShippingLabelsApi-getCustomerInvoices", 51 | "VendorShippingLabelsApi-createShippingLabels", 52 | 53 | // Easy Ship API v2022-03-23 54 | "EasyShipApi-createScheduledPackageBulk", 55 | 56 | // Orders API 57 | "OrdersV0Api-getOrders", 58 | "OrdersV0Api-getOrder", 59 | "OrdersV0Api-getOrderBuyerInfo", 60 | "OrdersV0Api-getOrderAddress", 61 | "OrdersV0Api-getOrderItems", 62 | "OrdersV0Api-getOrderItemsBuyerInfo", 63 | "OrdersV0Api-getOrderRegulatedInfo", 64 | 65 | // Merchant Fulfillment API 66 | "MerchantFulfillmentApi-getShipment", 67 | "MerchantFulfillmentApi-cancelShipment", 68 | "MerchantFulfillmentApi-createShipment", 69 | 70 | // Shipment Invoicing 71 | "ShipmentInvoiceApi-getShipmentDetails", 72 | 73 | // Reports API 74 | "ReportsApi-getReportDocument" 75 | ); 76 | 77 | return restrictedOperations.contains(operationName); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-php/resources/openapi-generator/templates/ApiException.mustache: -------------------------------------------------------------------------------- 1 | responseHeaders = $responseHeaders; 73 | $this->responseBody = $responseBody; 74 | } 75 | 76 | /** 77 | * Gets the HTTP response header 78 | * 79 | * @return string[][]|null HTTP response header 80 | */ 81 | public function getResponseHeaders(): ?array 82 | { 83 | return $this->responseHeaders; 84 | } 85 | 86 | /** 87 | * Gets the HTTP body of the server response either as Json or string 88 | * 89 | * @return stdClass|string|null HTTP body of the server response either as \stdClass or string 90 | */ 91 | public function getResponseBody(): string|stdClass|null 92 | { 93 | return $this->responseBody; 94 | } 95 | 96 | /** 97 | * Sets the deserialized response object (during deserialization) 98 | * 99 | * @param mixed $obj Deserialized response object 100 | * 101 | * @return void 102 | */ 103 | public function setResponseObject(mixed $obj): void 104 | { 105 | $this->responseObject = $obj; 106 | } 107 | 108 | /** 109 | * Gets the deserialized response object (during deserialization) 110 | * 111 | * @return stdClass|string|null the deserialized response object 112 | */ 113 | public function getResponseObject(): stdClass|string|null 114 | { 115 | return $this->responseObject; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.amazon.sellingpartnerapi 6 | sellingpartner-api-documents-helper-java 7 | 1.0.0 8 | 9 | 10 | com.squareup.okhttp 11 | okhttp 12 | 2.7.5 13 | 14 | 15 | com.squareup.okhttp 16 | logging-interceptor 17 | 2.7.5 18 | 19 | 20 | io.gsonfire 21 | gson-fire 22 | 1.8.0 23 | 24 | 25 | org.apache.directory.studio 26 | org.apache.commons.io 27 | 2.4 28 | 29 | 30 | com.google.guava 31 | guava 32 | 30.0-jre 33 | 34 | 35 | org.threeten 36 | threetenbp 37 | 1.3.5 38 | 39 | 40 | 41 | org.junit.platform 42 | junit-platform-commons 43 | 1.7.0 44 | 45 | 46 | org.junit.jupiter 47 | junit-jupiter-engine 48 | 5.0.0 49 | 50 | 51 | org.junit.jupiter 52 | junit-jupiter-params 53 | 5.3.2 54 | 55 | 56 | org.junit.jupiter 57 | junit-jupiter-migrationsupport 58 | 5.5.1 59 | 60 | 61 | org.mockito 62 | mockito-core 63 | 3.0.0 64 | 65 | 66 | org.mockito 67 | mockito-inline 68 | 3.0.0 69 | 70 | 71 | 72 | 73 | 74 | maven-surefire-plugin 75 | 2.22.2 76 | 77 | 78 | maven-failsafe-plugin 79 | 2.22.2 80 | 81 | 82 | 83 | 84 | 85 | org.apache.maven.plugins 86 | maven-compiler-plugin 87 | 3.7.0 88 | 89 | 90 | 91 | 92 | 93 | 1.8 94 | ${java.version} 95 | ${java.version} 96 | 1.0.0 97 | UTF-8 98 | 99 | 100 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/main/java/com/amazon/spapi/documents/impl/AESCryptoStreamFactory.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents.impl; 2 | 3 | import com.amazon.spapi.documents.CryptoStreamFactory; 4 | import com.amazon.spapi.documents.exception.CryptoException; 5 | import com.google.common.base.Preconditions; 6 | 7 | import javax.crypto.Cipher; 8 | import javax.crypto.CipherInputStream; 9 | import javax.crypto.NoSuchPaddingException; 10 | import javax.crypto.spec.IvParameterSpec; 11 | import javax.crypto.spec.SecretKeySpec; 12 | import java.io.InputStream; 13 | import java.security.InvalidAlgorithmParameterException; 14 | import java.security.InvalidKeyException; 15 | import java.security.Key; 16 | import java.security.NoSuchAlgorithmException; 17 | import java.util.Base64; 18 | 19 | /** 20 | * A crypto stream factory implementing AES encryption. 21 | */ 22 | public class AESCryptoStreamFactory implements CryptoStreamFactory { 23 | private static final String ENCRYPTION_ALGORITHM = "AES/CBC/PKCS5Padding"; 24 | 25 | private final Key key; 26 | private final byte[] initializationVector; 27 | 28 | private AESCryptoStreamFactory(Key key, byte[] initializationVector) { 29 | this.key = key; 30 | this.initializationVector = initializationVector; 31 | } 32 | 33 | private Cipher createInitializedCipher(int mode) throws CryptoException { 34 | try { 35 | Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM); 36 | cipher.init(mode, key, new IvParameterSpec(initializationVector)); 37 | return cipher; 38 | } catch (InvalidKeyException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | 39 | NoSuchPaddingException e) { 40 | throw new CryptoException(e); 41 | } 42 | } 43 | 44 | /** 45 | * {@inheritDoc} 46 | */ 47 | @Override 48 | public InputStream newDecryptStream(InputStream source) throws CryptoException { 49 | return new CipherInputStream(source, createInitializedCipher(Cipher.DECRYPT_MODE)); 50 | } 51 | 52 | /** 53 | * {@inheritDoc} 54 | */ 55 | @Override 56 | public InputStream newEncryptStream(InputStream source) throws CryptoException { 57 | return new CipherInputStream(source, createInitializedCipher(Cipher.ENCRYPT_MODE)); 58 | } 59 | 60 | /** 61 | * Use this to create an instance of an {@link AESCryptoStreamFactory}. 62 | */ 63 | public static class Builder { 64 | private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder(); 65 | private static final String REQUIRED_KEY_ALGORITHM = "AES"; 66 | 67 | private final String key; 68 | private final String initializationVector; 69 | 70 | /** 71 | * Create the builder. 72 | * 73 | * @param key The key 74 | * @param initializationVector The initialization vector 75 | */ 76 | public Builder(String key, String initializationVector) { 77 | Preconditions.checkArgument(key != null, "key is required"); 78 | Preconditions.checkArgument(initializationVector != null, "initializationVector is required"); 79 | 80 | this.key = key; 81 | this.initializationVector = initializationVector; 82 | } 83 | 84 | /** 85 | * Create the crypto stream factory. 86 | * 87 | * @return the crypto stream factory 88 | */ 89 | public AESCryptoStreamFactory build() { 90 | Key convertedKey = new SecretKeySpec(BASE64_DECODER.decode(key), REQUIRED_KEY_ALGORITHM); 91 | byte[] convertedInitializationVector = BASE64_DECODER.decode(initializationVector); 92 | 93 | return new AESCryptoStreamFactory(convertedKey, convertedInitializationVector); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-php/src/authandauth/LWAClient.php: -------------------------------------------------------------------------------- 1 | client = new Client(); 23 | $this->endpoint = $endpoint; 24 | } 25 | 26 | public function setLWAAccessTokenCache(?LWAAccessTokenCache $tokenCache): void 27 | { 28 | $this->lwaAccessTokenCache = $tokenCache; 29 | } 30 | 31 | public function getAccessToken(LWAAccessTokenRequestMeta &$lwaAccessTokenRequestMeta): string 32 | { 33 | if ($this->lwaAccessTokenCache !== null) { 34 | return $this->getAccessTokenFromCache($lwaAccessTokenRequestMeta); 35 | } else { 36 | return $this->getAccessTokenFromEndpoint($lwaAccessTokenRequestMeta); 37 | } 38 | } 39 | 40 | public function getAccessTokenFromCache(LWAAccessTokenRequestMeta &$lwaAccessTokenRequestMeta) 41 | { 42 | $requestBody = json_encode($lwaAccessTokenRequestMeta); 43 | if (!$requestBody) { 44 | throw new RuntimeException("Request body could not be encoded"); 45 | } 46 | $accessTokenCacheData = $this->lwaAccessTokenCache->get($requestBody); 47 | if ($accessTokenCacheData !== null) { 48 | return $accessTokenCacheData; 49 | } else { 50 | return $this->getAccessTokenFromEndpoint($lwaAccessTokenRequestMeta); 51 | } 52 | } 53 | 54 | public function getAccessTokenFromEndpoint(LWAAccessTokenRequestMeta &$lwaAccessTokenRequestMeta) 55 | { 56 | $requestBody = json_encode($lwaAccessTokenRequestMeta); 57 | 58 | if (!$requestBody) { 59 | throw new RuntimeException("Request body could not be encoded"); 60 | } 61 | 62 | $contentHeader = [ 63 | "Content-Type" => "application/json", 64 | ]; 65 | 66 | try { 67 | $lwaRequest = new Request("POST", $this->endpoint, $contentHeader, $requestBody); 68 | 69 | $lwaResponse = $this->client->send($lwaRequest); 70 | $responseJson = json_decode($lwaResponse->getBody(), true); 71 | 72 | if (!$responseJson["access_token"] || !$responseJson["expires_in"]) { 73 | throw new RuntimeException("Response did not have required body"); 74 | } 75 | 76 | $accessToken = $responseJson["access_token"]; 77 | 78 | if ($this->lwaAccessTokenCache !== null) { 79 | $timeToTokenExpire = (float)$responseJson["expires_in"]; 80 | $this->lwaAccessTokenCache->set($requestBody, $accessToken, $timeToTokenExpire); 81 | } 82 | } catch (BadResponseException $e) { 83 | //Catches 400 and 500 level error codes 84 | throw new RuntimeException("Unsuccessful LWA token exchange", $e->getCode()); 85 | } catch (Exception $e) { 86 | throw new RuntimeException("Error getting LWA Access Token", $e->getCode()); 87 | } catch (GuzzleException $e) { 88 | throw new RuntimeException("Error getting LWA Access Token", $e->getCode()); 89 | } 90 | 91 | return $accessToken; 92 | } 93 | 94 | public function setClient(Client $client): void 95 | { 96 | $this->client = $client; 97 | } 98 | 99 | public function getEndpoint(): string 100 | { 101 | return $this->endpoint; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/src/Amazon.SellingPartnerAPIAA/LWAClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using RestSharp; 7 | 8 | namespace Amazon.SellingPartnerAPIAA 9 | { 10 | public class LWAClient 11 | { 12 | public const string AccessTokenKey = "access_token"; 13 | public const string ErrorCodeKey = "error"; 14 | public const string ErrorDescKey = "error_description"; 15 | public const string JsonMediaType = "application/json; charset=utf-8"; 16 | 17 | public IRestClient RestClient { get; set; } 18 | public LWAAccessTokenRequestMetaBuilder LWAAccessTokenRequestMetaBuilder { get; set; } 19 | public LWAAuthorizationCredentials LWAAuthorizationCredentials { get; private set; } 20 | 21 | 22 | public LWAClient(LWAAuthorizationCredentials lwaAuthorizationCredentials) 23 | { 24 | LWAAuthorizationCredentials = lwaAuthorizationCredentials; 25 | LWAAccessTokenRequestMetaBuilder = new LWAAccessTokenRequestMetaBuilder(); 26 | RestClient = new RestClient(LWAAuthorizationCredentials.Endpoint.GetLeftPart(System.UriPartial.Authority)); 27 | } 28 | 29 | /// 30 | /// Retrieves access token from LWA 31 | /// 32 | /// LWA AccessTokenRequest metadata 33 | /// LWA Access Token 34 | public virtual string GetAccessToken() 35 | { 36 | LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta = LWAAccessTokenRequestMetaBuilder.Build(LWAAuthorizationCredentials); 37 | var accessTokenRequest = new RestRequest(LWAAuthorizationCredentials.Endpoint.AbsolutePath, Method.POST); 38 | 39 | string jsonRequestBody = JsonConvert.SerializeObject(lwaAccessTokenRequestMeta); 40 | 41 | accessTokenRequest.AddParameter(JsonMediaType, jsonRequestBody, ParameterType.RequestBody); 42 | 43 | string accessToken; 44 | 45 | LWAExceptionErrorCode errorCode; 46 | 47 | try 48 | { 49 | var response = RestClient.Execute(accessTokenRequest); 50 | JObject responseJson = !String.IsNullOrEmpty(response.Content) ? JObject.Parse(response.Content) : null; 51 | 52 | if (!IsSuccessful(response)) 53 | { 54 | if (responseJson != null) 55 | { 56 | // If error code is present check error code if its one of the defined LWAExceptionErrorCode values 57 | var parsedErrorCode = responseJson.ContainsKey(ErrorCodeKey) ? Enum.TryParse(responseJson.GetValue(ErrorCodeKey).ToString(), out errorCode) : false; 58 | 59 | if (parsedErrorCode) // throw error code and description if matches defined values 60 | { 61 | throw new LWAException(responseJson.GetValue(ErrorCodeKey).ToString(), responseJson.GetValue(ErrorDescKey).ToString(), "Error getting LWA Access Token"); 62 | } 63 | } //else throw general LWA exception 64 | throw new LWAException(LWAExceptionErrorCode.other.ToString(), "Other LWA Exception", "Error getting LWA Access Token"); 65 | } 66 | accessToken = responseJson.GetValue(AccessTokenKey).ToString(); 67 | } 68 | catch (LWAException e) 69 | { 70 | throw new LWAException(e.getErrorCode(), e.getErrorMessage(), e.Message); 71 | } 72 | catch (Exception e) 73 | { 74 | throw new SystemException("Error getting LWA Access Token", e); 75 | } 76 | 77 | return accessToken; 78 | } 79 | 80 | private bool IsSuccessful(IRestResponse response) 81 | { 82 | int statusCode = (int)response.StatusCode; 83 | return statusCode >= 200 && statusCode <= 299 && response.ResponseStatus == ResponseStatus.Completed; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /schemas/notifications/FBAOutboundShipmentStatusNotification.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema", 3 | "$id": "http://example.com/example.json", 4 | "type": "object", 5 | "description": "The root schema comprises the entire JSON document.", 6 | "examples": [ 7 | { 8 | "NotificationVersion": "1.0", 9 | "NotificationType": "FBA_OUTBOUND_SHIPMENT_STATUS", 10 | "PayloadVersion": "1.0", 11 | "EventTime": "2020-01-11T00:09:53.109Z", 12 | "Payload": { 13 | "FBAOutboundShipmentStatusNotification": { 14 | "SellerId": "merchantId", 15 | "AmazonOrderId": "113-2646096-4474645", 16 | "AmazonShipmentId": "DrLqQwqvb", 17 | "ShipmentStatus": "Created" 18 | } 19 | }, 20 | "NotificationMetadata": { 21 | "ApplicationId": "appId", 22 | "SubscriptionId": "subId", 23 | "PublishTime": "2020-01-11T00:02:50.501Z", 24 | "NotificationId": "requestId" 25 | } 26 | } 27 | ], 28 | "required": [ 29 | "NotificationVersion", 30 | "NotificationType", 31 | "PayloadVersion", 32 | "EventTime", 33 | "NotificationMetadata", 34 | "Payload" 35 | ], 36 | "additionalProperties": true, 37 | "properties": { 38 | "NotificationVersion": { 39 | "$id": "#/properties/NotificationVersion", 40 | "type": "string" 41 | }, 42 | "NotificationType": { 43 | "$id": "#/properties/NotificationType", 44 | "type": "string" 45 | }, 46 | "PayloadVersion": { 47 | "$id": "#/properties/PayloadVersion", 48 | "type": "string" 49 | }, 50 | "EventTime": { 51 | "$id": "#/properties/EventTime", 52 | "type": "string" 53 | }, 54 | "NotificationMetadata": { 55 | "$id": "#/properties/NotificationMetadata", 56 | "type": "object", 57 | "required": [ 58 | "ApplicationId", 59 | "SubscriptionId", 60 | "PublishTime", 61 | "NotificationId" 62 | ], 63 | "properties": { 64 | "ApplicationId": { 65 | "$id": "#/properties/NotificationMetadata/properties/ApplicationId", 66 | "type": "string" 67 | }, 68 | "SubscriptionId": { 69 | "$id": "#/properties/NotificationMetadata/properties/SubscriptionId", 70 | "type": "string" 71 | }, 72 | "PublishTime": { 73 | "$id": "#/properties/NotificationMetadata/properties/PublishTime", 74 | "type": "string" 75 | }, 76 | "NotificationId": { 77 | "$id": "#/properties/NotificationMetadata/properties/NotificationId", 78 | "type": "string" 79 | } 80 | } 81 | }, 82 | "Payload": { 83 | "$id": "#/properties/Payload", 84 | "type": "object", 85 | "required": [ 86 | "FBAOutboundShipmentStatusNotification" 87 | ], 88 | "properties": { 89 | "FBAOutboundShipmentStatusNotification": { 90 | "$id": "#/properties/Payload/properties/FBAOutboundShipmentStatusNotification", 91 | "type": "object", 92 | "required": [ 93 | "SellerId", 94 | "AmazonOrderId", 95 | "AmazonShipmentId", 96 | "ShipmentStatus" 97 | ], 98 | "properties": { 99 | "SellerId": { 100 | "$id": "#/properties/Payload/properties/FBAOutboundShipmentStatusNotification/properties/SellerId", 101 | "type": "string" 102 | }, 103 | "AmazonOrderId": { 104 | "$id": "#/properties/Payload/properties/FBAOutboundShipmentStatusNotification/properties/AmazonOrderId", 105 | "type": "string" 106 | }, 107 | "AmazonShipmentId": { 108 | "$id": "#/properties/Payload/properties/FBAOutboundShipmentStatusNotification/properties/AmazonShipmentId", 109 | "type": "string" 110 | }, 111 | "ShipmentStatus": { 112 | "$id": "#/properties/Payload/properties/FBAOutboundShipmentStatusNotification/properties/ShipmentStatus", 113 | "type": "string" 114 | } 115 | } 116 | } 117 | } 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/src/com/amazon/SellingPartnerAPIAA/LWAClient.java: -------------------------------------------------------------------------------- 1 | package com.amazon.SellingPartnerAPIAA; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonObject; 5 | import com.google.gson.JsonParser; 6 | import lombok.AccessLevel; 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | import okhttp3.MediaType; 10 | import okhttp3.OkHttpClient; 11 | import okhttp3.Request; 12 | import okhttp3.RequestBody; 13 | import okhttp3.Response; 14 | import okhttp3.ResponseBody; 15 | import org.apache.commons.lang3.EnumUtils; 16 | 17 | class LWAClient { 18 | private static final String ACCESS_TOKEN_KEY = "access_token"; 19 | private static final String ACCESS_TOKEN_EXPIRES_IN = "expires_in"; 20 | private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8"); 21 | @Getter 22 | private String endpoint; 23 | 24 | @Setter(AccessLevel.PACKAGE) 25 | private OkHttpClient okHttpClient; 26 | private LWAAccessTokenCache lwaAccessTokenCache; 27 | 28 | /** 29 | * Sets cache to store access token until token is expired 30 | */ 31 | public void setLWAAccessTokenCache(LWAAccessTokenCache tokenCache) { 32 | this.lwaAccessTokenCache = tokenCache; 33 | } 34 | 35 | LWAClient(String endpoint) { 36 | okHttpClient = new OkHttpClient(); 37 | this.endpoint = endpoint; 38 | } 39 | 40 | String getAccessToken(LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta) throws LWAException { 41 | if (lwaAccessTokenCache != null) { 42 | return getAccessTokenFromCache(lwaAccessTokenRequestMeta); 43 | } else { 44 | return getAccessTokenFromEndpoint(lwaAccessTokenRequestMeta); 45 | } 46 | } 47 | 48 | String getAccessTokenFromCache(LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta) throws LWAException { 49 | String accessTokenCacheData = (String) lwaAccessTokenCache.get(lwaAccessTokenRequestMeta); 50 | if (accessTokenCacheData != null) { 51 | return accessTokenCacheData; 52 | } else { 53 | return getAccessTokenFromEndpoint(lwaAccessTokenRequestMeta); 54 | } 55 | } 56 | 57 | String getAccessTokenFromEndpoint(LWAAccessTokenRequestMeta lwaAccessTokenRequestMeta) throws LWAException { 58 | RequestBody requestBody = RequestBody.create(JSON_MEDIA_TYPE, new Gson().toJson(lwaAccessTokenRequestMeta)); 59 | Request accessTokenRequest = new Request.Builder().url(endpoint).post(requestBody).build(); 60 | LWAExceptionErrorCode lwaErrorCode = null; 61 | String accessToken; 62 | try { 63 | Response response = okHttpClient.newCall(accessTokenRequest).execute(); 64 | ResponseBody body = response.body(); 65 | if (body == null) throw new LWAException(LWAExceptionErrorCode.other.toString(), 66 | "Error getting LWA Token", "Response body missing"); 67 | JsonObject responseJson = JsonParser.parseString(body.string()).getAsJsonObject(); 68 | if (!response.isSuccessful()) { 69 | // Check if response has element error and is a known LWA error code 70 | if (responseJson.has("error") && 71 | EnumUtils.isValidEnum(LWAExceptionErrorCode.class, responseJson.get("error").getAsString())) { 72 | throw new LWAException(responseJson.get("error").getAsString(), 73 | responseJson.get("error_description").getAsString(), "Error getting LWA Token"); 74 | } else { // else throw other LWA error 75 | throw new LWAException(LWAExceptionErrorCode.other.toString(), "Other LWA Exception", 76 | "Error getting LWA Token"); 77 | } 78 | } 79 | accessToken = responseJson.get(ACCESS_TOKEN_KEY).getAsString(); 80 | if (lwaAccessTokenCache != null) { 81 | long timeToTokenexpiry = responseJson.get(ACCESS_TOKEN_EXPIRES_IN).getAsLong(); 82 | lwaAccessTokenCache.put(lwaAccessTokenRequestMeta, accessToken, timeToTokenexpiry); 83 | } 84 | } catch (LWAException e) { // throw LWA exception 85 | throw new LWAException(e.getErrorCode(), e.getErrorMessage(), e.getMessage()); 86 | } catch (Exception e) { // throw other runtime exceptions 87 | throw new RuntimeException("Error getting LWA Token"); 88 | } 89 | return accessToken; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-java/README.md: -------------------------------------------------------------------------------- 1 | # Selling Partner API Authentication/Authorization Library 2 | This library provides helper classes for use when signing HTTP requests for Amazon Selling Partner APIs. It is intended for use 3 | with the Selling Partner API Client Libraries generated by [swagger codegen](https://swagger.io/tools/swagger-codegen/) 4 | using the OkHttp library. It can also be integrated into custom projects. 5 | 6 | ## LWAAuthorizationSigner 7 | Obtains and signs a request with an access token from LWA (Login with Amazon) for the specified endpoint using the provided LWA credentials. 8 | 9 | *Example* 10 | ``` 11 | com.squareup.okhttp.Request request = new Request.Builder() 12 | .url(...) 13 | ... 14 | .build(); 15 | 16 | // Seller APIs 17 | 18 | LWAAuthorizationCredentials lwaAuthorizationCredentials = LWAAuthorizationCredentials.builder() 19 | .clientId("...") 20 | .clientSecret("...") 21 | .refreshToken("...") 22 | .endpoint("...") 23 | .build(); 24 | 25 | /* Sellerless APIs 26 | The Selling Partner API scopes can be retrieved from the ScopeConstants class and passed as argument(s) to either the withScope(String scope) or withScopes(String... scopes) method during lwaAuthorizationCredentials object instantiation. */ 27 | 28 | import static com.amazon.SellingPartnerAPIAA.ScopeConstants.SCOPE_NOTIFICATIONS_API; 29 | 30 | LWAAuthorizationCredentials lwaAuthorizationCredentials = LWAAuthorizationCredentials.builder() 31 | .clientId("...") 32 | .clientSecret("...") 33 | .withScopes("...") 34 | .endpoint("...") 35 | .build(); 36 | 37 | com.squareup.okhttp.Request signedRequest = new LWAAuthorizationSigner(lwaAuthorizationCredentials) 38 | .sign(request); 39 | ``` 40 | 41 | ## LWAAccessTokenCache 42 | Interface to implement cache for access token that is returned in LWAClient and reuse the access token until time to live. 43 | 44 | ## RateLimitConfiguration 45 | Interface to set and get rateLimit configurations that are used with RateLimiter. RateLimiter is used on client side to restrict the rate at which requests are made. RateLimiter Configuration takes Permit, rate which requests are made and TimeOut 46 | 47 | *Example* 48 | ``` 49 | com.squareup.okhttp.Request request = new Request.Builder() 50 | .url(...) 51 | ... 52 | .build(); 53 | 54 | RateLimitConfiguration rateLimitOption = RateLimitConfigurationOnRequests.builder() 55 | .rateLimitPermit(...) 56 | .waitTimeOutInMilliSeconds(...) 57 | .build(); 58 | ``` 59 | ## Exception 60 | This package returns a custom LWAException when there is an error returned during LWA authorization. LWAException provides additional details like errorCode and errorDescription to help fix the issue. 61 | 62 | *Example* 63 | ``` 64 | catch (LWAException e) { 65 | System.err.println("LWA Exception when calling Selling partner API"); 66 | System.err.println(e.getErrorCode()); 67 | System.err.println(e.getErrorMessage()); 68 | e.printStackTrace(); 69 | } 70 | ``` 71 | 72 | ## Version 73 | Selling Partner API Authentication/Authorization Library version 3.0. 74 | 75 | ## Resources 76 | This package features Mustache templates designed for use with [swagger codegen](https://swagger.io/tools/swagger-codegen/). 77 | When you build Selling Partner API Swagger models with these templates, they help generate a rich SDK with functionality to invoke Selling Partner APIs built in. The templates are located in *resources/swagger-codegen*. 78 | 79 | ## Building 80 | This library can be built using Maven by running this command in the package root: 81 | ``` 82 | mvn clean package 83 | ``` 84 | Dependencies are declared in the pom.xml file. 85 | 86 | ## License 87 | Swagger Codegen templates are subject to the [Swagger Codegen License](https://github.com/swagger-api/swagger-codegen#license). 88 | 89 | All other work licensed as follows: 90 | 91 | Copyright 2019 Amazon.com, Inc 92 | 93 | Licensed under the Apache License, Version 2.0 (the "License"); 94 | you may not use this library except in compliance with the License. 95 | You may obtain a copy of the License at 96 | 97 | http://www.apache.org/licenses/LICENSE-2.0 98 | 99 | Unless required by applicable law or agreed to in writing, software 100 | distributed under the License is distributed on an "AS IS" BASIS, 101 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 102 | See the License for the specific language governing permissions and 103 | limitations under the License. 104 | -------------------------------------------------------------------------------- /schemas/reports/vendorRealTimeTrafficReport.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema", 3 | "type": "object", 4 | "description": "This report shares data on the customer traffic to the detail pages of the vendor's items with an hourly granularity. Requests can span multiple date range periods. For example, if the customer specified dataStartTime and dataEndTime span three hours, the report would contain data for each complete hour within the time span.", 5 | "examples": [ 6 | { 7 | "reportSpecification": { 8 | "reportType": "GET_VENDOR_REAL_TIME_TRAFFIC_REPORT", 9 | "dataStartTime": "2022-04-20T20:00:00Z", 10 | "dataEndTime": "2022-04-20T21:00:00Z", 11 | "marketplaceIds": [ 12 | "ATVPDKIKX0DER" 13 | ] 14 | }, 15 | "reportData": [ 16 | { 17 | "startTime": "2022-04-20T20:00:00Z", 18 | "endTime": "2022-04-20T21:00:00Z", 19 | "asin": "B00ABCD123", 20 | "glanceViews": 75 21 | } 22 | ] 23 | } 24 | ], 25 | "required": [ 26 | "reportSpecification", 27 | "reportData" 28 | ], 29 | "properties": { 30 | "reportSpecification": { 31 | "type": "object", 32 | "description": "Summarizes the original report request.", 33 | "required": [ 34 | "reportType", 35 | "dataStartTime", 36 | "dataEndTime", 37 | "marketplaceIds" 38 | ], 39 | "properties": { 40 | "reportType": { 41 | "type": "string", 42 | "description": "The type of the report.", 43 | "enum": [ 44 | "GET_VENDOR_REAL_TIME_TRAFFIC_REPORT" 45 | ] 46 | }, 47 | "dataStartTime": { 48 | "type": "string", 49 | "format": "date-time", 50 | "description": "The start of a date-time range in UTC used to determine hours to report on. Output will include all full hours that fall within the range.", 51 | "examples": [ 52 | "2022-04-20T20:00:00Z", 53 | "2022-04-20T20:59:59Z" 54 | ] 55 | }, 56 | "dataEndTime": { 57 | "type": "string", 58 | "format": "date-time", 59 | "description": "The end of a date-time range in UTC used to determine hours to report on. Output will include all full hours that fall within the range. End time should be at least 60 minutes earlier than the time of the request.", 60 | "examples": [ 61 | "2022-04-20T20:00:00Z", 62 | "2022-04-20T20:10:00Z" 63 | ] 64 | }, 65 | "marketplaceIds": { 66 | "type": "array", 67 | "description": "Marketplace IDs as specified in the report request.", 68 | "examples": [ 69 | [ 70 | "ATVPDKIKX0DER" 71 | ] 72 | ], 73 | "items": { 74 | "type": "string" 75 | } 76 | } 77 | } 78 | }, 79 | "reportData": { 80 | "type": "array", 81 | "description": "List of hour and ASIN combinations.", 82 | "items": { 83 | "$ref": "#/definitions/ReportData" 84 | } 85 | } 86 | }, 87 | "definitions": { 88 | "ReportData": { 89 | "type": "object", 90 | "description": "Contains details about hour and ASIN combinations for the specified time range.", 91 | "required": [ 92 | "startTime", 93 | "endTime", 94 | "asin", 95 | "glanceViews" 96 | ], 97 | "properties": { 98 | "startTime": { 99 | "type": "string", 100 | "format": "date-time", 101 | "description": "The start of a date-time range in UTC representing the beginning of the hour for this object.", 102 | "examples": [ 103 | "2022-04-20T20:00:00Z", 104 | "2022-04-20T08:00:00Z" 105 | ] 106 | }, 107 | "endTime": { 108 | "type": "string", 109 | "format": "date-time", 110 | "description": "The end of a date-time range in UTC representing the end of the hour for this object.", 111 | "examples": [ 112 | "2022-04-20T21:00:00Z", 113 | "2022-04-20T09:00:00Z" 114 | ] 115 | }, 116 | "asin": { 117 | "description": "The Amazon Standard Identification Number (ASIN).", 118 | "type": "string", 119 | "examples": [ 120 | "B123456789" 121 | ] 122 | }, 123 | "glanceViews": { 124 | "type": "integer", 125 | "description": "The number of customer views of the product detail page for this ASIN.", 126 | "minimum": 0, 127 | "examples": [ 128 | 75 129 | ] 130 | } 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-csharp/README.md: -------------------------------------------------------------------------------- 1 | # Selling Partner API Authentication/Authorization Library 2 | This library provides helper classes for use when signing HTTP requests for Amazon Selling Partner APIs. It is intended for use 3 | with the Selling Partner API Client Libraries generated by [swagger codegen](https://swagger.io/tools/swagger-codegen/) 4 | using the RestSharp library. It can also be integrated into custom projects. 5 | 6 | ## LWAAuthorizationSigner 7 | Obtains and signs a request with an access token from LWA (Login with Amazon) for the specified endpoint using the provided LWA credentials. 8 | 9 | *Example* 10 | ``` 11 | using RestSharp; 12 | using Amazon.SellingPartnerAPIAA; 13 | 14 | string resource = "/my/api/path"; 15 | RestClient restClient = new RestClient("https://..."); 16 | IRestRequest restRequest = new RestRequest(resource, Method.GET); 17 | 18 | // Seller APIs 19 | LWAAuthorizationCredentials lwaAuthorizationCredentials = new LWAAuthorizationCredentials 20 | { 21 | ClientId = "...", 22 | ClientSecret = "", 23 | RefreshToken = "", 24 | Endpoint = new Uri("...") 25 | }; 26 | 27 | /* Sellerless APIs 28 | The Selling Partner API scopes can be retrieved from the ScopeConstants class and used to specify a list of scopes of an LWAAuthorizationCredentials instance. */ 29 | LWAAuthorizationCredentials lwaAuthorizationCredentials = new LWAAuthorizationCredentials 30 | { 31 | ClientId = "...", 32 | ClientSecret = "", 33 | Scopes = new List() { ScopeConstants.ScopeNotificationsAPI, ScopeConstants.ScopeMigrationAPI } 34 | Endpoint = new Uri("...") 35 | }; 36 | 37 | restRequest = new LWAAuthorizationSigner(lwaAuthorizationCredentials).Sign(restRequest); 38 | ``` 39 | Note the IRestRequest reference is treated as **mutable** when signed. 40 | 41 | ## RateLimitConfiguration 42 | Interface to set and get rateLimit configurations that are used with RateLimiter. RateLimiter is used on client side to restrict the rate at which requests are made. RateLimiter Configuration takes Permit, rate which requests are made and TimeOut 43 | 44 | *Example* 45 | ``` 46 | RateLimitConfiguration rateLimitConfig = new RateLimitConfigurationOnRequests 47 | { 48 | RateLimitPermit = .., 49 | WaitTimeOutInMilliSeconds = ... 50 | }; 51 | 52 | ``` 53 | 54 | ## Exception 55 | This package returns a custom LWAException when there is an error returned during LWA authorization. LWAException provides additional details like errorCode and errorDescription to help fix the issue. 56 | 57 | *Example* 58 | ``` 59 | catch (LWAException e) 60 | { 61 | Console.WriteLine("LWA Exception when calling Selling partner API"); 62 | Console.WriteLine(e.getErrorCode()); 63 | Console.WriteLine(e.getErrorMessage()); 64 | Console.WriteLine(e.Message); 65 | } 66 | ``` 67 | 68 | ## Version 69 | Selling Partner API Authentication/Authorization Library version 2.0. 70 | 71 | ## Resources 72 | This package features Mustache templates designed for use with [swagger codegen](https://swagger.io/tools/swagger-codegen/). 73 | When you build Selling Partner API Swagger models with these templates, they help generate a rich SDK with functionality to invoke Selling Partner APIs built in. The templates are located in *resources/swagger-codegen*. 74 | 75 | ## Building 76 | This package is built as a .NET Standard Library via a Visual Studio Solution with implementation and test projects. The Visual Studio Community Edition can be obtained for free from Microsoft and used to build the solution and generate a .dll assembly which can be imported into other projects. 77 | 78 | ## Dependencies 79 | All dependencies can be installed via NuGet 80 | - RestSharp - 106.12.0 81 | - Newtonsoft.Json 12.0.3 82 | - NETStandard.Library 2.0.3 (platform-specific implementation requirements are documented on the [Microsoft .NET Guide](https://docs.microsoft.com/en-us/dotnet/standard/net-standard)) 83 | 84 | ## License 85 | Swagger Codegen templates are subject to the [Swagger Codegen License](https://github.com/swagger-api/swagger-codegen#license). 86 | 87 | All other work licensed as follows: 88 | 89 | Copyright 2020 Amazon.com, Inc 90 | 91 | Licensed under the Apache License, Version 2.0 (the "License"); 92 | you may not use this library except in compliance with the License. 93 | You may obtain a copy of the License at 94 | 95 | http://www.apache.org/licenses/LICENSE-2.0 96 | 97 | Unless required by applicable law or agreed to in writing, software 98 | distributed under the License is distributed on an "AS IS" BASIS, 99 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 100 | See the License for the specific language governing permissions and 101 | limitations under the License. -------------------------------------------------------------------------------- /clients/sellingpartner-api-aa-python/auth/LwaRequest.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import logging 4 | import sys 5 | 6 | #Update path 7 | sys.path.append("path_to_folder/SellingPartnerAPIAuthAndAuthPython") 8 | 9 | from auth.LwaException import LwaException 10 | from auth.LwaExceptionErrorCode import LwaExceptionErrorCode 11 | 12 | logging.basicConfig(level=logging.INFO) 13 | logger = logging.getLogger(__name__) 14 | 15 | class AccessTokenCache: 16 | def __init__(self, max_retries=3): 17 | self.token_info = None 18 | self.max_retries = max_retries 19 | 20 | def get_lwa_access_token(self, client_id, client_secret, refresh_token=None, grant_type="refresh_token", scope=None): 21 | if self.token_info and time.time() < self.token_info["expires_at"]: 22 | return self.token_info["access_token"] 23 | return self.request_new_token(client_id, client_secret, refresh_token, grant_type, scope) 24 | 25 | 26 | def request_new_token(self, client_id, client_secret, refresh_token, grant_type, scope): 27 | self.validate_token_request(grant_type, refresh_token, scope) 28 | data = self.prepare_token_request_data(client_id, client_secret, refresh_token, grant_type, scope) 29 | 30 | retries = 0 31 | while retries <= self.max_retries: 32 | try: 33 | response = requests.post("https://api.amazon.com/auth/o2/token", data=data) 34 | response.raise_for_status() 35 | token_response = response.json() 36 | token_response["expires_at"] = time.time() + token_response.get("expires_in", 1800) - 30 37 | self.token_info = token_response 38 | return token_response["access_token"] 39 | except requests.RequestException as e: 40 | if retries < self.max_retries: 41 | retries += 1 42 | time.sleep(2 ** retries) 43 | continue 44 | error_code = self.map_http_status_to_lwa_exception_code(e.response.status_code if e.response else None) 45 | error_message = f"Token request failed with status code {e.response.status_code}: {e.response.text}" if e.response else "Token request failed." 46 | logger.error(error_message) 47 | raise LwaException(error_code, error_message) from e 48 | 49 | 50 | def validate_token_request(self, grant_type, refresh_token, scope): 51 | if grant_type == "refresh_token" and not refresh_token: 52 | raise LwaException(LwaExceptionErrorCode.INVALID_REQUEST.value, "Refresh token must be provided for grant_type 'refresh_token'") 53 | if grant_type == "client_credentials" and not scope: 54 | raise LwaException(LwaExceptionErrorCode.INVALID_SCOPE.value, "Scope must be provided for grant_type 'client_credentials'") 55 | 56 | 57 | def prepare_token_request_data(self, client_id, client_secret, refresh_token, grant_type, scope): 58 | data = { 59 | "client_id": client_id, 60 | "client_secret": client_secret, 61 | "grant_type": grant_type 62 | } 63 | if grant_type == "refresh_token": 64 | if not refresh_token: 65 | raise LwaException(LwaExceptionErrorCode.INVALID_REQUEST.value, "Refresh token must be provided for grant_type 'refresh_token'") 66 | data["refresh_token"] = refresh_token 67 | elif grant_type == "client_credentials": 68 | if not scope: 69 | raise LwaException(LwaExceptionErrorCode.INVALID_SCOPE.value, "Scope must be provided for grant_type 'client_credentials'") 70 | data["scope"] = scope 71 | return data 72 | 73 | def is_retriable(self, error_code): 74 | retriable_codes = [ 75 | LwaExceptionErrorCode.SERVER_ERROR.value, 76 | LwaExceptionErrorCode.TEMPORARILY_UNAVAILABLE.value 77 | ] 78 | return error_code in retriable_codes 79 | 80 | def format_error_message(self, e): 81 | return f"Token request failed with status code {e.response.status_code}: {e.response.text}" if e.response else f"Token request failed: {e}" 82 | 83 | def map_http_status_to_lwa_exception_code(self, status_code): 84 | if status_code is None: 85 | return LWAExceptionErrorCode.SERVER_ERROR.value 86 | if status_code == 400: 87 | return LWAExceptionErrorCode.INVALID_REQUEST.value 88 | if status_code == 401: 89 | return LWAExceptionErrorCode.UNAUTHORIZED_CLIENT.value 90 | if status_code == 403: 91 | return LWAExceptionErrorCode.ACCESS_DENIED.value 92 | if status_code == 500: 93 | return LWAExceptionErrorCode.SERVER_ERROR.value 94 | if status_code == 503: 95 | return LWAExceptionErrorCode.TEMPORARILY_UNAVAILABLE.value 96 | return LWAExceptionErrorCode.OTHER.value 97 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/main/java/com/amazon/spapi/documents/impl/OkHttpTransferClient.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents.impl; 2 | 3 | import com.amazon.spapi.documents.HttpTransferClient; 4 | import com.amazon.spapi.documents.exception.HttpResponseException; 5 | import com.squareup.okhttp.MediaType; 6 | import com.squareup.okhttp.OkHttpClient; 7 | import com.squareup.okhttp.Request; 8 | import com.squareup.okhttp.RequestBody; 9 | import com.squareup.okhttp.Response; 10 | import org.apache.commons.io.FileUtils; 11 | import org.apache.commons.io.IOUtils; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.io.Reader; 16 | 17 | /** 18 | * HTTP transfer client utilizing OkHttp. 19 | */ 20 | public class OkHttpTransferClient implements HttpTransferClient { 21 | private static final String CONTENT_TYPE_HEADER = "Content-Type"; 22 | 23 | private final OkHttpClient client; 24 | private final int maxErrorBodyLen; 25 | 26 | private OkHttpTransferClient(OkHttpClient client, int maxErrorBodyLen) { 27 | this.client = client; 28 | this.maxErrorBodyLen = maxErrorBodyLen; 29 | } 30 | 31 | private HttpResponseException createResponseException(Response response) { 32 | String body = ""; 33 | if (maxErrorBodyLen > 0) { 34 | try (Reader bodyReader = response.body().charStream()) { 35 | char[] buf = new char[maxErrorBodyLen]; 36 | int charsRead = IOUtils.read(bodyReader, buf); 37 | if (charsRead > 0) { 38 | body = new String(buf, 0, charsRead); 39 | } 40 | } catch (Exception e) { 41 | // Ignore any failures reading the body so that the original failure is not lost 42 | } 43 | } 44 | 45 | return new HttpResponseException(response.message(), body, response.code()); 46 | } 47 | 48 | /** 49 | * {@inheritDoc} 50 | */ 51 | @Override 52 | public String download(String url, File destination) throws HttpResponseException, IOException { 53 | Request request = new Request.Builder() 54 | .url(url) 55 | .get() 56 | .build(); 57 | 58 | Response response = client.newCall(request).execute(); 59 | try { 60 | if (!response.isSuccessful()) { 61 | throw createResponseException(response); 62 | } 63 | 64 | FileUtils.copyInputStreamToFile(response.body().byteStream(), destination); 65 | } finally { 66 | IOUtils.closeQuietly(response.body()); 67 | } 68 | 69 | return response.header(CONTENT_TYPE_HEADER); 70 | } 71 | 72 | /** 73 | * {@inheritDoc} 74 | */ 75 | @Override 76 | public void upload(String url, String contentType, File source) throws HttpResponseException, IOException { 77 | Request request = new Request.Builder() 78 | .url(url) 79 | .put(RequestBody.create(MediaType.parse(contentType), source)) 80 | .build(); 81 | 82 | Response response = client.newCall(request).execute(); 83 | try { 84 | if (!response.isSuccessful()) { 85 | throw createResponseException(response); 86 | } 87 | } finally { 88 | IOUtils.closeQuietly(response.body()); 89 | } 90 | } 91 | 92 | /** 93 | * Use this to create an instance of an {@link OkHttpTransferClient}. 94 | */ 95 | public static class Builder { 96 | private OkHttpClient client = null; 97 | private int maxErrorBodyLen = 4096; 98 | 99 | /** 100 | * The {@link OkHttpClient}. If not specified, a new instance of {@link OkHttpClient} will be created using the 101 | * no-arg constructor. 102 | * 103 | * @param client The client 104 | * @return this 105 | */ 106 | public Builder withClient(OkHttpClient client) { 107 | this.client = client; 108 | return this; 109 | } 110 | 111 | /** 112 | * When an HTTP response indicates failure, the maximum number of characters for 113 | * {@link HttpResponseException#getBody()}. Default 4096. 114 | * 115 | * @param maxErrorBodyLen The maximum number of characters to extract from the response body on failure 116 | * @return this 117 | */ 118 | public Builder withMaxErrorBodyLen(int maxErrorBodyLen) { 119 | this.maxErrorBodyLen = maxErrorBodyLen; 120 | return this; 121 | } 122 | 123 | /** 124 | * Create the client. 125 | * 126 | * @return The client 127 | */ 128 | public OkHttpTransferClient build() { 129 | if (client == null) { 130 | client = new OkHttpClient(); 131 | } 132 | 133 | return new OkHttpTransferClient(client, maxErrorBodyLen); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /clients/sellingpartner-api-documents-helper-java/src/main/java/com/amazon/spapi/documents/DownloadHelper.java: -------------------------------------------------------------------------------- 1 | package com.amazon.spapi.documents; 2 | 3 | import com.amazon.spapi.documents.exception.HttpResponseException; 4 | import com.amazon.spapi.documents.impl.OkHttpTransferClient; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | 9 | /** 10 | * Helper for downloading encrypted documents. 11 | */ 12 | public class DownloadHelper { 13 | private final HttpTransferClient httpTransferClient; 14 | private final String tmpFilePrefix; 15 | private final String tmpFileSuffix; 16 | private final File tmpFileDirectory; 17 | 18 | private DownloadHelper(HttpTransferClient httpTransferClient, String tmpFilePrefix, String tmpFileSuffix, 19 | File tmpFileDirectory) { 20 | this.httpTransferClient = httpTransferClient; 21 | this.tmpFilePrefix = tmpFilePrefix; 22 | this.tmpFileSuffix = tmpFileSuffix; 23 | this.tmpFileDirectory = tmpFileDirectory; 24 | } 25 | 26 | /** 27 | * Download the specified document's encrypted contents to a temporary file on disk. It is the responsibility of the 28 | * caller to call close on the returned {@link AutoCloseable} {@link DownloadBundle}. 29 | * 30 | * Common reasons for receiving a 403 response include: 31 | *
  • The signed URL has expired 32 | * 33 | * @param spec The specification for the download 34 | * @return The closeable {@link DownloadBundle} 35 | * @throws HttpResponseException On failure HTTP response 36 | * @throws IOException IO Exception 37 | */ 38 | public DownloadBundle download(DownloadSpecification spec) throws HttpResponseException, IOException { 39 | 40 | File tmpFile = File.createTempFile(tmpFilePrefix, tmpFileSuffix, tmpFileDirectory); 41 | 42 | try { 43 | tmpFile.deleteOnExit(); 44 | 45 | String contentType = httpTransferClient.download(spec.getUrl(), tmpFile); 46 | 47 | return new DownloadBundle( 48 | spec.getCompressionAlgorithm(), contentType, spec.getCryptoStreamFactory(), tmpFile); 49 | } catch (Exception e) { 50 | tmpFile.delete(); 51 | throw e; 52 | } 53 | } 54 | 55 | /** 56 | * Use this to create an instance of a {@link DownloadHelper}. 57 | */ 58 | public static class Builder { 59 | private HttpTransferClient httpTransferClient = null; 60 | private String tmpFilePrefix = "SPAPI"; 61 | private String tmpFileSuffix = null; 62 | private File tmpFileDirectory = null; 63 | 64 | /** 65 | * The HTTP transfer client. 66 | * 67 | * @param httpTransferClient The HTTP transfer client 68 | * @return this 69 | */ 70 | public Builder withHttpTransferClient(HttpTransferClient httpTransferClient) { 71 | this.httpTransferClient = httpTransferClient; 72 | return this; 73 | } 74 | 75 | /** 76 | * The tmp file prefix. If not specified, defaults to "SPAPI". 77 | * 78 | * @param tmpFilePrefix The prefix string to be used in generating the tmp file's name; must be at least three 79 | * characters long 80 | * @return this 81 | */ 82 | public Builder withTmpFilePrefix(String tmpFilePrefix) { 83 | if (tmpFilePrefix.length() < 3) { 84 | throw new IllegalArgumentException("Prefix string too short"); 85 | } 86 | 87 | this.tmpFilePrefix = tmpFilePrefix; 88 | return this; 89 | } 90 | 91 | /** 92 | * The tmp file suffix. If not specified, defaults to null. 93 | * 94 | * @param tmpFileSuffix The suffix string to be used in generating the file's name; may be null, in 95 | * which case the suffix ".tmp" will be used 96 | * @return this 97 | */ 98 | public Builder withTmpFileSuffix(String tmpFileSuffix) { 99 | this.tmpFileSuffix = tmpFileSuffix; 100 | return this; 101 | } 102 | 103 | /** 104 | * The tmp file directory. If not specified, defaults to null. 105 | * 106 | * @param tmpFileDirectory The directory in which the file is to be created, or null if the default 107 | * temporary-file directory is to be used 108 | * @return this 109 | */ 110 | public Builder withTmpFileDirectory(File tmpFileDirectory) { 111 | this.tmpFileDirectory = tmpFileDirectory; 112 | return this; 113 | } 114 | 115 | /** 116 | * Create the helper. 117 | * 118 | * @return The helper 119 | */ 120 | public DownloadHelper build() { 121 | if (httpTransferClient == null) { 122 | httpTransferClient = new OkHttpTransferClient.Builder().build(); 123 | } 124 | 125 | return new DownloadHelper(httpTransferClient, tmpFilePrefix, tmpFileSuffix, tmpFileDirectory); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /clients/sample-code/DelegatedRestrictedDataTokenWorkflowForDelegatee.java: -------------------------------------------------------------------------------- 1 | package sampleCode; 2 | 3 | // Imports from the Selling Partner API (SP-API) Auth & Auth client library. 4 | import com.amazon.SellingPartnerAPIAA.AWSAuthenticationCredentials; 5 | import com.amazon.SellingPartnerAPIAA.AWSAuthenticationCredentialsProvider; 6 | import com.amazon.SellingPartnerAPIAA.AWSSigV4Signer; 7 | import com.squareup.okhttp.OkHttpClient; 8 | import com.squareup.okhttp.Request; 9 | import com.squareup.okhttp.RequestBody; 10 | import com.squareup.okhttp.Response; 11 | 12 | import java.io.IOException; 13 | 14 | public class DelegatedRestrictedDataTokenWorkflowForDelegatee { 15 | 16 | // This represents a workflow for a simple use case. Other use cases can be implemented using similar patterns. 17 | public static void main(String[] args) throws IOException { 18 | // The values for method, path and restrictedDataToken should match the RDT request made by delegator and the response. 19 | String method = "GET"; 20 | String path = "/orders/v0/orders/123-1234567-1234567"; 21 | String restrictedDataToken = "Atz.sprdt|AYABeKCs7hKXXXXXXXXXXXXXXXXXX..."; 22 | 23 | // Build, sign, and execute the request, specifying method, path, RDT, and RequestBody. 24 | // Pass a RequestBody only if required by the restricted operation. The requestBody is not required in this example. 25 | Response restrictedRequestResponse = buildAndExecuteDelegatedRestrictedRequest(method, path, restrictedDataToken, null); 26 | 27 | // Check the restricted operation response status code and headers. 28 | System.out.println(restrictedRequestResponse.code()); 29 | System.out.println(restrictedRequestResponse.headers()); 30 | } 31 | 32 | // The SP-API endpoint. 33 | private static final String sellingPartnerAPIEndpoint = "https://sellingpartnerapi-na.amazon.com"; 34 | 35 | // Configure the AWSAuthenticationCredentials object. 36 | // If you registered your application using an IAM Role ARN, the AWSAuthenticationCredentials and AWSAuthenticationCredentialsProvider objects are required. 37 | private static final AWSAuthenticationCredentials awsAuthenticationCredentials = AWSAuthenticationCredentials.builder() 38 | // If you registered your application using an IAM Role ARN, use the AWS credentials of an IAM User that is linked to the IAM Role through the AWS Security Token Service policy. 39 | // Or, if you registered your application using an IAM User ARN, use the AWS credentials of that IAM User. Be sure that the IAM User has the correct IAM policy attached. 40 | .accessKeyId("aws_access_key") 41 | .secretKey("aws_secret_key") 42 | .region("aws_region") 43 | .build(); 44 | 45 | // Configure the AWSAuthenticationCredentialsProvider object. This is only needed for applications registered using an IAM Role. 46 | // If the application was registered using an IAM User, the AWSAuthenticationCredentialsProvider object should be removed. 47 | private static final AWSAuthenticationCredentialsProvider awsAuthenticationCredentialsProvider = AWSAuthenticationCredentialsProvider.builder() 48 | // The IAM Role must have the IAM policy attached as described in "Step 3. Create an IAM policy" in the SP-API Developer Guide. 49 | .roleArn("arn:aws:iam::XXXXXXXXX:role/XXXXXXXXX") 50 | .roleSessionName("session-name") 51 | .build(); 52 | 53 | // An example of a helper method to build, sign, and execute a restricted operation, specifying RestrictedResource, (String) RDT, and RequestBody. 54 | // Returns the restricted operation Response object. 55 | private static Response buildAndExecuteDelegatedRestrictedRequest(String method, String path, String restrictedDataToken, RequestBody requestBody) throws IOException { 56 | // Construct a request with the specified RestrictedResource, RDT, and RequestBody. 57 | Request signedRequest = new Request.Builder() 58 | .url(sellingPartnerAPIEndpoint + path) // Define the URL for the request, based on the endpoint and restricted resource path. 59 | .method(method, requestBody) // Define the restricted resource method value, and requestBody, if required by the restricted operation. 60 | .addHeader("x-amz-access-token", restrictedDataToken) // Sign the request with the RDT by adding it to the "x-amz-access-token" header. 61 | .build(); // Build the request. 62 | 63 | // Initiate an AWSSigV4Signer instance using your AWS credentials. This example is for an application registered using an AIM Role. 64 | AWSSigV4Signer awsSigV4Signer = new AWSSigV4Signer(awsAuthenticationCredentials, awsAuthenticationCredentialsProvider); 65 | 66 | /* 67 | // Or, if the application was registered using an IAM User, use the following example: 68 | AWSSigV4Signer awsSigV4Signer = new AWSSigV4Signer(awsAuthenticationCredentials); 69 | */ 70 | 71 | // Sign the request with the AWSSigV4Signer. 72 | signedRequest = awsSigV4Signer.sign(signedRequest); 73 | 74 | // Execute the signed request. 75 | OkHttpClient okHttpClient = new OkHttpClient(); 76 | Response response = okHttpClient.newCall(signedRequest).execute(); 77 | 78 | return response; 79 | } 80 | 81 | } --------------------------------------------------------------------------------