I'm making an eCommerce marketplace for used goods using Ruby on Rails with the PayPal API for completing purchases. So essentially the deal is that people can buy and sell their stuff - like eBay or Half.com. All items are used. Here's my question: used goods means that every item is unique - at least in the sense that sellers typically only carry 1 of the used good they are selling. That brings me my question, for any developers who have run into this kind of problem before: how should I handle the situation in which:

Seller lists his sweatshirt

Person #1 adds the sweatshirt to his cart

Person #2 adds the same sweatshirt to his cart

Person #1 checks out via PayPal

Now, what do I do with Person #2? Clearly, he can't check out the same sweatshirt, because the seller only has 1 sweatshirt. What I want to know is how I should handle this situation. So far, I've come up with two possible solutions, but neither seems satisfactory to me. (By the way, it might be relevant to add that a user's current cart is a session variable)

Option #1: When Person #1 adds the sweatshirt to his cart, you flag the sweatshirt with a boolean field such as available = false. The downside to this is that a user can add an item to his cart, and then go idle. Thus, no one can purchase the item until Person #1's cart expires.

Option #2: The sweatshirt is only flagged as unavailable when I get the IPN from PayPal. The downside to this is that you could theoretically have Person #1 and #2 checking out from PayPal at once, and thus they would both buy the sweatshirt, and it wouldn't be until after I get the IPN that I realize I've sold the product twice.

What does StackOverflow think? Is there anyone with experience in this field who can offer some insight?

Prevent both of them from end up buying same Item which has 1 quantity in your inventory.

