Let’s Talk

Using the PHP Amazon API to Update Stock

In February we showed you how to integrate stock sharing with eBay, now we get to grips with Amazon’s API to do the same.

If you’ve implemented eBay stock sharing then prepare for a bit more work here as there are a few more obstacles in the way with this integration (including the terrible documentation) as Amazon throttle how much data you can send to, and receive from, them. In this post we are only providing the code to update stock; you will need to define the processes around it to adhere to Amazon’s rules.

Updating Amazon

I recommend storing all stock sold in a [stock sold] table then running through it every 20 minutes, pulling 15 products out at a time to inform Amazon to update. This means you will stay within Amazon’s throttle limits. It does of course mean that your stock levels won’t be 100% accurate all the time. Thanks Amazon.

First we need to assign the relevant details to the following variables (use the ones Amazon has given you), set up some parameters and place them in an array to be used later on.

$amazonSellerId         = '[use your details]';
$amazonAWSAccessKeyId   = '[use your details]';
$amazonSecretKey        = '[use your details]';
$amazonMarketPlaceId    = '[use your details]';

$param = array();
$param['AWSAccessKeyId']            = $amazonAWSAccessKeyId;
$param['Action']                    = "SubmitFeed";
$param['Merchant']                  = $amazonSellerId;
$param['FeedType']                  = "_POST_INVENTORY_AVAILABILITY_DATA_";
$param['SignatureMethod']           = "HmacSHA256";
$param['SignatureVersion']          = "2";
$param['Timestamp']                 = gmdate("Y-m-d\TH:i:s.\\0\\0\\0\\Z", time());
$param['Version']                   = "2009-01-01";
$param['MarketplaceIdList.Id.1']    = $amazonMarketPlaceId;
$param['PurgeAndReplace']           = "false";

$url = array();
foreach($param as $key => $val){
    $key = rawurlencode($key);
    $val = rawurlencode($val);
    $url[] = "{$key}={$val}";
}

Note the variables you’ve already populated, such as your AWS access key. The “FeedType” tells Amazon which of their many feeds we are wanting to use.

Then, looping through each product that needs to be updated we build up the “messages”. Each message tells Amazon which product we want to update based on its SKU. The MessageID needs to be unique for each message in the call (I just start at 1 and increment by one for each message). Quantity is the amount by which to reduce the stock i.e. how many have been sold.

$message .= <<<EOF
<Message>
    <MessageID>$i</MessageID>
    <OperationType>Update</OperationType>
    <Inventory>
        <SKU>$amazon_sku</SKU>
        <Quantity>$quantity</Quantity>
    </Inventory>
</Message>
EOF;

Then we incorporate the message into the complete XML.

$amazon_feed = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<AmazonEnvelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="amzn-envelope.xsd">
    <Header>
        <DocumentVersion>1.01</DocumentVersion>
        <MerchantIdentifier>$amazonSellerId</MerchantIdentifier>
    </Header>
    <MessageType>Inventory</MessageType>
    $message
</AmazonEnvelope>
EOF;

Just a bit more messing around here, setting up the headers and incorporating the $url array we created earlier.

sort($url);

$arr   = implode("&", $url);

$sign  = "POST\n";
$sign .= "mws-eu.amazonservices.com\n";
$sign .= "/Feeds/{$param['Version']}\n";
$sign .= $arr;

$signature      = hash_hmac("sha256", $sign, $amazonSecretKey, true);
$httpHeader     = array();
$httpHeader[]   = "Transfer-Encoding: chunked";
$httpHeader[]   = "Content-Type: application/xml";
$httpHeader[]   = "Content-MD5: ".base64_encode(md5($amazon_feed, true));
$httpHeader[]   = "Expect:";
$httpHeader[]   = "Accept:";

$signature = urlencode(base64_encode($signature));

$link  = "https://mws-eu.amazonservices.com/Feeds/{$param['Version']}?";
$link .= $arr."&Signature=".$signature;

And finally we get to actually send the XML to Amazon, using cURL.

$ch = curl_init($link);
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeader);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $amazon_feed);
$response = curl_exec($ch);
$info = curl_getinfo($ch);
$errors = curl_error($ch);
curl_close($ch);

$xml = simplexml_load_string($response);
$result = $xml->children()->SubmitFeedResult->
         children()->FeedSubmissionInfo->
         children()->FeedProcessingStatus;

If the almost-impossible-to-find $result is “_SUBMITTED_” then it’s worked, hurray! If not, good luck.

I would then update the [stock sold] table so we know which products have been updated and we don’t attempt to update them again.

Updating Website

Now we can update Amazon when we’ve sold something on our site, let’s see how we update our website when something’s sold on Amazon.

