Chapter 1. Product Discovery and Research

One of the most challenging activities for merchants is attracting new customers to their online or retail sites. Large amounts of funding and effort go into various forms of advertising, including banners, search engine keyword ads, and social or direct marketing to capture eyeballs, increase traffic, and optimally create new customer purchases. At the same time, customers have at hand a large quantity of readily accessible information while researching products or services of interest, which may include reviews and social feeds. These are the challenges of the first step of the simplified commerce lifecycle shown in Figure 1-1—product discovery and research—for both merchants and consumers.

Simplified commerce lifecycle—stage 1

Figure 1-1. Simplified commerce lifecycle—stage 1

In this chapter the examples will focus on leveraging various APIs to connect merchants with potential customers and customers with information to make their research more effective. The first example will cover incorporating eBay user reviews into a site to provide customers with opinions about products they may be contemplating purchasing. The goal is to provide visitors with enough information to make a purchase decision, so they don’t need to leave your site in search of more. For those customers that have an immediate need, the next example will show how to incorporate local inventory levels to facilitate a “must have now” type of purchase. The last recipe will look at getting greater exposure of your products by using APIs to list items in the eBay marketplace, where literally thousands of people may discover these products and a merchant’s online business.

By incorporating the functionality provided by the APIs shown here, you should be able to minimize the length of time that a potential consumer needs to research a product in this initial stage, and broaden the scope of potential consumers who know of your offering. The aim is to allow more customers to discover your products and to ensure that they have at hand the necessary information to make a buying decision, rather than seeking an alternate source for that information.

1.1. Tapping Into Product Reviews and Guides

Opportunity

For many online and offline customers, purchasing decisions are affected by recommendations from their circles of contacts. These circles may be small (close networks of friends and family) or large (including online reviews from other consumers who may have already purchased the product or had an experience with a site). For the merchant, providing online reviews of products by purchasers is rapidly becoming an expectation, and for consumers, this is becoming a required piece of data for product selection. Fortunately, including consumer reviews with your product information is not as difficult as you might expect, and there are several established repositories of reviews that can be tapped.

Solution

The eBay Shopping developers API provides an API call called FindReviewsAndGuides that opens access to product reviews and buying guides created by eBay users, along with average rating data for any product in the eBay catalog. Passing an internal eBay product ID into the FindReviewsAndGuides call can retrieve the applicable reviews. This example will go through a sample flow of finding a product in the catalog that could match one that you provide and then displaying the average rating, the number of reviews, and a list of the most recent reviews.

Discussion

The eBay Shopping API is designed with a series of calls for searching items on eBay. In addition, the API includes calls to retrieve products from a stock catalog that eBay maintains. An overview of the entire eBay Shopping API can be found online in the Shopping API Guide.

To retrieve reviews for a product, you first need to know the internal eBay product ID for that specific item. eBay provides a Shopping API call called FindProducts to facilitate locating this product ID. A sample FindProducts XML request is in Example 1-1; it will perform a search for products that match the query string “Amazon+kindle+fire”.

Example 1-1. FindProducts request

<?xml version="1.0" encoding="utf-8"?>
<FindProductsRequest xmlns="urn:ebay:apis:eBLBaseComponents">
  QueryKeywords>Amazon+kindle+fire</QueryKeywords>
</FindProductsRequest>

This is the most basic use of the FindProducts call; other fields exist to limit and filter results by category, pages of results, and sort order. The QueryKeywords field has a minimum length of 3 characters and maximum length of 350 characters and allows for the use of wildcard characters (+, −, and *) for further refinement or enlargement of your search. The request will provide an XML response containing all matching products in corresponding product blocks, as seen in the sample response in Example 1-2.

Example 1-2. FindProducts response

<?xml version="1.0" encoding="UTF-8"?>
<FindProductsResponse xmlns="urn:ebay:apis:eBLBaseComponents">
  <Timestamp>2012-07-08T13:42:23.164Z</Timestamp>
  <Ack>Success</Ack>
  <Build>E781_CORE_BUNDLED_15030490_R1</Build>
  <Version>781</Version>
  <ApproximatePages>3</ApproximatePages>
  <MoreResults>true</MoreResults>
  <PageNumber>1</PageNumber>
  <Product>
    <DomainName>Tablets</DomainName>
    <DetailsURL>
      http://syicatalogs.ebay.com/ws/
      eBayISAPI.dll?PageSyiProductDetails&amp;IncludeAttributes=
      1&amp;ShowAttributesTable=1&amp;ProductMementoString=
      117984:2:21418:574021108:424383568:730a26cc1986d38949
      d2048fe33ce24e:1:1:1:5000000768188
    </DetailsURL>
    <DisplayStockPhotos>true</DisplayStockPhotos>
    <ProductID type="Reference">110592598</ProductID>
    <ReviewCount>383</ReviewCount>
    <StockPhotoURL>
      http://i.ebayimg.com/00/$(KGrHqR,!jgE5)db1gtTBOmNLBOggw~~_
      6.JPG?set_id=89040003C1
    </StockPhotoURL>
    <Title>Amazon Kindle Fire 8GB, Wi-Fi, 7in - Black</Title>
  </Product>
  <Product>
  ...
  ...
  <TotalProducts>3</TotalProducts>
</FindProductsResponse>

If the search finds more than 2,000 products, the API call will return an error and you should further refine the search.

Most businesses that deal with products will have some code as part of their inventory information that uniquely identifies a particular product, such as a UPC code or an ISBN number. With FindProducts, you can also use one of these unique identifiers to isolate and retrieve the internal eBay product ID. Instead of using the QueryKeywords property, you would use the ProductID property, providing an ID type as an attribute and the ID in the value of the XML node:

<ProductID type="ISBN">9780321769381</ProductID>

For a list of the product types allowed, see the FindProducts call reference page.

So you don’t have to program your entire solution to see if the API calls will provide the data you need and to verify that your requests are properly structured, eBay provides an API Test Tool, seen in Figure 1-2, through developer.ebay.com. You can use this tool not only to exercise the Shopping API calls with sample structures of XML requests, but also to exercise other API sets from eBay. The tool will take your request and execute the call selected, providing the response XML block. In this manner you can select and test your call flows prior to full implementation of the solution in code.

To execute any of the Shopping API calls, you will need to register with the eBay developer network and go to the My Account section to create and retrieve your application key sets. An application key set can be generated for a testing sandbox and for the production environment. The application key set will contain three IDs that are sent with your API request:

  • DevID

  • AppID

  • CertID

eBay API Test Tool

Figure 1-2. eBay API Test Tool

The AppID will be needed for the calls in this example, and a different set will need to be used based on whether you are making sandbox or production requests. When you have your application key sets, you can use the API Test Tool under Development Tools to validate the credentials. An easy test call to make is GeteBayTime, under the Shopping API. The call has a simple request format with no arguments passed in the XML block and will return the current eBay time for the site specified on the left side of the tool. The site ID represents the applicable eBay country site; for these examples, 0 will be used for the site ID, which corresponds to the United States.

