This Sample Solution provides all required resources to deploy to the AWS cloud a fully functional SP-API application that implements the Easy Ship API v2022-03-23 Use Case Guide end-to-end. Use this application to test the proposed solution, do changes and/or integrate it to your own product.
The Amazon EasyShip API is designed to support you build applications that help sellers manage and ship Amazon Easy Ship orders. With these APIs, you can request below operations.
- Getting available time slots for packages to be scheduled for delivery
- Schedule, reschedule, and cancel Easy Ship orders
- Print labels, invoices, and warranties
If you haven't already, we recommend you to navigate the following resources:
This Sample Solution implements the following Easy Ship operations:
- Retrieve an order
- Check Inventory
- Get Handover Slots list
- Create Schedule package with the preferred shipment handover slots
- Confirm scheduled shipment by Scheduled Package Id
- Submit Feed Request for printing shipping label
- Get Feed Document to identify document Report Reference Id
- Download shipping label from Report API and Store it to S3 then generate pre-signed URL
If you want to test the proposed solution, do changes and/or integrate it to your own product, follow the steps under Deployment Guide.
A well-architected Easy Ship workflow includes subscribing to the ORDER_CHANGE
notification for automatic reception of new EasyShip orders.
Alternatively, new orders can be identified from incoming order reports.
Entire Workflow can be break into 2 parts below, EasyShip workflow and the Detail operation of Printing Label Workflow.
Below are the code steps for the Easy Ship service workflow.
The process for the steps belows should start by monitoring ORDER_CHANGE notifications. To subscribe to the Notifications, refer to Tutorial 5: Subscribe to MFN Notifications of the use case guide.
Upon an ORDER_CHANGE notification for unshipped MFN orders, the Orders API is used to retrieve the order details.
- Initialize API Client: The API client of OrdersAPI is initialized with the Refresh Token and the Region.
- Get Order: The Orders API getOrder operation is called using the orderId from the ORDER_CHANGE notification.
- Get Order Item: The Orders API getOrderItems operation is called using the orderId from the ORDER_CHANGE notification.
- Confirm EasyShip order: Confirm if the order is EasyShip order with right status using the Orders API getOrderResponse.
- Create the order processing class: The EasyShipOrder class is instantiated with the order items from getOrderItems.
- Pass EasyShipOrder object The EasyShipOrder object created is passed for further processing of the EasyShip Workflow.
Java
Find the full code here
// Get Order V0 API instance
OrdersV0Api OrdersApi = ApiUtils.getOrdersV0Api(input);
// API calls to retrieve order and order items
GetOrderResponse orderResponse = OrdersApi.getOrder(input.getAmazonOrderId());
GetOrderItemsResponse orderItemsResponse = OrdersApi.getOrderItems(input.getAmazonOrderId(), null);
if (!EasyShipShipmentStatus.PENDINGSCHEDULE.equals(orderResponse.getPayload().getEasyShipShipmentStatus())) {
throw new IllegalArgumentException(
String.format("Amazon Order Id : %s is not EasyShip order", input.getAmazonOrderId()));
}
EasyShipOrder easyShipOrder = new EasyShipOrder();
easyShipOrder.setOrderItems(getOrderItemList(orderItemsResponse));
input.setEasyShipOrder(easyShipOrder);
return input;
After retrieving the order details, the system performs an inventory availability check and retrieves the dimension and weight information.
- Process the order items: A loop over all order items is created to execute inventory checks.
- Prepare the request: Get the orderItem SKU and prepare the database (DynamoDB) request.
- Retrieve the Item from Inventory: Using the database client, the item is retrieved from inventory table.
- Check stock: The item stock quantity is checked. The order is aborted if quantity is insufficient.
- Calculate Order Weight and Dimensions: The total order weight and dimensions are calculated and returned as part of the EasyShip order.
Find the full code here
Java
Find the full code here
// Iterate over all order items and retrieve stock, size and weight from the database
for (EasyShipOrderItem orderItem : input.getEasyShipOrder().getOrderItems()) {
//Retrieve the item from DynamoDB by SKU
//Update this section to match your product's logic
Map<String, AttributeValue> key = new HashMap<>();
key.put(INVENTORY_TABLE_HASH_KEY_NAME, AttributeValue.fromS(orderItem.getSku()));
GetItemRequest getItemRequest = GetItemRequest.builder()
.tableName(System.getenv(INVENTORY_TABLE_NAME_ENV_VARIABLE))
.key(key)
.build();
DynamoDbClient dynamoDB = DynamoDbClient.builder().build();
GetItemResponse getItemResult = dynamoDB.getItem(getItemRequest);
Map<String, AttributeValue> item = getItemResult.item();
String stock = item.get(INVENTORY_TABLE_STOCK_ATTRIBUTE_NAME).n();
if (parseLong(stock) < orderItem.getQuantity()) {
throw new InternalError(
String.format("Stock level for SKU {%s} is not enough to fulfill the requested quantity",
orderItem.getSku()));
}
long itemWeightValue = parseLong(item.get(INVENTORY_TABLE_WEIGHT_VALUE_ATTRIBUTE_NAME).n());
//Valid values for the database records are uppercase: [OZ, G]
String itemWeightUnit = item.get(INVENTORY_TABLE_WEIGHT_UNIT_ATTRIBUTE_NAME).s();
long itemLength = parseLong(item.get(INVENTORY_TABLE_LENGTH_ATTRIBUTE_NAME).n());
long itemWidth = parseLong(item.get(INVENTORY_TABLE_WIDTH_ATTRIBUTE_NAME).n());
long itemHeight = parseLong(item.get(INVENTORY_TABLE_HEIGHT_ATTRIBUTE_NAME).n());
//Valid values for the database records are uppercase: [INCHES, CENTIMETERS]
String itemSizeUnit = item.get(INVENTORY_TABLE_SIZE_UNIT_ATTRIBUTE_NAME).s();
Weight itemWeight = new Weight();
itemWeight.setValue(BigDecimal.valueOf(itemWeightValue));
itemWeight.setUnit(UnitOfWeight.valueOf(itemWeightUnit));
//Package weight is calculated by adding the individual weights
//Update this section to match your selling partners' logic
packageWeightValue += itemWeightValue;
packageWeightUnit = itemWeightUnit;
//Package size is calculated by adding the individual sizes
//Update this section to match your selling partners' logic
packageLength += itemLength;
packageWidth += itemWidth;
packageHeight += itemHeight;
packageSizeUnit = itemSizeUnit;
}
input.getEasyShipOrder().setPackageWeight(new Weight()
.value(BigDecimal.valueOf(packageWeightValue))
.unit(UnitOfWeight.valueOf(packageWeightUnit)));
input.getEasyShipOrder().setPackageDimensions(new Dimensions()
.length(BigDecimal.valueOf(packageLength))
.width(BigDecimal.valueOf(packageWidth))
.height(BigDecimal.valueOf(packageHeight))
.unit(UnitOfLength.valueOf(packageSizeUnit)));
return input;
Once the inventory is checked and Weight and Dimensions are retrieved, we use the listHandoverSlots operation to check for available handover slots list.
- Initialize API Client: The API client for EasyShip is initialized using the Refresh Token and the Region.
- Prepare the request: The request for the listHandoverSlots operation is prepared.
- Call the API: The EasySHip Api listHandoverSlots is called using the prepared request.
- Set the Handover slots list: Set the fetched eligible Handover slots as part of the order and return it.
Java
Find the full code here
//Get list of handover shipment slot for the order
ListHandoverSlotsRequest request = new ListHandoverSlotsRequest()
.amazonOrderId(input.getAmazonOrderId())
.marketplaceId(input.getMarketplaceId())
.packageDimensions(input.getEasyShipOrder().getPackageDimensions())
.packageWeight(input.getEasyShipOrder().getPackageWeight());
logger.log("EasyShip API - listHandoverSlots request: " + new Gson().toJson(request));
EasyShipApi easyShipApi = ApiUtils.getEasyShipApi(input);
ListHandoverSlotsResponse response = easyShipApi.listHandoverSlots(request);
input.setTimeSlots((response.getTimeSlots()));
return input;
After getting the eligible Handover slots, Call createScheduledPackage operation to create a schedule with preferred shipment handover slots.
- Prepare the request: Create CreateScheduledPackageRequest with the preferred shipment handover slots.
- Initialize API Client: The API client for EasyShip is initialized using the Refresh Token and the Region.
- Return Scheduled Package Id: The Scheduled Package Id is returned for confirmation step coming to next.
Java
Find the full code here
//Get list of handover shipment slot for the order
CreateScheduledPackageRequest request = new CreateScheduledPackageRequest()
.amazonOrderId(input.getAmazonOrderId())
.marketplaceId(input.getMarketplaceId())
.packageDetails(new PackageDetails()
.packageTimeSlot(input.getTimeSlots().get(0)));
logger.log("EasyShip API - CreateScheduledPackage request: " + new Gson().toJson(request));
EasyShipApi easyShipApi = ApiUtils.getEasyShipApi(input);
ModelPackage response = easyShipApi.createScheduledPackage(request);
logger.log("EasyShip API - CreateScheduledPackage response: " + new Gson().toJson(response));
// Store ScheduledPackageId to validate scheduled correctly using this information on the next step
input.setScheduledPackageId(response.getScheduledPackageId());
return input;
After creating schedule package on EasyShip, confirm schedule was created as expected by calling getScheduledPackage operation. Note that, this step was added for making sure, so it can be skipped if not needed.
- Initialize API Client: The EasyShip API is initialized using the Refresh Token and the Region.
- Call the getScheduledPackage operation The getScheduledPackage operation is called using Amazon Order Id and Market Place Id.
- Validate Scheduled Package Id: Confirm the Scheduled Package Id part of the response payload is same as the Scheduled Package Id from previous step.
Java
Find the full code here
EasyShipApi easyShipApi = ApiUtils.getEasyShipApi(input);
ModelPackage response = easyShipApi.getScheduledPackage(input.getAmazonOrderId(), input.getMarketplaceId());
logger.log("EasyShip API - CreateScheduledPackage response: " + new Gson().toJson(response));
// Validate if scheduled correctly using ScheduledPackageId
if (!response.getScheduledPackageId().equals(input.getScheduledPackageId())) {
throw new IllegalArgumentException(
String.format("Amazon Order Id : %s was not scheduled correctly", input.getAmazonOrderId()));
}
After creating scheduled package, start printing the shipping label process starting calling Feed API. Detailed instruction can be found Tutorial: Get shipping labels, invoice, and warranty documents.
- Initialize API Client: The Feed API is initialized using the Refresh Token and the Region.
- Call Create Feeds document API: The createFeedDocument operation needs to be called to generate feedDocumentId and url for storing Feed Document.
- Prepare Feed Document Create a Feed document which will be uploaded to the url was given at the previous step.
- Upload Feed Document: Upload Feed document just created using Http PUT method with give credentials at the Create Feeds document API response.
- Call Create Feeds API: After upload feed document, execute Feed process by calling createFeed operation.
Java
Find the full code here
// Create Feeds document
FeedsApi feedsApi = ApiUtils.getFeedsApi(input);
String contentType = String.format("text/xml; charset=%s", StandardCharsets.UTF_8);
CreateFeedDocumentSpecification createFeedDocumentbody = new CreateFeedDocumentSpecification().contentType(contentType);
CreateFeedDocumentResponse createFeedDocumentresponse = feedsApi.createFeedDocument(createFeedDocumentbody);
logger.log("Feed API - Create Feeds document response: " + new Gson().toJson(createFeedDocumentresponse));
// Upload Feeds Document
String url = createFeedDocumentresponse.getUrl();
String content = XmlUtil.generateEasyShipAmazonEnvelope(
input.getSellerId(),input.getAmazonOrderId(), FEED_OPTIONS_DOCUMENT_TYPE_VALUE);
HttpFileTransferUtil.upload(content.getBytes(StandardCharsets.UTF_8), url);
// Create Feeds
FeedOptions feedOptions = new FeedOptions();
feedOptions.put(FEED_OPTIONS_KEY_AMAZON_ORDER_ID, input.getAmazonOrderId());
feedOptions.put(FEED_OPTIONS_KEY_DOCUMENT_TYPE, FEED_OPTIONS_DOCUMENT_TYPE_VALUE);
String feedDocumentId = createFeedDocumentresponse.getFeedDocumentId();
CreateFeedSpecification createFeedbody = new CreateFeedSpecification()
.feedType(POST_EASYSHIP_DOCUMENTS)
.marketplaceIds(Collections.singletonList(input.getMarketplaceId()))
.feedOptions(feedOptions)
.inputFeedDocumentId(feedDocumentId);
logger.log("Feed API - Create Feeds request body: " + new Gson().toJson(createFeedbody));
CreateFeedResponse createFeedResponse = feedsApi.createFeed(createFeedbody);
logger.log("Feed API - Create Feeds response: " + new Gson().toJson(createFeedDocumentresponse));
input.setFeedId(createFeedResponse.getFeedId());
return input;
After creating the Feed request, wait until the Feed process is completed by confirming its status via the getFeed operation. Then call getFeedDocument operation to download and see the result of the Feed process.
- Initialize API Client: The Feed API is initialized using the Refresh Token and the Region.
- Call Get Feeds API: The getFeed operation is called to retrieve resultFeedDocumentId which will be used to download result file at the next step.
- Call Get Feeds Document API: The getFeedDocument operation is called using resultFeedDocumentId to retrieve result file URL.
- Download Feeds Result Document: Download Feeds Result Document via Http GET method with give credentials from the Get Feeds Document API response.
- Return Document Report Reference Id: The documentReportReferenceId is returned from the downloaded Feeds Result Document.
Java
Find the full code here
//Initialize API Client
FeedsApi feedsApi = ApiUtils.getFeedsApi(input);
// Get Feeds
String resultFeedDocumentId = waitForFeedCompletion(feedsApi, input.getFeedId(), logger);
// Get Feed Document
FeedDocument document = getFeedDocument(feedsApi, resultFeedDocumentId, logger);
// Download Document
InputStream documentStream = HttpFileTransferUtil.download(document.getUrl(), null);
// Extract documentReportReferenceId from the document
String documentReportReferenceId = XmlUtil.getXmlDocumentTag(documentStream, FEED_DOCUMENT_REPORT_REFERENCE_ID);
input.setReportId(documentReportReferenceId);
return input;
After identifying documentReportReferenceId by downloading Feeds Result Document, call getReport API to identify reportDocumentId, then call getReportDocument API to identify URL of generated document, download the document, store it in S3 and generate pre-signed URL which will be send as E-mail content.
- Initialize API Client: The Report API is initialized using the Refresh Token and the Region.
- Call Get Report API: The getReport operation is called to retrieve reportDocumentId which will be used to identify URL of the Report document at the next step.
- Call Get Report Document API: The getReportDocument operation is called using reportDocumentId to retrieve the Report document URL.
- Download Report Document: Download the Report Document via Http GET method with give credentials from the Get Report Document API response.
- Store Report Document into S3: Store downloaded file into S3 bucket.
- Pre-sign: A pre-signed URL is generated on the label object on S3.
Java
Find the full code here
//Initialize API Client
ReportsApi reportsApi = ApiUtils.getReportsApi(input);
// Get reportDocumentId from ReportAPI
String reportDocumentId = waitForReportCompletion(reportsApi, input, logger);
// Get Report Document
String documentUrl = getReportDocumentUrl(reportsApi, reportDocumentId, logger);
// Download Report Document
InputStream documentStream = HttpFileTransferUtil.download(documentUrl, null);
// Get S3 bucket name from environment variables
String s3BucketName = System.getenv(EASYSHIP_LABEL_S3_BUCKET_NAME_ENV_VARIABLE);
// Generate S3 key for the document
String objectKey = generateObjectKey(input);
logger.log("S3 Bucket Name: " + s3BucketName + " S3 Object Key: " + objectKey);
//Store into S3 bucket
storeDocumentInS3(s3BucketName, objectKey, documentStream);
//Generate a presigned url to browse the label
return generatePresignedUrl(s3BucketName, objectKey, logger);
The pre-signed URL is finally passed to next steps such as printing. In this sample solution, the step functions last step provides the pre-signed URl to an SNS topic which triggers then sends it by Email.