One way to handle this scenario in my opinion is(Which is a kind of upgraded version of your option #1)-

When Person 1 adds an item in their cart you will(if not then you should) be changing the order status i.e. in cart, address, payment, complete etc.

So when Person 1 adds Item A in their cart(status: in cart) block it for 10 minutes for others(Person 2) to add them into their cart. Now you need to have a rake script or Delayed Job using crone job running on your server which checks for every products, in this case Item A in orders table with in cart status for 10 or more than 10 minutes and flush/ remove those products from that row. Which will enable others(Person 2) to add that Item into their cart. And have others(Person 2) and Person 1 updated about your process of this 10 minutes flushing thing with some sort of notice. For example: Item 1 Will be available in Time Counter running to Person 2 and Item 1 will go in Time Counter running to Person 1. Here this process will make a sense of urgency in users mind and you'll have a control over your inventory from not getting an item sold more than its count on hand/ quantity. You can check this website for a live and running implementation - http://www.thepeacockparade.com/

Hope it gives you a fair idea to handle this situation. And yes if, you get any better way to do the same thing please, do keep me updated about it because, I'm also looking for an upgrade.

Thanks

Update on performance

After implementing this process you may end up with some performance issues on your website. One way I figured out a way to keep your application running and, serving your customers and, the background process synced at the same time is, If you useamazon rds or any other cloud database service you can have two different application server altogether. One for your customers to serve and one for performing background processes such as, cleaning temporary data, files, rake tasks, uploading data etc. And since you have your application database totally outside of you application servers, it can be updated from both applications. So, it will keep your main application serving even better to its users instead of starving for memory used by background processes.

Update on database settings

Rails is awesome when it comes to database, tables and associations. If you are new to amazon cloud database service then, checkout their plans here: http://aws.amazon.com/rds/pricing. Best part about having your database on amazon rds are -

You are free to select the near endpoint/ server location to
serve your database for your application, which will reduce latency of
your db queries and your server load in processing them.

You can use multiple applications to access it. Apparently for different purposes, i.e. One for uploading data, second for running analytics etc.

You have to pay only for what you use.

If anything(sometimes for some reasons I prefer being a pessimist) goes wrong and server crashes for any weird reason. You are not screwed!! Your DATABASE is safe. Just set up a new application and voila!! You are back in no time.

You have freedom to automate database backup without messing your head into BASH SCRIPTS.

You can expand it as your Business grow.

To set up an amazon rds database go to your amazon rds console: https://console.aws.amazon.com/rds/home. Select your preferred region from Navigation pane on left side of the page. Click on "Launch DB Instance" button, select you preferred Database and follow rest of the flow, i.e. selecting instance type etc.

Now if you have your rds instance running, you are able to see an endpoint something like this: database-name.random-string.region-endpoint.rds.amazonaws.com. In your rails application edit and update your config/database.yml to this:

Looked surprised?? Yes, that's it!! You are all set to you use your application with your new amazon rds DB instance. Now rake db:create to test connection and rake db:migrate to create your tables.

Here I'd like to add one more thing. If you care to make your life better then, you should use amazon s3(simple storage service) too. It's damn cheap and reliable, Check it out here: http://aws.amazon.com/s3/

Wow! Thanks for the very detailed response -- I like this idea a lot, not only because it works, but also, as you said, because the timer gives the user a 'sense of urgency' and therefore an incentive to buy sooner rather than later. I'll leave this question open for a few days just to get more ideas, but this is a great answer. Thanks a lot. +1
–
varatisJan 17 '12 at 0:31

You're most welcome. But I'm trying to figure out even more better idea to crack this and possibly without breaking performance. After posting this I thought a bit more on this process and ended up with an idea for keeping your performance and yet the process in place and synced. Please see the updated part of the post.
–
SuryaJan 17 '12 at 5:45

That's an interesting idea with the servers. But as for your original idea, I'm kind of curious how sites like Half.com or Amazon handle this situation: although I haven't tested checking out an item on either site the full way, it seems like both allow different users to add the same item to their cart.
–
varatisJan 17 '12 at 14:50

Also do you have some sort of resource for the stuff about using an application cloud database outside of application servers? I'm unfamiliar with the subject, so a reference would be handy.
–
varatisJan 17 '12 at 14:51

I'm not sure but my guess would be virtual stock. May be they show virtual stock and just let customers choose the particular item n times and then place an order for that item to their vendors after getting order request from customers.
–
SuryaJan 18 '12 at 5:18

Rather than having a locked flag and having a background process that finds locked items and expires those locks, you could instead have locked_by and locked_until flags.

When a person adds something to their cart, set locked_by to them and locked_until to 10 minutes in the future. You might want to consider bumping locked_until if they take certain actions (e.g. give them another few minutes once they actually start the checkout process).

When you want to test is something is available or not, simply check whether locked_until is in the past or not. This way you don't need to reset the locking columns once the grace period has passed.

If there are multiple instances of the same product (i.e. there is stock of 5 of this sweatshirt), you could still use this approach by having a second model that represents the actual physical objects, i.e. you might have

class Product < AR:Base
has_many :physical_objects
end

and instance of PhysicalObject represent the actual item (rather than the idealised object). Since you have 5 sweatshirts, the sweatshirt product has 5 physical objects. PhysicalObject could also track stuff that might vary on a per item basis (for example which warehouse it is in).

The locking columns then sit on the physical object: when someone adds a product to their cart you lock one of the physical objects for that product, when an order is confirmed in you can make the physical object as permanently gone (or even delete it). If you use the same locking strategy as before then you don't need to do any sweeping type tasks to clear locks.

Whatever you do, you should probably look into optimistic locking, to prevent race conditions if two customers try to add the same product to their cart at the same time

How is this different from Surya's answer? Also, note that PhysicalObject probably wouldn't be very useful, because this is a used goods site (so there is in essence only one of each item, and no warehouses).
–
varatisJan 19 '12 at 17:16

It's different in that you don't have to do periodic checks to expire old locks. If you only have one of each item you could just go with the approach in the first half of the answer.
–
Frederick CheungJan 19 '12 at 18:12

+1 I agree that background process is unnecessary. And this is a really good answer to my question on performance. Thanks a ton!! I was wondering what if for some reason background process doesn't work or stops working?? But, now I know that, they're not required in the first place.
–
SuryaNov 29 '12 at 6:29