Importing Amazon Seller Central Orders

For general questions and discussions specific to the AbleCommerce GOLD ASP.Net shopping cart software.
Post Reply
User avatar
vsammons
Lieutenant, Jr. Grade (LT JG)
Lieutenant, Jr. Grade (LT JG)
Posts: 33
Joined: Mon May 10, 2010 7:10 am

Importing Amazon Seller Central Orders

Post by vsammons » Mon Nov 16, 2015 9:11 am

Has anyone created a module to do this currently? We want to manage all of our orders including shipping from AbleCommerce and would like to import the orders from Amazon (currently re-keying) them in. If there is a large demand for this I am willing to create this plugin. I just do not want to reinvent the wheel. I have not been able to find any plugins for this to date.

nethrelm
Lieutenant (LT)
Lieutenant (LT)
Posts: 61
Joined: Thu May 09, 2013 4:47 pm

Re: Importing Amazon Seller Central Orders

Post by nethrelm » Wed Nov 18, 2015 11:34 am

We do this. I pull orders using the Amazon MWS C# library. It's not exactly what I would consider a publishing-ready plugin though. There were a lot of customizations I added that the Amazon stuff has dependencies on (e.g., a custom plugins system, a task system that is a plugin for the previous, wrappers around the MWS library for fetching the orders as a task, processing the results, preparing XML feeds to post back to Amazon, another task that sends pending feeds, another plugin that tracks the status of submitted feeds...probably more I am forgetting at the moment). There's quite a bit involved in the whole thing to be honest. I'm sure I can offer some tips, or post some code if you need help with something specific.

User avatar
vsammons
Lieutenant, Jr. Grade (LT JG)
Lieutenant, Jr. Grade (LT JG)
Posts: 33
Joined: Mon May 10, 2010 7:10 am

Re: Importing Amazon Seller Central Orders

Post by vsammons » Wed Nov 18, 2015 12:05 pm

Would you be willing to share what you have or pay you for the "Module/function documentation" ? It may be better than trying to recreate the wheel...

nethrelm
Lieutenant (LT)
Lieutenant (LT)
Posts: 61
Joined: Thu May 09, 2013 4:47 pm

Re: Importing Amazon Seller Central Orders

Post by nethrelm » Wed Nov 18, 2015 1:23 pm

Well, let's see.......First we have some settings to define:

Code: Select all

using CommerceBuilder.Common;
using CommerceBuilder.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AbleCommerce.Plugins.Amazon.MWS
{
    public static class Settings
    {
        public static void Save()
        {
            AbleContext.Current.Store.Settings.Save();
        }

        public static bool UseMock
        {
            get { return AlwaysConvert.ToBool(AbleContext.Current.Store.Settings.GetValueByKey(AmazonSettingKeys.UseMock), true); }
            set { AbleContext.Current.Store.Settings.SetValueByKey(AmazonSettingKeys.UseMock, value.ToString()); }
        }

        public static string MerchantID
        {
            get { return AbleContext.Current.Store.Settings.GetValueByKey(AmazonSettingKeys.MerchantID); }
            set { AbleContext.Current.Store.Settings.SetValueByKey(AmazonSettingKeys.MerchantID, value); }
        }

        public static string MarketplaceID
        {
            get { return AbleContext.Current.Store.Settings.GetValueByKey(AmazonSettingKeys.MarketplaceID); }
            set { AbleContext.Current.Store.Settings.SetValueByKey(AmazonSettingKeys.MarketplaceID, value); }
        }

        public static string AccessKey
        {
            get { return AbleContext.Current.Store.Settings.GetValueByKey(AmazonSettingKeys.AccessKey); }
            set { AbleContext.Current.Store.Settings.SetValueByKey(AmazonSettingKeys.AccessKey, value); }
        }

        public static string SecretKey
        {
            get { return AbleContext.Current.Store.Settings.GetValueByKey(AmazonSettingKeys.SecretKey); }
            set { AbleContext.Current.Store.Settings.SetValueByKey(AmazonSettingKeys.SecretKey, value); }
        }

        private static class AmazonSettingKeys
        {
            public static readonly string MerchantID = "AMWS_MerchantID";
            public static readonly string MarketplaceID = "AMWS_MarketplaceID";
            public static readonly string AccessKey = "AMWS_AccessKey";
            public static readonly string SecretKey = "AMWS_SecretKey";
            public static readonly string UseMock = "AMWS_UseMock";
        }
    }
}
Next we have a static Services class that makes things easier later on...