We need to import orders. For this we need the help of Amazon’s PHP SDK, MarketplaceWebServiceOrders, which you can find here (note: at time of writing this is the latest version, if a new version is out it should be linked to from here).

Firstly you’ll need to edit the config file (config.inc.php) to include your details.

Then use the below code to request all “unshipped” and “partially shipped” orders from your preferred $date onwards.

$serviceUrl = "https://mws-eu.amazonservices.com/Orders/2013-09-01";

$config = array (
	"ServiceURL" => $serviceUrl,
	"ProxyHost" => null,
	"ProxyPort" => -1,
	"MaxErrorRetry" => 3,
);

$service = new MarketplaceWebServiceOrders_Client(
	AWS_ACCESS_KEY_ID,
	AWS_SECRET_ACCESS_KEY,
	APPLICATION_NAME,
	APPLICATION_VERSION,
	$config);

$request = new MarketplaceWebServiceOrders_Model_ListOrdersRequest();
$request->setSellerId(MERCHANT_ID);

$request->setCreatedAfter(new DateTime($date));

$marketplaceIdList = new MarketplaceWebServiceOrders_Model_MarketplaceIdList();
$marketplaceIdList->setId(array(MARKETPLACE_ID));
$request->setMarketplaceId($marketplaceIdList);

$orderStatuses = new MarketplaceWebServiceOrders_Model_OrderStatusList();
$orderStatuses->setStatus(array("Unshipped", "PartiallyShipped"));
$request->setOrderStatus($orderStatuses);
invokeListOrders($service, $request);

The function invokeListOrders is as below.

function invokeListOrders(MarketplaceWebServiceOrders_Interface $service, $request){
    $response = $service->listOrders($request);

    if($response->isSetListOrdersResult()){
        $listOrdersResult = $response->getListOrdersResult();

        if($listOrdersResult->isSetOrders()){
            $orders = $listOrdersResult->getOrders();
            $orderList = $orders->getOrder();

            foreach($orderList as $order){
                $shippingAddress = $order->getShippingAddress();

                $name = $shippingAddress->getName();
                $addr1 = $shippingAddress->getAddressLine1();
                $addr2 = $shippingAddress->getAddressLine2();
                $addr3 = $shippingAddress->getAddressLine3();
                $city = $shippingAddress->getCity();
                $county = $shippingAddress->getStateOrRegion();
                $postcode = $shippingAddress->getPostalCode();
                $country = $shippingAddress->getCountryCode();
                $tel = $shippingAddress->getPhone();
                $orderTotal = $order->getOrderTotal();
                $total = $orderTotal->getAmount();

                $amazonid = $order->getAmazonOrderId();

                $request = new MarketplaceWebServiceOrders_Model_ListOrderItemsRequest();
                $request->setSellerId(MERCHANT_ID);
                $request->setAmazonOrderId($order->getAmazonOrderId());
                invokeListOrderItems($service, $request, $amazonid);
            }
        }
    }
}

That loops through all orders matching the status and date limit previously set. You should store these details in your database so you can check whether you’ve imported the order or not before changing stock (it’s not in the code example above but if you have already imported, don’t call invokeListOrderItems); I don’t know of a way to only import “new” orders, so Amazon will send you everything from the date you specify.

The function invokeListOrderItems gets all items/products from each order, as can be seen here:

function invokeListOrderItems(MarketplaceWebServiceOrders_Interface $service, $request, $amazonid){
    global $basket;

    $response = $service->listOrderItems($request);
    if($response->isSetListOrderItemsResult()){
        $listOrderItemsResult = $response->getListOrderItemsResult();
        if($listOrderItemsResult->isSetOrderItems()){
            $orderItems = $listOrderItemsResult->getOrderItems();
            $orderItemList = $orderItems->getOrderItem();

            foreach($orderItemList as $orderItem){
                $product_name = $orderItem->getTitle();
                if($orderItem->isSetSellerSKU()){
                    $product_stockcode = $orderItem->getSellerSKU();
                }
                $itemPrice = $orderItem->getItemPrice();
                $quantity = $orderItem->getQuantityOrdered();
                $product_price = $itemPrice->getAmount()/$quantity;
            }
        }
    }
}

As you can see that rather laborious task eventually yields the product information that we need to update the website’s stock – $product_stockcode and $quantity.

You can also access the product name and price amongst numerous other things. Note that the price, which you may or may nor need, is the price of the individual item multiplied by the number of that item purchased, so to get the individual item price we need to divide by the number purchased.

This code is free to use at your own discretion. It comes without warranty. Please feel free to feedback any edits.