Note

The eBay Shopping API has a request limit of 5,000 queries per day, per IP address. If you need a higher limit, you can apply for an increase by having your application certified through the Compatible Application Check program.

The first web page of our example will allow the user to enter a keyword or keywords to search for, in a form. The form will be posted to the same page and the FindProducts API call will be constructed and sent to the eBay API servers via a PHP curl command. The XML response block will then be parsed by looping through each product in the response, and the product name, image, and product ID for each will be retrieved. Each product will be displayed and linked to a second page, passing the product ID when selected. Example 1-3 has the code for the findProducts.php page.

Example 1-3. findProducts.php

<?php
/********************************************
findProducts.php

Uses FindProducts to retrieve list of
products based on keyword query.

********************************************/

// include our Shopping API constants
require_once 'shoppingConstants.php';

// check if posted
if (!empty($_POST)) {

  // grab our posted keywords and call helper function
  $query = $_POST['query'];
  $response = getFindProducts($query);

  // create a simple XML object from results
  $xmlResponse = simplexml_load_string($response);
}

// function to call the Shopping API FindProducts
function getFindProducts($query) {

  // create the XML request
  $xmlRequest  = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
  $xmlRequest .= "<FindProductsRequest
    xmlns=\"urn:ebay:apis:eBLBaseComponents\">";
  $xmlRequest .= "<QueryKeywords>" . $query . "</QueryKeywords>";
  $xmlRequest .= "<ProductSort>Popularity</ProductSort>";
  $xmlRequest .= "<SortOrder>Descending</SortOrder>";
  $xmlRequest .= "<MaxEntries>100</MaxEntries>";
  $xmlRequest .= "<HideDuplicateItems>true</HideDuplicateItems>";
  $xmlRequest .= "</FindProductsRequest>";

  // define our header array for the Shopping API call
  $headers = array(
    'X-EBAY-API-APP-ID:'.API_KEY,
    'X-EBAY-API-VERSION:'.SHOPPING_API_VERSION,
    'X-EBAY-API-SITE-ID:'.SITE_ID,
    'X-EBAY-API-CALL-NAME:FindProducts',
    'X-EBAY-API-REQUEST-ENCODING:'.RESPONSE_ENCODING,
    'Content-Type: text/xml;charset=utf-8'
  );

  // initialize our curl session
  $session  = curl_init(SHOPPING_API_ENDPOINT);

  // set our curl options with the XML request
  curl_setopt($session, CURLOPT_HTTPHEADER, $headers);
  curl_setopt($session, CURLOPT_POST, true);
  curl_setopt($session, CURLOPT_POSTFIELDS, $xmlRequest);
  curl_setopt($session, CURLOPT_RETURNTRANSFER, true);

  // execute the curl request
  $responseXML = curl_exec($session);

  // close the curl session
  curl_close($session);

  // return the response XML
  return $responseXML;
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>1-2 Find Products with FindProducts</title>
<style>
body {background: #fff; color: #000;
  font: normal 62.5%/1.5 tahoma, verdana, sans-serif;}
* {margin: 0; padding: 0;}
form {padding: 0 10px; width: 700px;}
legend {font-size: 2em; padding-left: 5px; padding-right: 5px;
  position: relative;}
fieldset {border: 1px solid #ccc; border-radius: 5px; padding: 10px;
  width: 320px;}
li {clear: both; list-style-type: none; margin: 0 0 10px;}
label, input {font-size: 1.3em;}
label {display: block; padding: 0 0 5px; width: 200px;}
input {background-position: 295px 5px; background-repeat: no-repeat;
  border: 2px solid #ccc; border-radius: 5px;
  padding: 5px 25px 5px 5px; width: 285px;}
input:focus {outline: none;}
input:invalid:required {background-image: url(asterisk.png);
  box-shadow: none;}
input:focus:invalid {background-image: url(invalid.png);
  box-shadow: 0px 0px 5px #b01212; border: 2px solid #b01212;}
input:valid:required {background-image: url(accept.png);
  border: 2px solid #7ab526;}
div label {width: 100%;}
div.product {float: left; border: 7px solid #ccc; border-radius: 5px;
  padding: 10px; margin: 10px; height: 150px; width: 200px;
  text-align: center; vertical-align: top;}
div.product:hover {background-color: #39F; border-color: #FF6;}
</style>
</head>
<body>
<div id="frmProduct">
  <!-- simple form for query keyword entry -->
  <form name="search" action="findProducts.php" method="post">
    <fieldset>
      <legend>Product Search</legend>
      <ol>
        <li>
          <label for="query">Keyword</label>
          <input autofocus required id="query" name="query"
            placeholder="Nook" />
        </li>
      </ol>
      <input type="submit" value="Search">
    </fieldset>
    <br/>
  </form>
</div>
<div id="container">
  <?php
    // result block creation if results from form being posted
    // check for valid XML response object
    if ($xmlResponse) {

      echo '<H1>Results for "' . $query . '". Select one.</H1>';

      // loop through each XML product node in the response
      foreach ($xmlResponse->Product as $product) {

        // display the image and title for each product
        // create link to reviews page with internal ProductID
        echo '<a href="showReviews.php?pid=' .
          $product->ProductID . '">';
        echo '<div class="product">';
        if (($product->DisplayStockPhotos)=='true') {
          echo '<img src="' . $product->StockPhotoURL . '" />';
        } else {
          echo '<img src="missing.png" style="height:70px;" />';
        }
        echo "<br/>";
        echo $product->Title;
        echo '</div></a>';
      }
    }
    else {
      // display message if no search results (not posted)
      echo "Enter a search keyword above.";
    }
  ?>
</div>
</body>
</html>

This example uses multiple API calls across pages, so the constants for the credentials have been placed in a separate file, shoppingConstants.php. These constants include the API key, URL endpoint, version number of the API being called, eBay site ID, and response encoding type, as seen in Example 1-4. You will need to replace the API key field in the shoppingConstants.php file with your AppID. The constants will be used in the headers of the curl request on the findProducts.php and review retrieval pages.

Example 1-4. shoppingConstants.php

<?php
/********************************************
shoppingConstants.php

Constants used for Shopping API calls.

********************************************/

// eBay developer API key
DEFINE("API_KEY","<YOUR_API_KEY>");

// eBay Shopping API constants
DEFINE("SHOPPING_API_ENDPOINT","http://open.api.ebay.com/shopping");
DEFINE("SHOPPING_API_VERSION",779);

// eBay site to use - 0 = United States
DEFINE("SITE_ID",0);

// response encoding format - XML
DEFINE("RESPONSE_ENCODING","XML");
?>

After placing the findProducts.php and shoppingConstants.php pages on your site and browsing to the findProducts.php page, type in a keyword to search for and submit the form. If the call is successful, you should see a chart of products, as displayed in Figure 1-3.

Resulting eBay product ID availability map

Figure 1-3. Resulting eBay product ID availability map

Now that the product IDs for each product have been retrieved, the reviews retrieval page needs to be added so that when a product is selected, the reviews and ratings for that product can be displayed.

Note

Remember that if you have the ISBN, EAN, or UPC code, you can skip the product search step and make the FindProducts call behind the scenes so that the reviews can be shown directly with the particular product. The keyword search form is used for the sample purpose of making the FindProducts API call.

The reviews retrieval page follows a similar flow to the findProducts.php page. Instead of a form, however, the showReviews.php page will take a product ID passed in the query string to create an XML request, as seen in Example 1-5, for the eBay Shopping API.

Example 1-5. FindReviewsAndGuides request

<?xml version="1.0" encoding="utf-8"?>
<FindReviewsAndGuidesRequest xmlns="urn:ebay:apis:eBLBaseComponents">
  <ProductID type="Reference">110592598</ProductID>
</FindReviewsAndGuidesRequest>

The resulting return XML block will contain summary items including the average rating and number of reviews available for the product, followed by the most current reviews in one or more <Review> XML blocks, as seen in Example 1-6.

Example 1-6. FindReviewsAndGuides response

<?xml version="1.0" encoding="UTF-8"?>
<FindReviewsAndGuidesResponsexmlns="urn:ebay:apis:eBLBaseComponents">
  <Timestamp>2012-07-08T13:45:31.555Z</Timestamp>
  <Ack>Success</Ack>
  <Build>E781_CORE_BUNDLED_15030490_R1</Build>
  <Version>781</Version>
  <ReviewCount>383</ReviewCount>
  <BuyingGuideCount>0</BuyingGuideCount>
  <ProductID type="Reference">110592598</ProductID>
  <ReviewsAndGuidesURL>
    http://search.reviews.ebay.com/
    Amazon-Kindle-Fire-8GB-Wi-Fi-7in-Black?fvcs=5918&amp;sopr=
    110592598&amp;upvr=2
  </ReviewsAndGuidesURL>
  <PageNumber>1</PageNumber>
  <TotalPages>77</TotalPages>
  <BuyingGuideDetails>
    <BuyingGuideHub>
      http://search.reviews.ebay.com/?satitle=
      Amazon+Kindle+Fire+8GB%2C+Wi-Fi%2C+7in+-+Black&amp;uqt=g
    </BuyingGuideHub>
  </BuyingGuideDetails>
  <ReviewDetails>
    <AverageRating>4.5</AverageRating>
    <Review>
      <URL>
        http://search.reviews.ebay.com/
        Amazon-Kindle-Fire-8GB-Wi-Fi-7in-Black?fvcs=5918&amp;sopr=
        110592598&amp;upvr=2
      </URL>
      <Title>
        Fair price should be $199. Just don't expect too m...
      </Title>
      <Rating>4</Rating>
      <Text>
        Kindle Fire is great
        ...
        ...
      </Text>
      <UserID>nopink2000</UserID>
      <CreationTime>2012-03-10T17:16:41.000Z</CreationTime>
    </Review>
    <Review>
      <URL>
        http://search.reviews.ebay.com/
        Amazon-Kindle-Fire-8GB-Wi-Fi-7in-Black?fvcs=5918&amp;sopr=
        110592598&amp;upvr=2
      </URL>
      <Title>Excellent buy.</Title>
      <Rating>5</Rating>
      <Text>
        I love my Kindle!
        ...
        ...

The showReviews.php page in Example 1-7 will display the summary product rating information and loop through the reviews, displaying the title, rating, author’s eBay username, and text of each review.

Example 1-7. showReviews.php

<?php
/********************************************
showReviews.php

Uses FindReviewsAndGuides to retrieve list of
most recent reviews.

Called by findProducts.php with ProductID.
********************************************/

// include our Shopping API constants
require_once 'shoppingConstants.php';

// check if called with query string
if (!empty($_GET)) {

  // get the product ID and call the helper function
  $pid = $_GET['pid'];
  $response = getFindReviewsAndGuides($pid);

  // create a simple XML object from results
  $xmlResponse = simplexml_load_string($response);
}

// function to call the Shopping API FindReviewsAndGuides
function getFindReviewsAndGuides($pid) {

  // create the XML request
  $xmlRequest  = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
  $xmlRequest .= "<FindReviewsAndGuidesRequest
    xmlns=\"urn:ebay:apis:eBLBaseComponents\">";
  $xmlRequest .= "<ProductID type=\"Reference\">" . $pid .
    "</ProductID>";
  $xmlRequest .= "</FindReviewsAndGuidesRequest>";

  // define our header array for the Shopping API call
  $headers = array(
    'X-EBAY-API-APP-ID:'.API_KEY,
    'X-EBAY-API-VERSION:'.SHOPPING_API_VERSION,
    'X-EBAY-API-SITE-ID:'.SITE_ID,
    'X-EBAY-API-CALL-NAME:FindReviewsAndGuides',
    'X-EBAY-API-REQUEST-ENCODING:'.RESPONSE_ENCODING,
    'Content-Type: text/xml;charset=utf-8'
  );

  // initialize our curl session
  $session  = curl_init(SHOPPING_API_ENDPOINT);

  // set our curl options with the XML request
  curl_setopt($session, CURLOPT_HTTPHEADER, $headers);
  curl_setopt($session, CURLOPT_POST, true);
  curl_setopt($session, CURLOPT_POSTFIELDS, $xmlRequest);
  curl_setopt($session, CURLOPT_RETURNTRANSFER, true);

  // execute the curl request
  $responseXML = curl_exec($session);

  // close the curl session
  curl_close($session);

  // return the response XML
  return $responseXML;
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>1-2 Show Reviews with FindReviewsAndGuides</title>
<style>
body {background: #fff; color: #000;
  font: normal 62.5%/1.5 tahoma, verdana, sans-serif;}
* {margin: 0; padding: 0;}
div#stats {font-size: 1.3em; font-weight: bold; margin: 10px;
  padding: 10px;}
div.reviewHeader {font-size: 1.3em; font-weight: bold;
  border: 7px solid #ccc; border-radius: 5px;
  padding: 10px 10px 20px 10px; margin: −17px 0px 10px −17px;
  width: 500px; vertical-align: top; background-color: #9FC}
div.review {border: 7px solid #ccc; border-radius: 5px; padding: 10px;
  margin: 10px; width: 500px; vertical-align: top;}
</style>
</head>
<body>
<div>
  <a href="findProducts.php">< Back</a>
</div>
<div>
  <?php
    // Result block creation if results from product ID
    // Check for valid XML response object
    if ($xmlResponse) {

      // display review count, guide count, and average rating
      echo '<H1>Reviews</H1>';
      echo '<div id="stats">';
      echo $xmlResponse->ReviewCount . ' Reviews<br/>';
      echo $xmlResponse->BuyingGuideCount . ' Guides<br/>';
      echo 'Average rating: ' .
        $xmlResponse->ReviewDetails->AverageRating;
      echo '</div>';

      // loop through each XML review node in the response
      foreach ($xmlResponse->ReviewDetails->Review as $review) {

        // display the title, userid, rating, and text for each
        // review based on internal ProductID
        echo '<div class="review">';
        echo '<div class="reviewHeader">';
        echo '<div>' . $review->Title . '</div>';
        echo '<div style="float:left;">' . $review->UserID . '</div>';
        echo '<div style="float:right;">
          <img src="stars' . $review->Rating . '.png"
            style="height:15px;"/></div>';
        echo '</div>';
        echo '<div style="clear:both;"></div>';
        echo '<div class="reviewText">';
        echo $review->Text;
        echo '</div>';
        echo '</div>';
      }
    }
    else {
      // display message if no reviews returned
      echo "No reviews found.";
    }
  ?>
</div>
</body>
</html>

Figure 1-4 shows the resulting screen for showReviews.php after having selected a product on the findProducts.php page.

eBay reviews for the product

Figure 1-4. eBay reviews for the product

This example has shown how the Shopping API provides an easy method to include social data on products in the form of ratings and reviews for potential customers to research. In addition, this information can be combined with other Shopping API calls to show lists of popular items, allowing visitors to discover new products.

1.2. Mapping Product Availability

Opportunity

One of the challenges of having a business storefront is attracting potential customers to what you carry and your product inventory. This results in a significant line item for marketing in any business budget. The Milo.com website provides visitors a means to search for products within a specific radius of their current location or another specified location. Not only can businesses link into this service and integrate their stores and inventory into the results of Milo.com, but also an API is available for integrating the data into your site for your own business needs.

Solution

In this example, we will go through the Milo Open API and its endpoints, specifically looking at the use of Milo product IDs and the Availability endpoint to access current inventory of an item within a certain radius of a location. This can provide a means to get your goods in front of online buyers, who can discover the availability of the product locally from you. For businesses, this API can also provide critical information for verification of your store’s inventory and competitive intelligence about other merchants and their pricing, products, and availability.

Discussion

The Milo Open API is focused on three areas of information: merchant locations, product information, and availability. The API is free to developers and has standard rate limits in place for usage. The default rate limit is 5,000 queries per API key per hour. Businesses or developers who hit the rate limit are encouraged to contact the Milo API group via email explaining their situation for possible rate increases. For more information, see the online Milo Open API documentation.

A per-domain developer API key is required to make calls to the Milo Open API platform. The key is included with your API requests from your pages to validate the origin of the requests. You can acquire a key by going to the X.com developer’s portal. If you do not have an account, you will need to create one. Once logged in, click “Manage Applications.” This will bring you to “My Applications.” Click the “Register Application” button to add a new application that will use the Milo Open API. A form will be displayed for registering your new application, as seen in Figure 1-5. Fill out the form, making sure to check the Milo offering for the API Scope. This will allow your application to access the Milo Open API from the domain that you specify in the form.

X.com new application registration

Figure 1-5. X.com new application registration

After submitting the form via the “Register Application” button, you will be provided with your API key, as shown in Figure 1-6. If you forget your API key, you can access it by logging into the developer’s portal and viewing your applications. Your API key will be listed with each application you have registered.

X.com completed registration for new application

Figure 1-6. X.com completed registration for new application

Now that you have an API key, try a call to one of the Milo Open API endpoints to verify that the key works. Open a browser and type in the following URL, replacing <YOUR_API_KEY> with the key for your application:

https://api.x.com/milo/v3/store_addresses?key=<YOUR_API_KEY>

If your API key is valid, you will see a list of store addresses returned in JSON format, similar to the one in Figure 1-7.

Milo store address results from API key test

Figure 1-7. Milo store address results from API key test

If your key is invalid, you will get a message saying that you need to register your application here.

Note

Note that the JSON return in Figure 1-7 and used throughout the book is nicely formatted for visual verification. The formatted output is available via a helpful extension to the Chrome browser called JSONView.

The test link used to validate your API key is hitting the first of the endpoints of the Milo Open API: the Store Addresses endpoint. This endpoint provides a list of store address blocks of information based on a list of merchant IDs (Milo IDs) or 10-digit phone numbers. The return information can include merchant information, latitude, longitude, hours of operation, and address information for the store.

The second Milo endpoint is the Products endpoint, which will return detailed product data including a list of merchants that carry the product, Milo category IDs, brand IDs, minimum and maximum pricing, image links, and the Milo product ID. Search requests can be performed on keywords or a range of other criteria, including UPC codes. To perform a test search, navigate to this endpoint in your browser with a URL like the one shown here:

https://api.x.com/milo/v3/products?q=nook&key=<YOUR_API_KEY>

Remember to substitute your key for <YOUR_API_KEY> and put in an appropriate product keyword for the q variable in the query string. To perform a search using a UPC code, use the format q=upc:<upc_code>, where the <upc_code> field is replaced with a corresponding numerical UPC code. The return for this call should look something like Figure 1-8.

Return from Products endpoint

Figure 1-8. Return from Products endpoint

The results from the Products endpoint will be paginated and contain a list of matching products and other information. The primary key is the internal Milo product ID, which will be used in the upcoming coding example. If you were presenting a solution to visitors, you would typically show the matching products and have the user select one to see the availability of that product in her area.

The last endpoint, Availability, is how we can access information about the availability of a product at particular stores in a given geographic area. The request takes a specific location, a radius, and a Milo product ID to isolate the local stores carrying that product and the availability levels. The result of the Availability endpoint calls is structured differently from that of the other endpoints, and may be slightly confusing. Unlike the other endpoints, which have a single request/response format, the response for the Availability endpoint returns data via chunked transfer encoding. That is, the data is returned in separate merchant, store, and inventory response blocks. An inventory block will follow the corresponding store block, which will follow a merchant block. There could be multiple store and inventory blocks for a single merchant, since a merchant could have multiple store locations. The connections of the blocks will be covered more with the web service call in the example.

To map the availability of a particular product (now that we have the Milo product ID from the Products endpoint), open a blank HTML file and paste in the code from Example 1-8.

Example 1-8. productAvail.html product availability map

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>1-3 Mapping Product Availability with Milo</title>
<script
  src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js">
</script>
<style>
body {background: #fff; color: #000;
  font: normal 62.5%/1.5 tahoma, verdana, sans-serif;}
* {margin: 0; padding: 0;}
#container {width: 500px;}
#mapCanvas {width: 500px; height: 300px; border: 1px solid #ccc;
  border-radius: 5px; margin: 22px 10px; padding:10px;}
form {padding: 0 10px; width: 700px;}
legend {font-size: 2em; padding-left: 5px; padding-right: 5px;
  position: relative;}
fieldset {border: 1px solid #ccc; border-radius: 5px; padding: 10px;
  width: 320px;}
li {clear: both; list-style-type: none; margin: 0 0 10px;}
label, input {font-size: 1.3em;}
label {display: block; padding: 0 0 5px; width: 200px;}
input {background-position: 295px 5px; background-repeat: no-repeat;
  border: 2px solid #ccc; border-radius: 5px;
  padding: 5px 25px 5px 5px; width: 285px;}
input:focus {outline: none;}
input:invalid:required {background-image: url(asterisk.png);
  box-shadow: none;}
input:focus:invalid {background-image: url(invalid.png);
  box-shadow: 0px 0px 5px #b01212; border: 2px solid #b01212;}
input:valid:required {background-image: url(accept.png);
  border: 2px solid #7ab526;}
input[type=range]:before{content: "1";}
input[type=range]:after{content: "100";}
div#range label {font-weight: bold;}
output {font-size: 1.3em; font-weight: bold; display: block;
  text-align: center;}
div label {width: 100%;}
</style>
<script src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script>
// global map reference variable
var map;

// initialize the page
function init() {

  // load and initialize map with default location
  var myLatlng = new google.maps.LatLng(33.85095,-84.2075);
  var myOptions = {
    zoom: 10,
    center: myLatlng,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  }
  map = new google.maps.Map(document.getElementById("mapCanvas"),
    myOptions);


  // add the button click listener
  var btnFindProduct = document.getElementById('findProduct');
  btnFindProduct.addEventListener('click',findProduct,false);

}  // end init

// function to call AJAX helper for retrieving availability
function findProduct() {

  // set helper URL location
  var wsUrl = "ajax/wsMiloAvailability.php";

  // grab values from form for product ID search
  var inProduct = document.getElementById('product').value;
  var inZip = document.getElementById('zip').value;
  var inRadius = document.getElementById('radius').value;

  // encapsulate fields for query string
  var params = {
    product_id: inProduct,
    postal_code: inZip,
    radius: inRadius
  };

  // make AJAX call
  $.ajax({ url: wsUrl, data: $.param(params), success: processData});

} // end findProduct

// function to process the availability data received via AJAX
function processData(data) {

  var merName;
  var merImg;
  var storeLat;
  var storeLng;
  var storeStock;

  // convert JSON data into JSON object
  var obj = jQuery.parseJSON(data);

  // TODO: should handle if no data or error returned

  // loop through each merchant returned
  $.each(obj, function(i, merchant) {

    // grab merchant name and image
    merName = this['name'];
    merImg = this['image_url'];

    // loop through each store location of merchant
    $.each(merchant, function() {

      // simple check if a store object
      if (this['id']) {

        // grab the store location information and availability
        storeLat = this['latitude'];
        storeLng = this['longitude'];
        storeStock = this['availability'];

        // create the marker with the data
        createMarker(merName, merImg, storeLat, storeLng, storeStock)
      }
    });
  });

  // pan to the marker displayed
  map.panTo(mkrLatLng);


} // end processData

// function to create each map marker based on passed data
function createMarker(mkrName, mkrImg, mkrLat, mkrLng, mkrStock) {

  /* Milo availbility values include:
     in_stock     Currently available for purchase
     out_of_stock Currently not available for purchase
     limited      Currently available though stock may be low
     carries      The product is sold at the store but availability
                  is unknown
     likely       Likely to be available but not certain
     never        The store does not carry the product
     call         The store should be contacted for availability
                  information
  */

  // check availabilty and set icon to merchant image if available
  var mkrIcon = '';
  if (mkrStock == 'in_stock' || mkrStock == 'limited') {
    mkrIcon = mkrImg;
  }

  // create the marker based on Latitude and Longitude
  var mkrLatLng = new google.maps.LatLng(mkrLat, mkrLng);
  var marker = new google.maps.Marker({
    position: mkrLatLng,
    map: map,
    icon: mkrIcon
  });

  // set the info window content and click listener to display
  var infowindow = new google.maps.InfoWindow();
  google.maps.event.addListener(marker, 'click', (function(marker) {
    return function() {
      infowindow.setContent('<img src="' + mkrImg + '" />' + mkrName
        + '<br/>' + mkrStock);
      infowindow.open(map, marker);
    }
  })(marker));

}  // end createMarker

// initialize the page
window.addEventListener('load',init,false);

</script>
</head>
<body>
<div id="container">
  <div id="mapCanvas"></div>
  <div id="productForm">
    <form>
      <fieldset>
      <legend>Product Search</legend>
      <ol>
        <li>
          <label for="product">Product ID (Milo ID)</label>
          <input autofocus required id="product" name="product"
            value="6754308" />
        </li>
        <li>
          <label for="zip">Zip Code</label>
          <input required id="zip" name="zip" value="30345" />
        </li>
        <li>
          <label for="radius">Radius (1-100 miles)</label>
          <input required id="radius" name="radius" min="1" max="100"
            value="10" />
        </li>
      </ol>
      </fieldset>
      <br/>
      <button id="findProduct" name="findProduct" type="button">
        Locate Product
      </button>
    </form>
  </div>
</div>
</body>
</html>

This page will allow the user to enter a product ID, zip code, and radius in a form. The radius parameter is a range for the search, between 1 and 100 miles. After submitting the form, the page will make a request to a helper web service we will create, which will in turn call the Milo Availability endpoint, get the results, merge them into a single JSON block, and return the block to the HTML page. Once received, the page will map the locations on an embedded Google map, as seen in Figure 1-9. If a store has stock, the page will show the store logo image as the marker. If there is no stock, the marker will be a regular map marker. If you click on the marker, an info window will appear with the merchant logo and a stock count. You could add the store hours, address, and other information from the results as desired.

To assist with the chunked data that is returned from the Availability call, a PHP helper web service has been implemented in this example. The PHP file takes the parameters set in the page, adds them to a request to the Availability endpoint, and executes the call, as if a file were being opened via a URL so that the return can be handled as a stream. Each chunk of data received is a block or line, which is then handled according to the type of data in the block: merchant, location, or availability. The blocks are added as arrays into a master array, which is then encoded in JSON and returned to the HTML page. The code for the PHP web service helper is in Example 1-9.

Resulting product ID availability map

Figure 1-9. Resulting product ID availability map

Example 1-9. wsMiloAvailability.php web service helper

<?php

/********************************************
wsMiloAvailability.php
Web service helper for accessing Milo Open
API Availabilty endpoint.

API key required for your domain.
Calls  caller.php and APIError.php.
********************************************/

DEFINE("API_KEY","<YOUR_API_KEY>");
DEFINE("AVAIL_ENDPOINT","https://api.x.com/milo/v3/availability");

// set the URL to call
$url = AVAIL_ENDPOINT . "?key=" . API_KEY . "&" .
  $_SERVER['QUERY_STRING'];

// Use a file stream to handle availability chunking of the
// return information. We will loop through all the chunks
// received and collapse to one JSON block.
$file_handle = fopen($url, "r");
if ($file_handle) {

  // array to hold new block for client
  $arr_json = array();

  while (!feof($file_handle)) {

    // get the next line from the file stream
    $line = fgets($file_handle);

    // decode the chunk or line we just received
    $jsonLine = json_decode($line, true);

    // determine chunk type and handle
    switch (key($jsonLine)) {

      // received a merchant chunk
      case "merchant":
        // get the merchant and hold the merchant JSON
        $merchant_id = (string)$jsonLine['merchant']['id'];
        $arr_json[$merchant_id] = $jsonLine["merchant"];
        break;

      // received a store location chunk
      case "location":
        // get the location and add under merchant
        $location_id = (string)$jsonLine['location']['id'];
        $merchant_id = (string)$jsonLine['location']['merchant_id'];
        $arr_json[$merchant_id][$location_id] = $jsonLine["location"];
        break;

      // received the availability chunk
      case "result":
        // get the result and just merge with location stored
        $arr_json[$merchant_id][$location_id] += $jsonLine["result"];
        break;
    }
  }
  if (!feof($file_handle)) {
    echo "Error: unexpected fgets() fail\n";
  }

  // close our stream handler
  fclose($file_handle);

  // send the new JSON block down to the client
  echo json_encode($arr_json);

}
?>

This is but one method for dealing with the chunked data return. Other options include using JSONP, which is supported by the Milo API. However, by including a helper web server you can also filter the results as needed and track the usage by your visitors.

As a business that is trying to get exposure to online customers, integrating and using the Milo API provides another avenue of reaching potential customers searching online: the customer can see that a storefront right around the corner has the product in stock, and see the current price. This can be compelling if the customer doesn’t want to wait for (or pay for) the product to be shipped.

Note

A business can easily integrate and expose locations and inventories on Milo and third-party applications that use the Milo Open API by using a service called Milo Fetch. At the time of writing, the service is in beta form. It integrates with several third-party business administration and point-of-sale software packages, including Intuit QuickBooks Point of Sale, Pro, Premier, and Enterprise editions. See http://pointofsale.milo.com for more information.

1.3. Presenting Products Through eBay

Opportunity

When potential customers are searching online for a product, the main challenge a merchant faces is having its product offerings exposed and presented to those customers. Most of the time merchants rely on indexing of their sites and keywords to hopefully be mapped to online searches performed by people browsing the Internet. The more places that merchants can get their products indexed and listed, the greater potential there is for discovery by potential customers.

Solution

With a little effort, merchants can manually post products for auction and purchase on eBay by signing up for a seller account. This provides an opportunity for eBay users to discover the products. However, this is a manual process that is not integrated into the merchant’s backend system. This example will show how an item can be added to eBay’s marketplace by using the eBay Trading API and some simple calls. This model could then be employed to either automate or streamline the process.

Discussion

The eBay Trading API is designed to work both in a sandbox and a production environment to search for items, retrieve category information, and add items. In this case we will use the AddItem call to programmatically add an item, with fields passed in an XML request that mimics adding an item manually. There are several fields in the AddItem request that are required, and additional fields that can be included based on the type of listing. eBay provides a full documentation set on the AddItem call, available in the eBay Trading API Call Reference.

This example will present the user with a brief form in which to enter the title of the item, the eBay category ID, the starting price, a URL of the item’s picture, and a description, as seen in Figure 1-10. These are the core fields of an item listing.

Add eBay item form

Figure 1-10. Add eBay item form

The additem.php page, which presents this form, will take the submitted fields and place them in an XML block to pass over in the AddItem call. In addition to these fields, there are a number of other fields that get included in the XML request. In this case, these fields have been left in the XML block and not brought into the form. When adapting this example to your needs, however, you may wish to change some of these fields, include others, or even allow the user to select the AddItem call fields in the form.

The eBay call reference page for the AddItem call describes each of the fields in detail and their possible values. For example, in the XML block of the addItem.php page you will find ConditionID, which is a numerical value representing the condition of the item (1000 is equal to “New” while 5000 is “Good”). For more information on working with the ConditionID field, you can reference the eBay document “Specifying an Item’s Condition”. You could let the user select from a drop-down or choose a different value automatically. In this example, the item will be listed in an auction format, or in eBay terms as a “Chinese” auction, which is the standard auction type for eBay. You could change this to any of the other listing types that eBay provides; the full list of eBay listing types can be viewed in the eBay Trading API Call Reference online. Example 1-10 shows the addItem.php code.

Example 1-10. addItem.php

<?php
/********************************************
addItem.php

Uses eBay Trading API to list an item under
a seller's account.

********************************************/

// include our Trading API constants
require_once 'tradingConstants.php';

// check if posted
if (!empty($_POST)) {

  // grab our posted keywords and call helper function
  // TODO: check if need urlencode
  $title = $_POST['title'];
  $categoryID = $_POST['categoryID'];
  $startPrice = $_POST['startPrice'];
  $pictureURL = $_POST['pictureURL'];
  $description = $_POST['description'];

  // call the getAddItem function to make AddItem call
  $response = getAddItem($title, $categoryID, $startPrice, $pictureURL,
    $description);

}

// function to call the Trading API AddItem
function getAddItem($addTitle, $addCatID, $addSPrice, $addPicture,
  $addDesc) {

  // create unique ID for adding item to prevent duplicate adds
  $uuid = md5(uniqid());

  // create the XML request
  $xmlRequest  = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
  $xmlRequest .= "<AddItemRequest
    xmlns=\"urn:ebay:apis:eBLBaseComponents\">";
  $xmlRequest .= "<ErrorLanguage>en_US</ErrorLanguage>";
  $xmlRequest .= "<WarningLevel>High</WarningLevel>";
  $xmlRequest .= "<Item>";
  $xmlRequest .= "<Title>" . $addTitle . "</Title>";
  $xmlRequest .= "<Description>" . $addDesc . "</Description>";
  $xmlRequest .= "<PrimaryCategory>";
  $xmlRequest .= "<CategoryID>" . $addCatID . "</CategoryID>";
  $xmlRequest .= "</PrimaryCategory>";
  $xmlRequest .= "<StartPrice>" . $addSPrice . "</StartPrice>";
  $xmlRequest .= "<ConditionID>1000</ConditionID>";
  $xmlRequest .= "<CategoryMappingAllowed>true
    </CategoryMappingAllowed>";
  $xmlRequest .= "<Country>US</Country>";
  $xmlRequest .= "<Currency>USD</Currency>";
  $xmlRequest .= "<DispatchTimeMax>3</DispatchTimeMax>";
  $xmlRequest .= "<ListingDuration>Days_7</ListingDuration>";
  $xmlRequest .= "<ListingType>Chinese</ListingType>";
  $xmlRequest .= "<PaymentMethods>PayPal</PaymentMethods>";
  $xmlRequest .= "<PayPalEmailAddress>yourpaypal@emailaddress.com
    </PayPalEmailAddress>";
  $xmlRequest .= "<PictureDetails>";
  $xmlRequest .= "<PictureURL>" . $addPicture . "</PictureURL>";
  $xmlRequest .= "</PictureDetails>";
  $xmlRequest .= "<PostalCode>05485</PostalCode>";
  $xmlRequest .= "<Quantity>1</Quantity>";
  $xmlRequest .= "<ReturnPolicy>";
  $xmlRequest .= "<ReturnsAcceptedOption>ReturnsAccepted
    </ReturnsAcceptedOption>";
  $xmlRequest .= "<RefundOption>MoneyBack</RefundOption>";
  $xmlRequest .= "<ReturnsWithinOption>Days_30</ReturnsWithinOption>";
  $xmlRequest .= "<Description>" . $addDesc . "</Description>";
  $xmlRequest .= "<ShippingCostPaidByOption>Buyer
    </ShippingCostPaidByOption>";
  $xmlRequest .= "</ReturnPolicy>";
  $xmlRequest .= "<ShippingDetails>";
  $xmlRequest .= "<ShippingType>Flat</ShippingType>";
  $xmlRequest .= "<ShippingServiceOptions>";
  $xmlRequest .= "<ShippingServicePriority>1
    </ShippingServicePriority>";
  $xmlRequest .= "<ShippingService>USPSMedia</ShippingService>";
  $xmlRequest .= "<ShippingServiceCost>2.50</ShippingServiceCost>";
  $xmlRequest .= "</ShippingServiceOptions>";
  $xmlRequest .= "</ShippingDetails>";
  $xmlRequest .= "<Site>US</Site>";
  $xmlRequest .= "<UUID>" . $uuid . "</UUID>";
  $xmlRequest .= "</Item>";
  $xmlRequest .= "<RequesterCredentials>";
  $xmlRequest .= "<eBayAuthToken>" . AUTH_TOKEN . "</eBayAuthToken>";
  $xmlRequest .= "</RequesterCredentials>";
  $xmlRequest .= "<WarningLevel>High</WarningLevel>";
  $xmlRequest .= "</AddItemRequest>";

  // Define our header array for the Trading API call
  // Notice different headers from shopping API and SITE_ID
  // changes to SITEID
  $headers = array(
    'X-EBAY-API-SITEID:'.SITEID,
    'X-EBAY-API-CALL-NAME:AddItem',
    'X-EBAY-API-REQUEST-ENCODING:'.RESPONSE_ENCODING,
    'X-EBAY-API-COMPATIBILITY-LEVEL:' . API_COMPATIBILITY_LEVEL,
    'X-EBAY-API-DEV-NAME:' . API_DEV_NAME,
    'X-EBAY-API-APP-NAME:' . API_APP_NAME,
    'X-EBAY-API-CERT-NAME:' . API_CERT_NAME,
    'Content-Type: text/xml;charset=utf-8'
  );

  // initialize our curl session
  $session  = curl_init(API_URL);

  // set our curl options with the XML request
  curl_setopt($session, CURLOPT_HTTPHEADER, $headers);
  curl_setopt($session, CURLOPT_POST, true);
  curl_setopt($session, CURLOPT_POSTFIELDS, $xmlRequest);
  curl_setopt($session, CURLOPT_RETURNTRANSFER, true);

  // execute the curl request
  $responseXML = curl_exec($session);

  // close the curl session
  curl_close($session);

  // return the response XML
  return $responseXML;
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>1-4 Add Item to eBay using eBay Trading API</title>
<style>
body {background: #fff; color: #000;
  font: normal 62.5%/1.5 tahoma, verdana, sans-serif;}
* {margin: 0; padding: 0;}
form {padding: 0 10px; width: 700px;}
legend {font-size: 2em; padding-left: 5px; padding-right: 5px;
  position: relative;}
fieldset {border: 1px solid #ccc; border-radius: 5px; padding: 10px;
  width: 320px;}
li {clear: both; list-style-type: none; margin: 0 0 10px;}
label, input {font-size: 1.3em;}
label {display: block; padding: 0 0 5px; width: 200px;}
input {background-position: 295px 5px; background-repeat: no-repeat;
  border: 2px solid #ccc; border-radius: 5px;
  padding: 5px 25px 5px 5px; width: 285px;}
input:focus {outline: none;}
input:invalid:required {background-image: url(asterisk.png);
  box-shadow: none;}
input:focus:invalid {background-image: url(invalid.png);
  box-shadow: 0px 0px 5px #b01212; border: 2px solid #b01212;}
input:valid:required {background-image: url(accept.png);
  border: 2px solid #7ab526;}
div label {width: 100%;}
</style>
</head>
<body>
<div id="frmProduct">
  <!-- simple form for query keyword entry -->
  <form name="addItem" action="addItem.php" method="post">
    <fieldset>
    <legend>Add Item</legend>
    <ol>
      <li>
        <label for="title">Title</label>
        <input autofocus required id="title" name="title"
          value="Great Black Headphones" maxlength="80" />
      </li>
      <li>
        <label for="categoryID">Category ID</label>
        <input required id="categoryID" name="categoryID"
        value="112529"/>
      </li>
      <li>
        <label for="startPrice">Start Price</label>
        <input required id="startPrice" name="startPrice"
          value="20.00"/>
      </li>
      <li>
        <label for="pictureURL">Picture URL</label>
        <textarea rows="4" cols="40" required id="pictureURL"
          name="pictureURL">http://www.monsterproducts.com/images_db/
          mobile/MH_BTS_ON-SOHD_BK_CT_glam.jpg</textarea>
      </li>
      <li>
        <label for="description">Description</label>
        <textarea rows="4" cols="40" required id="description"
          name="description">A great pair of brand new black
          headphones - one for each ear.</textarea>
      </li>
    </ol>
    <input type="submit" value="Add Item">
    </fieldset>
    <br/>
  </form>
</div>
<div id="container">
  <?php
    // display information to user based on AddItem response

    // convert the XML response string in an XML object
    $xmlResponse = simplexml_load_string($response);

    // verify that the XML response object was created
    if ($xmlResponse) {

      // check for call success
      if ($xmlResponse->Ack == "Success") {

        // display the item ID number added
        echo "<p>Successfully added item as item #" .
          $xmlResponse->ItemID . "<br/>";

        // calculate fees for listing
        // loop through each Fee block in the Fees child node
        $totalFees = 0;
        $fees = $xmlResponse->Fees;
        foreach ($fees->Fee as $fee) {
          $totalFees += $fee->Fee;
        }
        echo "Total Fees for this listing: " . $totalFees . ".</p>";

      } else {

        // unsuccessful call, display error(s)
        echo "<p>The AddItem called failed due to the following
          error(s):<br/>";
        foreach ($xmlResponse->Errors as $error) {
          $errCode = $error->ErrorCode;
          $errLongMsg = htmlentities($error->LongMessage);
          $errSeverity = $error->SeverityCode;
          echo $errSeverity . ": [" . $errCode . "] " .
            $errLongMsg . "<br/>";
        }
        echo "</p>";
      }

    }
  ?>
</div>
</body>
</html>

To get the appropriate eBay category ID that is required for adding an item, you can use the GetCategories Trading API call [and test it with the API Test Tool, as described in Recipe 1.1]. In this example, the appropriate category ID has been included directly in the XML request. This is fine if you are listing only one type of item that always fits into a single category, but you will most likely want to incorporate the GetCategories call into your own solution.

In addition to the fields already mentioned as required, there are two other fields worth describing in the AddItem call. The first is a universally unique ID, or UUID. For the AddItem call, this UUID field takes a 32-hex-character string. This value must be different for each AddItem call that is made. The purpose of this field is to prevent duplication of calls, which could add the same item more than once. In the example here, a simple MD5 hash of the output of the uniqid PHP function is used to produce this ID.

The last field that will be specific to your call is the eBayAuthToken, and it is critical to the request. The auth token represents the authorization of the eBay user on whose behalf this call is made in your application to access eBay data. To get an auth token for a single user—for example, if you will be adding items for only a single merchant in-house or you are testing in the sandbox—you can use the User Token Tool in the eBay Developer Tools. With this tool, you can generate an auth token for a single user in the sandbox or production environment and create a sandbox user to test with, as seen in Figure 1-11.

Note

When creating a sandbox user, use a real email address to which you have access. eBay listing confirmation messages from the sandbox environment will be sent to this email address.

Creating an eBay user token

Figure 1-11. Creating an eBay user token

Note

If you are creating an item-listing tool, you will need to get the auth token programmatically for the eBay user using your application. For more information on programmatically acquiring a user’s authorization and fetching the token for your application, see the online eBay Trading API tutorial, “Getting Tokens”.

After getting your auth token, place the token string and app credentials in the tradingConstants.php file. These constants are used in the HTTP headers and the XML request for the addItem.php page. Example 1-11 shows the tradingConstants.php file with sample sandbox credentials set.

Example 1-11. tradingConstants.php

<?php
/*********************************************
tradingConstants.php

Constants used for Trading API calls.
Replace keys and tokens with your information.

*********************************************/

// eBay site to use - 0 = United States
DEFINE("SITEID",0);

// production vs. sandbox flag - true=production
DEFINE("FLAG_PRODUCTION",false);

// eBay Trading API version to use
DEFINE("API_COMPATIBILITY_LEVEL",779);

/* Set the Dev, App, and Cert IDs.
Create these on developer.ebay.com.
Check if need to use production or sandbox keys. */
if (FLAG_PRODUCTION) {

  // PRODUCTION
  // set the production URL for Trading API calls
  DEFINE("API_URL",'https://api.ebay.com/ws/api.dll');

  // set production credentials (from developer.ebay.com)
  DEFINE("API_DEV_NAME",'<YOUR_PRODUCTION_DEV_ID>');
  DEFINE("API_APP_NAME",'<YOUR_PRODUCTION_APP_ID>');
  DEFINE("API_CERT_NAME",'<YOUR_PRODUCTION_CERT_ID>');

  // set the auth token for the user profile used
  DEFINE("AUTH_TOKEN",'<YOUR_PRODUCTION_TOKEN>');

} else {

  // SANDBOX
  // set the sandbox URL for Trading API calls
  DEFINE("API_URL",'https://api.sandbox.ebay.com/ws/api.dll');

  // set sandbox credentials (from developer.ebay.com)
  DEFINE("API_DEV_NAME",'48242bdb-6e6e-4a84-bba9-aaaaaaaaaaaa');
  DEFINE("API_APP_NAME",bbbbbbbbb-ff13-407f-993c-3fd6de0e3c27');
  DEFINE("API_CERT_NAME",'45daa688-54ec-cccc-cccc-b72247092fba');

  // set the auth token for the user profile used
  DEFINE("AUTH_TOKEN",'AgAAAA**AQAAAA**aAAAAA**8L/9Tw**nY+sHZ2PrBmd
    j6wVnY+sEZ2PrA2dj6wFk4GhCZCLow6dj6x9nY+seQ**7bgAAA**AAMAAA**MrJ
    m8FW/aMCMHEJUpxoPu3lbx3moJHLrO6E3dIJmN6Y7ljWtD0EMM/TKSS26K1IaSp
    Z4JR4pZ5YelGOS5z571BsPwokbcdcy/G4wDHxF3DWXfh8uEUY3j/R4VOY2h4VzU
    9sNLl6iXdMRvtm9Td7M4artDSiecqiR1JUv+Oy3OSI5XevHGY0RSEDt+...');
}
?>

After the form on the addItem.php page is submitted with the basic listing information, the AddItem Trading API call is made using the merged fields in the request XML block. The AddItem call will return an XML block similar to the one shown in Example 1-12 if successful. The return XML will contain an Ack element to flag whether the call was a Success or had a Failure. If a failure occurred, the return XML will contain the error causing the failure. In a successful call, summary information will be contained in the response, including the resulting eBay item ID and start and end times for the listing. In addition, the return block will include a section of fees representing the individual costs for the added item.

Example 1-12. Sample AddItem return XML

<?xml version="1.0" encoding="UTF-8"?>
<AddItemResponse xmlns="urn:ebay:apis:eBLBaseComponents">
  <Timestamp>2012-07-12T00:01:43.177Z</Timestamp>
  <Ack>Success</Ack>
  <Version>779</Version>
  <Build>E779_CORE_BUNDLED_15043314_R1</Build>
  <ItemID>110100620547</ItemID>
  <StartTime>2012-07-12T00:01:42.817Z</StartTime>
  <EndTime>2012-07-19T00:01:42.817Z</EndTime>
  <Fees>
    <Fee>
      <Name>AuctionLengthFee</Name>
      <Fee currencyID="USD">0.0</Fee>
    </Fee>
    <Fee>
      <Name>BoldFee</Name>
      ...
      ...
    <Fee>
      <Name>MotorsGermanySearchFee</Name>
      <Fee currencyID="USD">0.0</Fee>
    </Fee>
  </Fees>
  <DiscountReason>SpecialOffer</DiscountReason>
</AddItemResponse>

The addItem.php page will check the return XML for success via the Ack field, and if it’s found to be successful, will display the item ID and the total fees paid to list the item. If this functionality were being included in an automated listing application, the item ID, fees, and start and end times returned could be stored for further automation and viewing.

Figure 1-12 shows the resulting listing for our sample item.

Resulting eBay listing

Figure 1-12. Resulting eBay listing

To manually validate that the item was listed, you can search for the item ID returned on the addItem.php page on the eBay site. The GetItem Trading API call can also be used to present the same item details to the user.

With some simple programming, you can make a custom entry form for your staff to add products more easily, integrate your inventory into the eBay site, and even do batches of listings, potentially creating more traffic for your site and boosting sales of your products.

1.4. Conclusion

The first step of the commerce lifecycle—product discovery and research—can be one of the most difficult to get past since customers have so many options for finding goods on the Internet. The examples contained in this chapter show the possibilities of providing product reviews inline, displaying local purchase options if available, and adding products to eBay programmatically. By incorporating features such as these into your own commerce practices, you can increase the number of users who start the commerce lifecycle and the number of people who move on to the second step of the simplified commerce lifecycle described here.

Get eBay Commerce Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.