Code: Select all

using CommerceBuilder.Common;
using System;
using System.Collections.Generic;
using System.Reflection;

using AbleCommerce.Plugins.Amazon.MWS.Orders;

namespace AbleCommerce.Plugins.Amazon.MWS
{
    internal delegate void RetriableMethodCall();

    public static class Services
    {
        /// <summary>
        /// Gets the order service
        /// </summary>
        public static OrderService OrderService { get; private set; }

        /// <summary>
        /// Static constructor
        /// </summary>
        static Services()
        {
            OrderService = new OrderService();
        }

        /// <summary>
        /// Gets the Application Name
        /// </summary>
        static internal string ApplicationName
        {
            get
            {
                Assembly assembly = Assembly.GetExecutingAssembly();
                AssemblyTitleAttribute titleAtt = ((AssemblyTitleAttribute)Attribute.GetCustomAttribute(assembly, typeof(AssemblyTitleAttribute), false));
                return (titleAtt != null && !string.IsNullOrWhiteSpace(titleAtt.Title)) ? titleAtt.Title : assembly.GetName().Name;
            }
        }

        /// <summary>
        /// Gets the Application Version
        /// </summary>
        static internal string ApplicationVersion
        {
            get
            {
                Assembly assembly = Assembly.GetExecutingAssembly();
                return string.Format("{0}.{1}", assembly.GetName().Version.Major, assembly.GetName().Version.Minor);
            }
        }
    }
}
Then there is the OrderService itself:

Code: Select all

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MarketplaceWebServiceOrders;
using MarketplaceWebServiceOrders.Mock;
using MarketplaceWebServiceOrders.Model;

using IMarketplaceWebServiceOrders = MarketplaceWebServiceOrders.MarketplaceWebServiceOrders;

namespace AbleCommerce.Plugins.Amazon.MWS.Orders
{
    public delegate void OrderReceivedCallback(Order order, IList<OrderItem> items);

    public class OrderService
    {
        /// <summary>
        /// Internal contructor
        /// </summary>
        internal OrderService() { }

        /// <summary>
        /// Retrieves unshipped orders from the order service
        /// </summary>
        /// <param name="startDate">The starting date to begin retrieving orders</param>
        public void FetchNewOrders(DateTime startDate, OrderReceivedCallback callback)
        {
            OrderFetcher orderFetcher = new OrderFetcher(
                GetOrderService(),
                Settings.MerchantID,
                new string[] { Settings.MarketplaceID }
            );

            orderFetcher.FetchOrders(startDate, callback);
        }

        /// <summary>
        /// Gets the order service
        /// </summary>
        /// <returns>An interface for the Amazon order service</returns>
        private IMarketplaceWebServiceOrders GetOrderService()
        {
            if (Settings.UseMock)
            {
                return new MarketplaceWebServiceOrdersMock();
            }
            else
            {
                MarketplaceWebServiceOrdersConfig config = new MarketplaceWebServiceOrdersConfig();
                config.ServiceURL = "https://mws.amazonservices.com/Orders/2011-01-01";

                return new MarketplaceWebServiceOrdersClient(
                    Services.ApplicationName,
                    Services.ApplicationVersion,
                    Settings.AccessKey,
                    Settings.SecretKey,
                    config
                );
            }
        }
    }
}
Here is the OrderFetcher code. It's only dependency is on the MWS library. It's pretty much the same as the sample code they give you in the download though (some slight differences to accommodate how I designed the rest of the system):

Code: Select all

using MarketplaceWebServiceOrders;
using MarketplaceWebServiceOrders.Mock;
using MarketplaceWebServiceOrders.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IMarketplaceWebServiceOrders = MarketplaceWebServiceOrders.MarketplaceWebServiceOrders;

