Integrating eBay sales with a Google App Engine shopping cart - Part 2

By Peter Georgeson

Overview

This series describes how to integrate an existing shopping cart with eBay using eBay's developer API.

This is Part 2 and covers how to programmatically list an item for sale on eBay, and how to register to receive events regarding an item's status. This enables eBay sales to be integrated into an existing shopping cart.

This article assumes you have already configured your shopping cart and eBay developer account. For the details on how to do this, refer to Part 1 of this tutorial.

Experimenting with the eBay API

Although eBay provides comprehensive documentation for its API calls, it is a significant API and it is easy to get bogged down with details. Fortunately, eBay provide an API Test Tool which provides a basic working example for most API calls.

If you are experimenting with different API calls, or want a basic template, this tool is recommended for getting something working quickly before delving into the documentation for specifics.

Listing an item on eBay

When an item is listed on the app engine shopping cart, we also want to add it to eBay. This is reasonably straight-forward.

A new setting has been added to settings.py: USE_EBAY. Set this to True to invoke the eBay integration functionality.

Now in the main.Sell class, when an item is first listed for sale, it is also added for sale on eBay:


if settings.USE_EBAY: # ebay specific
  add = ebay.Add(item, self.request.host_url)
  item.put()

The ebay.Add class builds an XML-based request, based on the template defined in templates/ebay_add.xml.

A unique ID is generated for the item, which allows this item to be referenced later. The request headers specify the API version, identification keys and the API call. The request content is XML and includes details of the item to sell.


item.uuid = uuid.uuid4().hex

headers = {
  'X-EBAY-API-COMPATIBILITY-LEVEL': '731',
  'X-EBAY-API-DEV-NAME': settings.EBAY_DEVID,
  'X-EBAY-API-APP-NAME': settings.EBAY_APPID,
  'X-EBAY-API-CERT-NAME': settings.EBAY_CERTID,
  'X-EBAY-API-SITEID': '0',
  'X-EBAY-API-CALL-NAME': 'AddItem',
}

data = {
  'title': item.title,
  'description': item.description,
  'category': 1000,
  'price': item.price_dollars(),
  'paypal_email': settings.PAYPAL_EMAIL,
  'image_url': "%s/image/%s/" % ( host_url, item.key() ),
  'uuid': item.uuid,
  'ebay_token': settings.EBAY_AUTHTOKEN
}

The request is made using App Engine's urlfetch library and success is evaluated by checking to see if the eBay response contains an item ID:


  def success(self):
    '''was the item added?'''
    return ( self.raw_response.find( '<ItemID>' ) != -1 )

Voilà! The item is now for sale on eBay.

Tracking an item listed on eBay

The next step is to tell eBay that the application wishes to be notified of changes to the status of listed items. As with PayPal's notification framework (IPN), eBay's system requires an accessible URL to send notifications to. Notifications should be implemented using the deployed application rather than the local application, otherwise eBay will not be able to reach your application with its notifications.

To enable eBay notifications, ensure settings.USE_EBAY is True. Deploy the application, then visit http://YOUR_HOST/config.

This calls the ebay.SetNotifications class, which creates a request based on templates/ebay_set_notifications.xml. The request enables or disables notifications based on the value of settings.USE_EBAY, using the SetNotificationPreferences API call:


data = {
  'enable': enable_status,
  'notification_url': "%s/notification" % host_url,
  'ebay_token': settings.EBAY_AUTHTOKEN
}

In templates/ebay_set_notifications.xml, the application registers to receive the ItemClosed and ItemSold events:


<UserDeliveryPreferenceArray>
  <NotificationEnable>
    <EventType>ItemClosed</EventType>
    <EventEnable>{{ enable }}</EventEnable>
  </NotificationEnable>
  <NotificationEnable>
    <EventType>ItemSold</EventType>
    <EventEnable>{{ enable }}</EventEnable>
  </NotificationEnable>
</UserDeliveryPreferenceArray>

For full details of the request format, refer to the SetNotificationPreferences API documentation. There are a large range of events that can be listened for in addition to the ItemClosed and ItemSold events - refer to eBay's event types documentation for details.

A successful request will return a response containing <Ack>Success</Ack>, which is tested for in the success() method.

Once the application is successfully registered for notifications, whenever an auction ends on an item listed by the application, including if it fails to sell, a notification will be sent to http://YOUR_HOST/notification.

Registering for notifications from eBay

To test the processing of notifications, assuming they were enabled earlier, you first need to list an item for sale on eBay. Do this at http://YOUR_HOST/sell.

If the item is successfully listed by your seller account, you will receive a notification email.
eBay email notification

The next step is to buy this item, but you can't buy your own item, so another test account needs to be created on the eBay sandbox. After creating the buyer account, click on the link in the email to go directly to the item for sale, and log in using the newly created eBay account.
eBay item for sale

If you proceed to purchase this item using PayPal, you will be directed to the PayPal sandbox. If you have not done so already, you will need to create a PayPal sandbox account, and within that, a PayPal personal account. Log in to your main sandbox account - in another browser tab if necessary - then purchase the item using the created PayPal personal account.
eBay item purchased

