. tgeorge.net
11/05/2018 at 02:11 PM

Well, it's been over a year since I've blogged, so I guess it's probably a good time to get back at it. I haven't done a blog post about writing code yet, but this project seems like a good one to document as I go along. So let's get started!

I sell on eBay. I don't sell a lot. In fact, I usually only sell 1 thing. But, I'm the only person on the internet who sells this particular item, so it's a pretty decent side hustle! The only issue is... well, I'm lazy. So sometimes I don't always ship it out in a timely manner. To help this, I started prepackaging them. I print out a batch of return labels, and a batch of stamps, throw some in envelopes, and wait. Then when someone buys them, I just print the label with their address, throw it on a ready to go envelope, and drop it in a box down the street. But.... sometimes I'm still too lazy for that. Especially when I have a few orders queued up. All the copying and pasting from eBay, going back and marking the right item as shipped, ugh, it's such a hassle. (Okay, maybe I'm being over-dramatic, but still.) Regardless, I figured there must be a better way!

(Side note: I used to sell on Amazon as well, but my laziness got me blocked :( I've protested several times and each time my request to allow me to sell again gets denied. Hopefully someday I can sell on Amazon again, and I'll update my new software to use Amazon too.)

Luckily... I happen to know a (I like to think REALLY GOOD) software developer. (Hint: it's me!) So, let's write a script to automate a little bit more of this!

I can break my desired software into 3 simple steps:

  1. Get all non-shipped orders from eBay.
  2. Print out shipping labels for each order.
  3. Tell eBay to mark the order as shipped.

So, let's get started on step 1!

eBay offers a really solid API. An API is an Application Programming Interface. Basically, eBay has written code that is hosted on their server. I can call that code from my own software. eBay also offers a wrapper for it's code in C# using NuGet, so that's what I'll be using. An API wrapper means they've already written all the code to call the code, so I call the code that calls the code. Okay, maybe that sounds complicated, but trust me, it's easy! 

The first thing I have to do is authenticate. That means I have to tell eBay's API who I am. Luckily, I'm writing this as a console application for just me, which makes it easier. If I was writing some complicated software that allowed any eBay user to use my code, it'd be way more complicated. I already had an eBay developer account, so I logged into that, created a key set, then I was able to create a UserToken. This UserToken allows me to call eBay's API as my user. This does expire sometime next year, so I'm going to add it to my app.config as an app setting, so I can change it on the fly as needed. It's key is "userToken". Now when I need it in my code I just use:

var userToken = ConfigurationManager.AppSettings["userToken"];


Now, I need to create an ApiContext. This is the object that I will use when calling eBay's API, which contains my userToken. Luckily, again, using eBay's API wrapper, it's super simple:

using eBay.Service.Core.Sdk;
var apiContext = new ApiContext { ApiCredential = new ApiCredential(userToken) };


Now I can use the apiContext to get my orders! eBay doesn't have a way to only get unshipped orders, so I have to get all my orders, then see if it's shipped or not. To do this, I'm creating a new method called, well, GetOrders. It accepts my apiContext as an argument.

static OrderTypeCollection GetOrders(ApiContext apiContext)
{
var getOrdersCall = new eBay.Service.Call.GetOrdersCall(apiContext)
{
NumberOfDays = 14
};
getOrdersCall.Execute();
return getOrdersCall.ApiResponse.OrderArray;
}

Now I call my new GetOrders method like this:

var orders = GetOrders(apiContext);


Then I can simply loop through the orders to see which are unshipped.

foreach (OrderType order in orders)
{
if (!order.ShippedTimeSpecified)
{
// This is where I'll do stuff!
}
}


Now, I need their shipping address. That's included as an AddressType in the OrderType object. I created a quick (and ugly) method to format that the way my printer needs it.

static string FormatAddress(AddressType addressType)
{
var address = new StringBuilder();        address.Append(addressType.Name); address.Append("\r\n"); address.Append(addressType.Street1); address.Append("\r\n");
if (!string.IsNullOrEmpty(addressType.Street2))
{                address.Append(addressType.Street2); address.Append("\r\n"); } address.Append($"{addressType.CityName}, {addressType.StateOrProvince} {addressType.PostalCode}"); return address.ToString(); }


Which I can call like this:

var address = FormatAddress(order.ShippingAddress);                                               


A lot of people used to buy 2 or 3 of my item, so I started selling it in 2 and 3 packs as well. So I technically have 3 item's for sale on eBay, and people can buy multiple quantities of each. Luckily, I require immediate payment, so each order only has 1 transaction, with a quantity. So, I took each item, got it's item ID, then added those as app settings, with the value being how many are included in that listing. Using this method also allows me to continue to sell other random stuff on eBay, and not have it interfere with this.

<add key="itemIdFor1Pack" value="1" />
<add key="itemIdFor2Pack" value="2" />
<add key="itemIdFor3Pack" value="3" />               


Now I need to get the transaction, then I can get the item from that, get the itemID, look it up in my appsettings to determine the initial quantity, and multiply that by the quantity purchased. So, if someone bought 2 of my 3 packs, my total quantity would be 6. I'll explain why I need the quantity in a little bit.

var transaction = order.TransactionArray[0];
var item = transaction.Item;
var itemFromConfig = ConfigurationManager.AppSettings[item.ItemID];
if (itemFromConfig == null) continue; varinitialQuantity = int.Parse(itemFromConfig);
var quantity = initialQuantity * transaction.QuantityPurchased;  


Now, onto step 2.                                              

For my stamps and labels, I use a DYMO LabelWriter 450 TwinTurbo. This printer allows me to print shipping labels and allows me to buy and print stamps using Encidia. It's super convenient. Dymo also offers an SDK (Software Development Kit) which makes printing to it from my own custom software super simple! In fact, it's only 6 lines of code to print a shipping label. I followed this tutorial here to print from C#. That tutorial offers a lot of insight into how to do it, but I was able to remove a lot of that code as it wasn't needed. But basically, I created a label template, which has a spot for the address, and a spot for the quantity. I put the quantity here, so I know which envelope to put the label on, since they're all prepackaged with 1, 2, or 3 written on them. It just makes it easier.

static voidPrintShippingLabel (string address, int quantity)
{
var label = Label.Open(@"C:\Users\Timothy\Documents\DYMO Label\Labels\address-bottom-template.label");
label.SetObjectText("Address", address);
label.SetObjectText("Quantity", quantity.ToString());
var printParams = new LabelWriterPrintParams();
printParams.RollSelection = RollSelection.Left;
            label.Print("DYMO LabelWriter 450 Twin Turbo", printParams);
}

 
Whew, step 2 was way easier than step 1. Let's move onto step 3! 

This ended up being super easy, too, after everything I've already learned about eBay's API in step 1. I just needed to create a new function, called ShipOrder, which accepted the apiContext, transactionId, and itemId as arguments. The really cool part, though, is that I can also leave buyer feedback automatically right here too!

static void ShipOrder(ApiContext apiContext, string transactionId, string itemId)
{
var fulfillOrderCall = new CompleteSaleCall(apiContext)
{
TransactionID = transactionId,
FeedbackInfo = new FeedbackInfoType { CommentType = CommentTypeCodeType.Positive, CommentText = "Fast Payment! A+ Buyer!!" },
Shipped = true,
ItemID = itemId
};
            fulfillOrderCall.Execute();
}

 

All done! That ended up being pretty simple when it was all said and done! I went back and added some try/catch blocks, though. In theory, it should never error out, but in case it does, it'll fail gracefully. I also added a fun icon to the project and added it to my task-bar. Now I just click it whenever I'm ready, and it'll do all the work!

View the final product here: https://gist.github.com/tgeorge91/02cd1ddc1ff027011d97fc204a8fdcdf 

All in all, this was about 4 hours of work. It was a fun challenge and I'm super excited that it actually worked! Hopefully this helps to streamline my process and deliver a customer experience on my eBay store.

UPDATE 11/17/2018:

I had one cool person who "accidentally" placed an order and requested that I cancel it. I did this all on eBay's website, but when I ran my script, it still printed their label! Luckily, I was sort of expecting this, so it was easy to fix. For some reason, the order status from eBay's API was still "Active" even though "Cancelled" is an option. I discovered that the best way to do resolve this (best way - okay maybe easiest way?) was to check that the AmountPaid was greater than 0, since the order I refunded had a negative value. I updated the following line to this:

if (!order.ShippedTimeSpecified && order.AmountPaid.Value > 0)


And everything is good to go again! I've updated my gist to reflect this as well.

codingcsharp