namespace AbleCommerce.Plugins.Amazon.MWS.Orders
{
    internal class OrderFetcher
    {
        /// <summary>
        /// Default throttling limit for ListOrders calls; default to 1 per 15 seconds.
        /// </summary>
        private const int LIST_ORDERS_DEFAULT_THROTTLE_LIMIT = 15 * 1000;

        /// <summary>
        /// Default throttling limit for ListOrderItems calls; default to 1 per 2 seconds
        /// </summary>
        private const int LIST_ORDER_ITEMS_DEFAULT_THROTTLE_LIMIT = 2 * 1000;

        private IMarketplaceWebServiceOrders mwsService;
        private string mwsSellerId;
        private string[] mwsMarketplaceIdList;

        /// <summary>
        /// Creates a new instance of the OrderFetcher class.
        /// </summary>
        internal OrderFetcher(IMarketplaceWebServiceOrders service, string sellerId, string[] marketplaceIdList)
        {
            mwsService = service;
            mwsSellerId = sellerId;
            mwsMarketplaceIdList = marketplaceIdList;
        }

        /// <summary>
        /// Fetches all orders created between the starting time and the server's
        /// local system time minus two minutes.
        /// <param name="startTime">The starting time period of orders to fetch.</param>
        internal void FetchOrders(DateTime startTime, OrderReceivedCallback callback)
        {
            FetchOrders(startTime, DateTime.MinValue, callback);
        }

        /// <summary>
        /// Fetches all orders created in the given time period and processes them locally.        
        /// <param name="startTime">The starting time period of orders to fetch.</param>
        /// <param name="endTime">The ending time period of orders to fetch.</param>
        internal void FetchOrders(DateTime startTime, DateTime endTime, OrderReceivedCallback callback)
        {
            ListOrdersRequest request = new ListOrdersRequest();
            request.LastUpdatedAfter = startTime.AddMinutes(-2);
            if (endTime != DateTime.MinValue)
            {
                request.LastUpdatedBefore = endTime.AddMinutes(-2);
            }
            request.SellerId = mwsSellerId;
            request.MarketplaceId = new MarketplaceIdList();
            request.MarketplaceId.Id = new List<string>();
            foreach (string marketplaceId in mwsMarketplaceIdList)
            {
                request.MarketplaceId.Id.Add(marketplaceId);
            }
            request.OrderStatus = new OrderStatusList();
            request.OrderStatus.Status = new List<OrderStatusEnum>();
            request.OrderStatus.Status.Add(OrderStatusEnum.Unshipped);
            request.OrderStatus.Status.Add(OrderStatusEnum.PartiallyShipped);

            List<Order> orderList = new List<Order>();
            ListOrdersResponse response = null;
            OrderFetcher.InvokeRetriable(LIST_ORDERS_DEFAULT_THROTTLE_LIMIT, delegate()
            {
                response = mwsService.ListOrders(request);
            });

            ProcessOrders(response.ListOrdersResult.Orders.Order, callback);
            String nextTokenString = response.ListOrdersResult.NextToken;

            while (!string.IsNullOrEmpty(nextTokenString))
            {
                // If NextToken is set, continue looping through the orders.
                ListOrdersByNextTokenRequest nextRequest = new ListOrdersByNextTokenRequest();
                nextRequest.NextToken = nextTokenString;
                nextRequest.SellerId = mwsSellerId;

                ListOrdersByNextTokenResponse nextResponse = null;
                OrderFetcher.InvokeRetriable(LIST_ORDERS_DEFAULT_THROTTLE_LIMIT, delegate()
                {
                    nextResponse = mwsService.ListOrdersByNextToken(nextRequest);    
                });

                ProcessOrders(nextResponse.ListOrdersByNextTokenResult.Orders.Order, callback);
                nextTokenString = nextResponse.ListOrdersByNextTokenResult.NextToken;
            }
        }