All going well, eBay will notify your application that the item has been purchased. You can check that the notification arrived by checking the app engine logs. To check the application logs:

  1. Visit http://appengine.google.com/;
  2. Select the appropriate application;
  3. Select the logs menu item; and
  4. Choose to show All requests.
A recent request to /notification should appear, made by eBay. Expand the item to reveal the full request.
eBay HTTP notification

Although receiving this notification from eBay probably indicates that the listed item has sold on eBay, you cannot trust that this request has really come from eBay. Potentially an attacker could make this request themselves in the hope of convincing you to ship the item, even though they have not paid for it.

The eBay Platform Notifications API includes a signature which can be used to validate the notification. The ebay.Notification.validate method checks that the signature is valid.

The signature is a base64 encoded, MD5 hashed string that is built from the concatenation of the request timestamp with your application identifiers.


expected_signature_input = "%s%s%s%s" % ( timestamp, settings.EBAY_DEVID, settings.EBAY_APPID, settings.EBAY_CERTID )
md5 = hashlib.md5()
md5.update( expected_signature_input )
expected_signature = base64.b64encode( md5.digest() )

We then check that the timestamp is within 10 minutes of the correct time, limiting the replay value of any intercepted message to a 20 minute window:


datediff = datetime.utcnow() - datetime.utcfromtimestamp( timestamp )
if datediff < datetime.timediff( minutes=10 ) and datediff > datetime.timediff( minutes=-10 )
  return True

Strangely and unfortunately, the generated signature is not based on any message content. This means that an intercepted message could have its content completely replaced and it would still produce a valid signature. Consequently, even if the signature validates, any eBay notification should still not be trusted. Instead, consider a notification as an indication to check with eBay on the status of an item.

Confirming a notification from eBay

Once a notification is received, the application confirms that the item has actually been sold. In main.Notification, we create an ebay.Order instance to retrieve the order details from ebay. If the order is considered valid then the purchase can be completed:


item = model.Item.all().filter( "ebay_item_id =", notification.item_id )[0]
# success - now check item status with ebay
order = ebay.Order( item, notification.transaction_id )
purchase = model.Purchase.all().filter( "ebay_transaction_id =", notification.transaction_id )
if purchase.count() == 0:
  if order.success:
    item.fulfil_ebay_order( notification.transaction_id )
    logging.info( 'ebay order was successful' )

The communication with eBay is handled by the ebay.Order class. This class wraps the GetOrders eBay API call with the use case of retrieving details of a single order ID. We use the ItemID and TransactionID that were extracted from the notification:


<?xml version="1.0" encoding="utf-8"?>
<GetOrdersRequest xmlns="urn:ebay:apis:eBLBaseComponents">
  <RequesterCredentials>
    <eBayAuthToken>{{ ebay_token }}</eBayAuthToken>
  </RequesterCredentials>
  <OrderIDArray> OrderIDArrayType
    <OrderID>{{ item_id }}-{{ transaction_id }}</OrderID>
  </OrderIDArray>
  <OrderRole>Seller</OrderRole>
  <OrderStatus>Active</OrderStatus>
</GetOrdersRequest>

If this API call confirms the transaction then we have a sale! The inventory level is reduced and in a production system, the item would be ready to ship. If the inventory has reached zero, the item will no longer appear on the online store. If there is still stock available, the item is relisted on eBay:


if self.available > 0:
  adder = ebay.Add( self, host_url )
  if adder.success:
    logging.info( "relisted %s" % self )
  else:
    logging.warn( "failed to relist %s" % self )

Ending an auction

The previous section covers the case of stock being exhausted by an eBay sale - the item will no longer be listed on the web store and will not be relisted on eBay.

If the last item sells on the web store, the item may have already been listed on eBay and hence will still be available for sale. Since the item is now out of stock, it needs to be removed from eBay.

This is handled in model.Item.fulfil_web_order:


if settings.USE_EBAY and self.available == 0:
  ebay.Remove( self )

This is a relatively simple API call to eBay that requires the previously stored eBay Item ID to be passed to eBay:


<EndFixedPriceItemRequest xmlns="urn:ebay:apis:eBLBaseComponents">
  <ItemID>{{ item_id }}</ItemID>
  <EndingReason>NotAvailable</EndingReason>
  <RequesterCredentials>
    <eBayAuthToken>{{ ebay_token }}</eBayAuthToken>
  </RequesterCredentials>
  <WarningLevel>High</WarningLevel>
</EndFixedPriceItemRequest>

Conclusion

This article has demonstrated the basics of integrating an existing shopping cart with eBay's auction ecosystem.

The article first demonstrated how to list a new item for sale on eBay. Next, code was added to register for notifications, enabling the application to act accordingly when an item sells on eBay. Finally, code was added to relist or remove an item from eBay, depending on inventory levels.

Note that the resulting application is not production-ready. However, it provides the basis for further development. If you decide to take the application further, fork the existing application and consider sending a pull request to help progress the sample application.

Further Information