        /// <summary>
        /// Method called by the FetchOrders method to process the orders.
        /// </summary>
        /// <param name="orders">List of orders returned by FetchOrders</param>
        internal void ProcessOrders(List<Order> orders, OrderReceivedCallback callback)
        {
            foreach (Order order in orders)
            {
                IList<OrderItem> items = FetchOrderItems(order.AmazonOrderId);
                if (callback != null) callback.Invoke(order, items);
            }
        }

        /// <summary>
        /// Fetches the OrderItems for the specified orderId.
        /// </summary>
        private IList<OrderItem> FetchOrderItems(string orderId)
        {
            List<OrderItem> orderItems = new List<OrderItem>();
            ListOrderItemsRequest request = new ListOrderItemsRequest();
            request.SellerId = mwsSellerId;
            request.AmazonOrderId = orderId;
            
            ListOrderItemsResponse response = null;
            OrderFetcher.InvokeRetriable(LIST_ORDER_ITEMS_DEFAULT_THROTTLE_LIMIT, delegate()
            {
                response = mwsService.ListOrderItems(request);
            });

            orderItems.AddRange(response.ListOrderItemsResult.OrderItems.OrderItem);
            String nextTokenString = response.ListOrderItemsResult.NextToken;

            while (!string.IsNullOrEmpty(nextTokenString))
            {
                // If NextToken is set, continue looping through the orders.
                ListOrderItemsByNextTokenRequest nextRequest = new ListOrderItemsByNextTokenRequest();
                nextRequest.NextToken = nextTokenString;
                nextRequest.SellerId = mwsSellerId;

                ListOrderItemsByNextTokenResponse nextResponse = null;
                OrderFetcher.InvokeRetriable(LIST_ORDER_ITEMS_DEFAULT_THROTTLE_LIMIT, delegate()
                {
                    nextResponse = mwsService.ListOrderItemsByNextToken(nextRequest);
                });

                orderItems.AddRange(nextResponse.ListOrderItemsByNextTokenResult.OrderItems.OrderItem);
                nextTokenString = nextResponse.ListOrderItemsByNextTokenResult.NextToken;
            }

            return orderItems;
        }

        /// <summary>
        /// Invokes a method in a retriable fashion.
        /// </summary>
        /// <param name="throttledWaitTime">The amount of time to wait if the request is throttled.</param>
        /// <param name="method">The method to invoke.</param>
        private static void InvokeRetriable(int throttledWaitTime, RetriableMethodCall method)
        {
            bool retryRequest = false;
            do
            {
                retryRequest = false;
                try
                {
                    // Perform some action
                    method.Invoke();
                }
                catch (MarketplaceWebServiceOrdersException ordersErr)
                {
                    // If the request is throttled, wait and try again.
                    if (ordersErr.ErrorCode == "RequestThrottled")
                    {
                        Console.WriteLine("Request is throttled; waiting...");
                        retryRequest = true;
                        System.Threading.Thread.Sleep(throttledWaitTime);
                    }
                    else
                    {
                        // On any other error, re-throw the exception to be handled by the caller
                        throw;
                    }
                }
            } while (retryRequest);
        }
    }
}
That should be everything necessary to pull down the orders from seller central. All you have to do is call Services.OrderService.FetchNewOrders(DateTime, OrderReceivedCallback) with the start date and the callback method as arguments. That's where you will have to actually decide how to put the data into your system. That's where things start getting a bit more complex as far as dealing with what will happen down the road with fulfillment and such. This where all my other custom dependencies start entering the mix. Hopefully that will at least get you started though, and if you have any specific questions I'll try to answer them. Sorry if the documentation is lacking. It's not my strong suit. I try to write clean code that speaks for itself.

nethrelm
Lieutenant (LT)
Lieutenant (LT)
Posts: 61
Joined: Thu May 09, 2013 4:47 pm

Re: Importing Amazon Seller Central Orders

Post by nethrelm » Wed Nov 18, 2015 1:32 pm

Oh, and obviously you need to create a UI to add/update the settings we defined. It won't work without your MerchantID and all that jazz. I also realize looking over the code that I should have made the config.ServiceURL a setting as well so that if it changes I won't need to modify the code and recompile. Oops, heh.

Post Reply